JDK-8189092 : ArrayIndexOutOfBoundsException on Linux in getCachedGlyph
  • Type: Bug
  • Component: javafx
  • Sub-Component: graphics
  • Affected Version: 8,9,10
  • Priority: P3
  • Status: Resolved
  • Resolution: Fixed
  • OS: generic
  • CPU: generic
  • Submitted: 2017-10-09
  • Updated: 2020-06-22
  • Resolved: 2019-10-26
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 8 Other
8u261Fixed openjfx11.0.8Fixed
Related Reports
Duplicate :  
Description
FULL PRODUCT VERSION :
jdk 1.8.0_144 x64

ADDITIONAL OS VERSION INFORMATION :
Windows 7 64 SP1

A DESCRIPTION OF THE PROBLEM :
I have a loop that draws all glyphs of a font, and on each round does a snapshot to compute font bounding box up to current glyph.

With unifont-8.0.01.ttf, which supports many glyphs, exceptions are thrown for some (all?) code points between 0xFB1F and 0xFEFE.

Two exceptions are thrown:

1) One in what seems to be the rendering thread.
2) One by snapshot.


STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Run the test case code.

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
No exception is thrown.
ACTUAL -
Exceptions in Canvas.snapshot(...) and QuantumRenderer.


ERROR MESSAGES/STACK TRACES THAT OCCUR :
java.lang.ArrayIndexOutOfBoundsException: -30
    at com.sun.prism.impl.GlyphCache.getCachedGlyph(GlyphCache.java:246)
    at com.sun.prism.impl.GlyphCache.render(GlyphCache.java:147)
    at com.sun.prism.impl.ps.BaseShaderGraphics.drawString(BaseShaderGraphics.java:2101)
    at com.sun.javafx.sg.prism.NGText.renderText(NGText.java:312)
    at com.sun.javafx.sg.prism.NGText.renderContent2D(NGText.java:270)
    at com.sun.javafx.sg.prism.NGShape.renderContent(NGShape.java:261)
    at com.sun.javafx.sg.prism.NGCanvas.handleRenderOp(NGCanvas.java:1433)
    at com.sun.javafx.sg.prism.NGCanvas.renderStream(NGCanvas.java:1097)
    at com.sun.javafx.sg.prism.NGCanvas.renderContent(NGCanvas.java:606)
    at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2053)
    at com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1945)
    at com.sun.javafx.tk.quantum.QuantumToolkit$5.draw(QuantumToolkit.java:1393)
    at com.sun.javafx.tk.quantum.QuantumToolkit$5.run(QuantumToolkit.java:1429)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
    at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:308)
    at com.sun.javafx.tk.RenderJob.run(RenderJob.java:58)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at com.sun.javafx.tk.quantum.QuantumRenderer$PipelineRunnable.run(QuantumRenderer.java:125)
    at java.lang.Thread.run(Thread.java:748)
Exception in thread "JavaFX Application Thread" java.lang.IllegalArgumentException: Unrecognized image loader: null
    at javafx.scene.image.WritableImage.loadTkImage(WritableImage.java:240)
    at javafx.scene.image.WritableImage.access$000(WritableImage.java:46)
    at javafx.scene.image.WritableImage$1.loadTkImage(WritableImage.java:51)
    at javafx.scene.Scene.doSnapshot(Scene.java:1236)
    at javafx.scene.Node.doSnapshot(Node.java:1864)
    at javafx.scene.Node.snapshot(Node.java:1942)
    at bugz.JfxTextSnapshotMain.drawAndSnapshotLoop(JfxTextSnapshotMain.java:82)
    at bugz.JfxTextSnapshotMain.access$0(JfxTextSnapshotMain.java:48)
    at bugz.JfxTextSnapshotMain$1.run(JfxTextSnapshotMain.java:36)
    at com.sun.javafx.application.PlatformImpl.lambda$null$173(PlatformImpl.java:295)
    at java.security.AccessController.doPrivileged(Native Method)
    at com.sun.javafx.application.PlatformImpl.lambda$runLater$174(PlatformImpl.java:294)
    at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
    at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at com.sun.glass.ui.win.WinApplication.lambda$null$148(WinApplication.java:191)
    at java.lang.Thread.run(Thread.java:748)


REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.Pane;
import javafx.scene.text.Font;
import javafx.stage.Stage;

