JDK-6640532 : Graphics.getFontMetrics() throws NullPointerException
  • Type: Bug
  • Component: client-libs
  • Sub-Component: 2d
  • Affected Version: 6
  • Priority: P3
  • Status: Closed
  • Resolution: Fixed
  • OS: linux
  • CPU: x86
  • Submitted: 2007-12-11
  • Updated: 2011-03-08
  • Resolved: 2011-03-08
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 6 JDK 7
6u10Fixed 7 b28Fixed
Description
FULL PRODUCT VERSION :
JRE 1.6.0_03-b05

ADDITIONAL OS VERSION INFORMATION :
Linux 2.6.21-1.3194.fc7 #1 SMP Wed May 23 22:35:01 EDT 2007 i686 i686 i386 GNU/Linux

A DESCRIPTION OF THE PROBLEM :
Graphics.getFontMetrics() throws NullPointerException.

Happens on FontMetrics.charsWidth  also.

The problem seems to be in sun.font.FontManager.addToPool(). The following assertion fails.

// is it possible for this to be the same font?
assert fontFileCache[lastPoolIndex] != font;

This can happen when the sun.font.TrueTypeFont.readBlock() was executed, and during execution if the threads were interrupted.  Several threads executing readBlock() needs to be interrupted, for this problem to show up.  In such a scenario, FontManager cache might be containing multiple references to the same TrueType font.  When FontManager.addToPool attempts to close a font file which is same as the one getting added to pool, we get this exception.

One solution would be in the TrueTypeFont.open(), assign the fontfile after adding it to pool.

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Run the attached testcase and keep selecting entries in  the combo box which contains the list of fonts.  Selecting a entry in the Combox  enables getting the fontmetrics of that font.

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Should not throw NPE
ACTUAL -
throws NPE

ERROR MESSAGES/STACK TRACES THAT OCCUR :
Exception in thread "Thread-1" java.lang.NullPointerException
at sun.font.TrueTypeFont.readBlock(Unknown Source)
        at sun.font.TrueTypeFont.readBlock(Unknown Source)
        at sun.font.TrueTypeFont.createScaler(Native Method)
        at sun.font.TrueTypeFont.getScaler(Unknown Source)
        at sun.font.FileFontStrike.<init>(Unknown Source)
        at sun.font.FileFont.createStrike(Unknown Source)
        at sun.font.Font2D.getStrike(Unknown Source)
        at sun.font.Font2D.getStrike(Unknown Source)
        at sun.font.CompositeStrike.getStrikeForSlot(Unknown Source)
        at sun.font.CompositeStrike.getFontMetrics(Unknown Source)
        at sun.font.FontDesignMetrics.initMatrixAndMetrics(Unknown Source)
        at sun.font.FontDesignMetrics.<init>(Unknown Source)
        at sun.font.FontDesignMetrics.getMetrics(Unknown Source)
        at sun.java2d.SunGraphics2D.getFontMetrics(Unknown Source)
        at lab.FontNPETest$2.run(FontNPETest.java:57)
        at java.lang.Thread.run(Unknown Source)

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
package lab;

import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.GraphicsEnvironment;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JComboBox;
import javax.swing.JFrame;

public class FontNPETest {

  private static Font gfont = null;
  
  public static void main(String[] args) {
    final JFrame fr = new JFrame("FontTest");
    final JComboBox box = createCombo();
    fr.getContentPane().add(box);
    fr.pack();
    fr.setVisible(true);
    fr.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    startFontManipulation(fr);
  }
  
  private static JComboBox createCombo() {
    final JComboBox box = new JComboBox(
        GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts());
    box.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        Font f = (Font)box.getSelectedItem();
        f = f.deriveFont(12f);
        setFont(f);
      }
    });
    return box;
  }
  
  private static synchronized Font getFont() {
    return gfont;
  }
  
  private static synchronized void setFont(Font f) {
    gfont = f;
  }
  
  private static void startFontManipulation(final JFrame fr) {
    Thread t1 = new Thread(new Runnable() {
      public void run() {
          while(true) {
              Font f = getFont();
              if(f != null) {
                Image img = fr.createImage(100, 100);
                Graphics g = img.getGraphics();
                FontMetrics fm = g.getFontMetrics(gfont);
                final Thread thisT = Thread.currentThread();
                Thread t = new Thread(new Runnable() {
                    public void run() {
                        thisT.interrupt();
                    }
                });
                t.start();
                try {
                   fm.charsWidth("ABCDE".toCharArray(), 0, 5);
                } catch(NullPointerException e) {
                  e.printStackTrace();
                }
                g.dispose();
              }
          }
      }
    });
    t1.start();
  }
}

---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
Don't interrupt the thread.

Comments
EVALUATION If the thread is being interrupted repeatedly, its possible we end up with the same font filling a number of slots, and since this code is called only from within an open() call, to register that the font file is open for reading, its a bad idea to close() it as happens here, since its destined for almost immediate use : // is it possible for this to be the same font? assert fontFileCache[lastPoolIndex] != font; /* replace with new font, poolSize is unchanged. */ fontFileCache[lastPoolIndex].close(); fontFileCache[lastPoolIndex] = font; Need to revise this code to detect the addition of duplicates. I can also see how we can get a deadlock when updating the pool. Its hard to capture this without fixing the first problem, after which I was able to see an instance Thread-A locks font1, then tries to acquire the pool lock at sun.font.FontManager.addToPool(FontManager.java:287) - waiting to lock <0xf47a6fb8> (a [Lsun.font.FileFont;) at sun.font.TrueTypeFont.open(TrueTypeFont.java:282) - locked <0xf485cec0> (a sun.font.TrueTypeFont) at sun.font.TrueTypeFont.readBlock(TrueTypeFont.java:313) - locked <0xf485cec0> (a sun.font.TrueTypeFont) meanwhile Thread-B first acquires a lock on font2, then the pool lock, and then needs the lock on font1 to close() it and hence free it from the pool : at sun.font.TrueTypeFont.close(TrueTypeFont.java:304) - waiting to lock <0xf485cec0> (a sun.font.TrueTypeFont) at sun.font.FontManager.addToPool(FontManager.java:309) - locked <0xf47a6fb8> (a [Lsun.font.FileFont;) at sun.font.TrueTypeFont.open(TrueTypeFont.java:282) - locked <0xf485d5f0> (a sun.font.TrueTypeFont) at sun.font.TrueTypeFont.getTableBuffer(TrueTypeFont.java:819) - locked <0xf485d5f0> (a sun.font.TrueTypeFont) The simplest fix here is for close() to be moved outside the synchronised block.
13-12-2007