Maintenance Notice

The bugs.java.com site will be undergoing maintenance on 15th Dec 2017 21:00 PST to 16th Dec 2017 1:00 AM PST.
JDK-6359722 : Uncatchable recursive NullPointerException at sun.font.TrueTypeFont.open()
  • Type: Bug
  • Component: client-libs
  • Sub-Component: 2d
  • Affected Version: 5.0
  • Priority: P5
  • Status: Closed
  • Resolution: Fixed
  • OS: linux
  • CPU: x86
  • Submitted: 2005-12-06
  • Updated: 2011-04-05
  • Resolved: 2011-04-05
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 Availabitlity Release.

To download the current JDK release, click here.
Other JDK 6 JDK 7
5.0u17Fixed 6u11Fixed 7 b03Fixed
Related Reports
Relates :  
Description
FULL PRODUCT VERSION :
java version "1.5.0_05"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_05-b05)
Java HotSpot(TM) Client VM (build 1.5.0_05-b05, mixed mode, sharing)

ADDITIONAL OS VERSION INFORMATION :
Linux 2.4.21-37.ELsmp #1 SMP Wed Sep 7 13:28:55 EDT 2005 i686 i686 i386 GNU/Linux

A DESCRIPTION OF THE PROBLEM :
Our java server (using intensively font files) was freezing almost every day reporting from time to time the recursive following stack trace in the log file :

2005-11-16 11:51:23,700 ERROR [STDERR] java.awt.FontFormatException: java.lang.NullPointerException
2005-11-16 11:51:23,700 ERROR [STDERR] 	at sun.font.TrueTypeFont.open(TrueTypeFont.java:251)
2005-11-16 11:51:23,700 ERROR [STDERR] 	at sun.font.TrueTypeFont.readBlock(TrueTypeFont.java:279)
2005-11-16 11:51:23,700 ERROR [STDERR] 	at sun.font.FileFont.getGlyphAdvance(Native Method)
2005-11-16 11:51:23,700 ERROR [STDERR] 	at sun.font.FileFontStrike.getGlyphAdvance(FileFontStrike.java:491)
2005-11-16 11:51:23,701 ERROR [STDERR] 	at sun.font.FileFontStrike.getCodePointAdvance(FileFontStrike.java:502)
2005-11-16 11:51:23,701 ERROR [STDERR] 	at sun.font.FontDesignMetrics.handleCharWidth(FontDesignMetrics.java:226)
2005-11-16 11:51:23,701 ERROR [STDERR] 	at sun.font.FontDesignMetrics.getLatinCharWidth(FontDesignMetrics.java:235)
2005-11-16 11:51:23,701 ERROR [STDERR] 	at sun.font.FontDesignMetrics.stringWidth(FontDesignMetrics.java:285)
2005-11-16 11:51:23,701 ERROR [STDERR] 	at com.sun.java.swing.SwingUtilities2.stringWidth(SwingUtilities2.java:292)
2005-11-16 11:51:23,701 ERROR [STDERR] 	at javax.swing.SwingUtilities.computeStringWidth(SwingUtilities.java:761)
...

We couldn't reproduce this behaviour on our test server until we tried this simple test : start the server and run a scenario using font files, then delete the .tmp file generated in the jvm temp directory.
Apparently, this leads to the error we had on the production server. For some reason, the tmp directory is cleaned up by some daemon. Though we still have to investigate about this linux daemon which should not run, the error generated in the jvm can not be recovered and finally freezes the server which should not normally happen.

Indeed, the open or readBlock method is synchronized and any other thread trying to use font files even if the tmp file still exists is stuck by the one that tries indefinitely to write error logs because of the NullPointerException.


STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Start the server and run a scenario using font files, then delete the .tmp file generated in the jvm temp directory.


EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
If the font tmp file was deleted : throw an exception catchable by the calling thread.
ACTUAL -
Recursive stack traces in the log file  + blocked threads + finally server freeze.

ERROR MESSAGES/STACK TRACES THAT OCCUR :
:

2005-11-16 11:51:23,700 ERROR [STDERR] java.awt.FontFormatException: java.lang.NullPointerException
2005-11-16 11:51:23,700 ERROR [STDERR] 	at sun.font.TrueTypeFont.open(TrueTypeFont.java:251)
2005-11-16 11:51:23,700 ERROR [STDERR] 	at sun.font.TrueTypeFont.readBlock(TrueTypeFont.java:279)
2005-11-16 11:51:23,700 ERROR [STDERR] 	at sun.font.FileFont.getGlyphAdvance(Native Method)
2005-11-16 11:51:23,700 ERROR [STDERR] 	at sun.font.FileFontStrike.getGlyphAdvance(FileFontStrike.java:491)
2005-11-16 11:51:23,701 ERROR [STDERR] 	at sun.font.FileFontStrike.getCodePointAdvance(FileFontStrike.java:502)
2005-11-16 11:51:23,701 ERROR [STDERR] 	at sun.font.FontDesignMetrics.handleCharWidth(FontDesignMetrics.java:226)
2005-11-16 11:51:23,701 ERROR [STDERR] 	at sun.font.FontDesignMetrics.getLatinCharWidth(FontDesignMetrics.java:235)
2005-11-16 11:51:23,701 ERROR [STDERR] 	at sun.font.FontDesignMetrics.stringWidth(FontDesignMetrics.java:285)
2005-11-16 11:51:23,701 ERROR [STDERR] 	at com.sun.java.swing.SwingUtilities2.stringWidth(SwingUtilities2.java:292)
2005-11-16 11:51:23,701 ERROR [STDERR] 	at javax.swing.SwingUtilities.computeStringWidth(SwingUtilities.java:761)
...



REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
Using this fonction in loop on several fonts and images while deleting the tmp font files in the jvm tmp dir :

    private static boolean checkWidth(String text, Font font, Graphics graphics, int imgWidth) {
        final FontMetrics fm = graphics.getFontMetrics(font);
        final int width = SwingUtilities.computeStringWidth(fm, text);
        return imgWidth > width;
    }
---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
Using our own java.io.tmpdir directory and cleaning up the tmp files manually when the corresponding font file is not used any more solved the problem.
Here is a piece of code that produces the exception.
You need to place a directory called 'font' with more than 20 ttf fonts in
it (so at least 21) at the same level you compile and run this code (i don't
know if our fonts are special but I guess you can reproduce the problem with
any font files).
Compile and just run the test :
java -cp . CheckFontWidth

/*
 * Copyright 2004 Mediabilis S.A.S., 135 rue de Billancourt, 
 * 92514 Boulogne-Billancourt, France
 * www.mediabilis.com, ###@###.###
 * All Rights Reserved.
 *
 * Created on 29 oct. 2004
 */
import java.awt.Font;
import java.awt.FontFormatException;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;

import javax.swing.SwingUtilities;


/**
 *
 * @version 1.0 29 oct. 2004
 * @author j.guyard
 */
public class CheckFontWidth {
    
    // Font cache map
    private static Map fonts;
    
    public static void main(String[] args)  {
        try {
            // Init font cache
            fonts = new HashMap();
            // list font file names
            File fontdir = new File("font");
            String[] fontStrs = fontdir.list();
            System.out.println("Nb of fonts = " + fontStrs.length);
            Font font = null;
            int i = 0;
            BufferedImage img = new
BufferedImage(10,10,BufferedImage.TYPE_INT_RGB);
            while (true)
            {
                System.out.println("i = " + i);
                // get and cache next font
                font = getFont("font/" + fontStrs[i++]);
                
                // Use a text that changes (hour for instance) 
                String text = String.valueOf(System.currentTimeMillis());
                
                // Compute width
                int width =
SwingUtilities.computeStringWidth(img.createGraphics().getFontMetrics(font),
text);
                
                // Reset counter and delete tmp file
                if (i == (fontStrs.length)) {
                    System.out.println("Reset counter and delete tmp
file.");
                    i = 0;
                    File tmp = new
File(System.getProperties().getProperty("java.io.tmpdir"));
                    if (tmp.exists()) {
                        File[] tmpFiles = tmp.listFiles(new
FilenameFilter(){
                            public boolean accept(File file, String str) {
                                final String [] ext = str.split("[.]");
                                if (ext.length >= 2) {
                                    return ext[ext.length-1].equals("tmp");
                                } else { 
                                    return false;
                                }
                            }
                        });
                        for (int j = 0; j < tmpFiles.length; j++) {
                            File file = tmpFiles[j];
                            file.delete();
                        }
                    }
                   
                }
            }
            
        } catch (IOException e) {
            e.printStackTrace();
        } catch (FontFormatException e) {
            e.printStackTrace();
        }
    }
    
    // Font loading method
    public static Font getFont(String path) throws IOException,
FontFormatException {
        final File fontFile = new File(path);
        final Font font;
        if (!fonts.containsKey(path)) {
            InputStream fontStream = null;
            try {
                fontStream = new FileInputStream(path);
                font = Font.createFont(Font.TRUETYPE_FONT, fontStream);
            } finally {
                if (null != fontStream) {
                    try {
                        fontStream.close();
                    } catch (IOException ioe) {
                    }
                }
            }
            fonts.put(path, font);
        } else {
            font = (Font) fonts.get(path);
        }
        return font;
    }
    
}

Comments
EVALUATION We have implemented code that can longjmp out of bad reads once its determined its an unrecoverable error. The system marks the font file as unusable and logging has been added so severe errors like this this can be detected (-Dsun.java2d.font.debugfonts=severe) without too much overhead.
2006-09-25

EVALUATION Not surprisingly this is still reproducible in 1.6. We could be right in the middle of rasterising a glyph or reading a layout table when the file vanishes on us. I don't know that this is something we would regard as anything other than a user error. tmp files can be used by any part of the JDK or any running program. Removing it whilst in use is not a valid thing to do. I expect its possible we could detect excessive recursion or similar in readBlock() and throw a runtime exception which sends us back though the native layer up to the original call to sun.font.FileFont.getGlyphImage where we could catch it and declare the font "bad". This should prevent us reusing the scaler instance. But there's many other places in the font system where we may initiate a read to the font file. Is it really worth trying to design recovery paths for all cases where someone may simply delete the underlying font file whilst we are in operation?
2005-12-06