public class JfxTextSnapshotMain extends Application {
    private static final boolean ANTI_BUG_MODIF_1 = false;
    private static final boolean VARIANT_BUG_MODIF_1 = false;
    private static final String UNIFONT_URL = "file:src/main/java/bugz/unifont-8.0.01.ttf";
    private static final int FIRST_BAD_CODE_POINT = 0xFB1F;
    private static final int LAST_BAD_CODE_POINT = 0xFEFE;
    public static void main(String[] args) {
        launch(args);
    }
    public JfxTextSnapshotMain() {
    }
    @Override
    public void start(Stage primaryStage) {
        final Pane root = new Pane();
        final Scene scene = new Scene(root, 100.0, 100.0);
        final Canvas canvas = new Canvas(100.0, 100.0);
        root.getChildren().add(canvas);
        
        final Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println("drawAndSnapshotLoop()...");
                System.out.flush();
                try {
                    drawAndSnapshotLoop(canvas);
                } finally {
                    System.out.println("...drawAndSnapshotLoop()");
                    System.out.flush();
                }
            }
        };
        Platform.runLater(runnable);

        primaryStage.setScene(scene);
        primaryStage.show();
    }
    private static void drawAndSnapshotLoop(Canvas canvas) {
        final Font font = Font.loadFont(UNIFONT_URL, 12.0);
        if (font == null) {
            // Issue doesn't seem to happen with fonts that only have a few glyphs.
            throw new AssertionError("font could not be loaded");
        }
        canvas.getGraphicsContext2D().setFont(font);
        
        WritableImage img = new WritableImage(1,1);
        
        for (int cp = FIRST_BAD_CODE_POINT - 10; cp <= LAST_BAD_CODE_POINT + 10; cp++) {
            
            final String text = new String(new int[]{cp}, 0, 1);
            if (ANTI_BUG_MODIF_1) {
                // No issue if using strokeText(...).
                canvas.getGraphicsContext2D().strokeText(text, 0, 0);
            } else {
                canvas.getGraphicsContext2D().fillText(text, 0, 0);
            }

            System.out.println("cp = 0x" + Integer.toHexString(cp).toUpperCase());
            System.out.flush();

            final WritableImage inputImg;
            if (VARIANT_BUG_MODIF_1) {
                // Reusing a (most often) large enough image prevents the
                // IllegalArgumentException,
                // but not the ArrayIndexOutOfBoundsException,
                // which then occurs multiple times as the loop
                // keeps running.
                inputImg = img;
            } else {
                inputImg = null;
            }
            img = canvas.snapshot(null, inputImg);
        }
    }
}

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


Comments
Changeset: 5a70b0c5 Author: Phil Race <prr@openjdk.org> Date: 2019-10-26 15:14:47 +0000 URL: https://git.openjdk.java.net/jfx/commit/5a70b0c5 8189092: ArrayIndexOutOfBoundsException on Linux in getCachedGlyph Reviewed-by: kcr ! modules/javafx.graphics/src/main/java/com/sun/javafx/font/CompositeGlyphMapper.java ! modules/javafx.graphics/src/main/java/com/sun/prism/impl/GlyphCache.java ! modules/javafx.graphics/src/main/native-font/fontpath_linux.c
26-10-2019

No, it is a different issue and one we are most likely to see on Linux. The fundamental issue here is that we don't limit the number of fallback fonts that we get from Linux fontconfig and we store that slot index number the most significant byte of the glyph code making the glyph code negative. If it is > 127, then when the code in getCachedGlyph does an arithmetic shift it will always end up with a negative value. Also the "%" operator will be operating on a negative int and have a similar problem. So this fixes these issues by using a logical shift in one case and bitwise and with a mask in the other. But we also need to make sure that we don't store more slots than we need. There was a fix several years ago in the Java 2D copy of fontpath_linux.c which was never ported to FX. So we need to do that too. Also for extra safety, although it ought not to be needed we can make sure that if some how there are > 255 slots, we just stop when we reach it when looking for a font that supports a code point and treat it as missing. convertToGlyph is the place to do this.
25-10-2019

[~prr] Can you confirm that this should now be closed as a duplicate of JDK-8207839 ?
22-01-2019

Instead of modifying field type from char to int, it may stay char but we should use the proper bit mask to convert java signed char to unsigned int... like glyphCode & 0xFFFF ?
11-12-2018

Seems I can help here, I will handle fixing this bug asap
11-12-2018

This line in java/com/sun/javafx/font/directwrite/DWGlyph.java looks very suspicious : run.glyphIndices = (short)glyphCode; Since there can be up to 65535 glyph indices you can't store it in a short .. it has to be at least an unsigned short, but in Java there is no unsigned, so you either have to use an int or be sure that 32767 is the max. So using short here is a mistake. And no surprise, that when I look into the directwrite.cpp I see that DW uses it as unsigned short .. const UINT16 glyphIndices[glyphCount] = {arg1}; So we need to go through all the Java code and convert from short to int and downcast to unsigned short in native.
11-10-2017

Hmm .. I can reproduce on Windows but not on Linux which points to a bug in DirectWrite-specific code.
11-10-2017

Somewhere glyph code values have overflowed such that we are storing a negative glyph code and this results in a negative index into an array.
11-10-2017

Checked in 8u40, 8u144, 9-ea+181, 10-ea+23 and issue is reproducible in all of them (Windows 10). Getting below output when ran the attached reproducible test case (JfxTextSnapshotMain.java) : drawAndSnapshotLoop()... cp = 0xFB15 cp = 0xFB16 cp = 0xFB17 cp = 0xFB18 cp = 0xFB19 cp = 0xFB1A cp = 0xFB1B cp = 0xFB1C cp = 0xFB1D cp = 0xFB1E cp = 0xFB1F java.lang.ArrayIndexOutOfBoundsException: -30 at javafx.graphics/com.sun.prism.impl.GlyphCache.getCachedGlyph(GlyphCache.java:246) ---- --- ...drawAndSnapshotLoop() Exception in thread "JavaFX Application Thread" java.lang.IllegalArgumentException: Unrecognized image loader: null at javafx.graphics/javafx.scene.image.WritableImage.loadTkImage(WritableImage.java:242)
10-10-2017