JDK-8155903 : Crash while running imported/w3c/canvas/2d.gradient.interpolate.overlap2.html
  • Type: Bug
  • Component: javafx
  • Sub-Component: graphics
  • Affected Version: 8,9
  • Priority: P2
  • Status: Resolved
  • Resolution: Fixed
  • OS: windows
  • CPU: x86
  • Submitted: 2016-05-03
  • Updated: 2016-05-12
  • Resolved: 2016-05-12
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.
8u112Fixed 9Fixed
Open the test case[1] in simple WebView to produce the crash.

[1] http://w3c-test.org/2dcontext/fill-and-stroke-styles/2d.gradient.interpolate.overlap2.html

Following exception was thrown,

        at com.sun.javafx.webkit.prism.PrismInvoker.runOnRenderThread(PrismInvoker.java:91)
        at com.sun.javafx.webkit.prism.RTImage.getPixelBuffer(RTImage.java:162)
        at com.sun.webkit.network.URLLoader.twkDidFinishLoading(Native Method)
        at com.sun.webkit.network.URLLoader.notifyDidFinishLoading(URLLoader.java:844)
        at com.sun.webkit.network.URLLoader.lambda$didFinishLoading$5(URLLoader.java:835)
        at com.sun.javafx.application.PlatformImpl.lambda$runLater$5(PlatformImpl.java:315)
        at java.security.AccessController.doPrivileged(Native Method)
        at com.sun.javafx.application.PlatformImpl.lambda$runLater$6(PlatformImpl.java:314)
        at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:96)
        at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
        at com.sun.glass.ui.win.WinApplication.lambda$runLoop$3(WinApplication.java:189)
        at java.lang.Thread.run(Thread.java:804)
Caused by: java.util.concurrent.ExecutionException: java.lang.OutOfMemoryError: Requested array size exceeds VM limit
        at java.util.concurrent.FutureTask.report(FutureTask.java:123)
        at java.util.concurrent.FutureTask.get(FutureTask.java:193)
        at com.sun.javafx.webkit.prism.PrismInvoker.runOnRenderThread(PrismInvoker.java:89)
        ... 11 more
Caused by: java.lang.OutOfMemoryError: Requested array size exceeds VM limit
        at com.sun.prism.impl.paint.MultipleGradientContext.calculateSingleArrayGradient(MultipleGradientContext.java:263)
        at com.sun.prism.impl.paint.MultipleGradientContext.calculateLookupData(MultipleGradientContext.java:220)
        at com.sun.prism.impl.paint.MultipleGradientContext.<init>(MultipleGradientContext.java:137)
        at com.sun.prism.impl.paint.LinearGradientContext.<init>(LinearGradientContext.java:71)
        at com.sun.prism.impl.paint.PaintUtil.fillImageWithGradient(PaintUtil.java:81)
        at com.sun.prism.impl.BaseContext.getGradientTexture(BaseContext.java:526)
        at com.sun.prism.impl.ps.BaseShaderGraphics.renderWithComplexPaint(BaseShaderGraphics.java:433)
        at com.sun.prism.impl.ps.BaseShaderGraphics.fillRect(BaseShaderGraphics.java:1496)
        at com.sun.javafx.webkit.prism.WCGraphicsPrismContext$1.doPaint(WCGraphicsPrismContext.java:525)
        at com.sun.javafx.webkit.prism.WCGraphicsPrismContext$Composite.paint(WCGraphicsPrismContext.java:1500)
        at com.sun.javafx.webkit.prism.WCGraphicsPrismContext$Composite.paint(WCGraphicsPrismContext.java:1485)
        at com.sun.javafx.webkit.prism.WCGraphicsPrismContext.fillRect(WCGraphicsPrismContext.java:528)
        at com.sun.webkit.graphics.GraphicsDecoder.decode(GraphicsDecoder.java:99)
        at com.sun.webkit.graphics.WCRenderQueue.decode(WCRenderQueue.java:91)
        at com.sun.webkit.graphics.WCRenderQueue.decode(WCRenderQueue.java:102)
        at com.sun.webkit.graphics.WCImage.flushRQ(WCImage.java:52)
        at com.sun.javafx.webkit.prism.RTImage.lambda$getPixelBuffer$2(RTImage.java:163)
        at com.sun.javafx.webkit.prism.RTImage$$Lambda$244/1166350219.run(Unknown Source)
        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:514)
        at java.util.concurrent.FutureTask.run(FutureTask.java:266)
        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:514)
        at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:307)
        at com.sun.javafx.tk.RenderJob.run(RenderJob.java:58)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1158)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:632)
        at com.sun.javafx.tk.quantum.QuantumRenderer$PipelineRunnable.run(QuantumRenderer.java:125)
        ... 1 more

Changeset: 07c4fa3bf87f Author: arajkumar Date: 2016-05-13 01:30 +0530 URL: http://hg.openjdk.java.net/openjfx/9-dev/rt/rev/07c4fa3bf87f

Looks good. +1

Thank you Jim. >> It isn't clear to my why Float.compare is used here in the test? You are right. I used |Float.compare| to handle the NaN case. New webrev addresses your concern: http://cr.openjdk.java.net/~arajkumar/8155903/webrev.04

It isn't clear to my why Float.compare is used here in the test? If you are concerned about NaN values, the easiest way to weed them out is to make sure that the comparisons are always done in such a way that the failure redirects to a case that isn't affected by the NaN. In this case, the comparison was "do we need to use the slow general method" and NaN would cause that to fail and go to the optimized method, which would be bad. If you reverse the test to "is the number compatible with using the fast method" then NaN will cause that to fail and go to the slow method, as you would want. So, simply reversing the test (and swapping the if/else clauses) would handle NaN values just fine. So: if (estimatedSize > MAX) { // too big and not NaN slow method that doesn't care about the NaN } else { // NaN comes here which is bad... fast method that can't deal with NaN } could be turned into this: if (estimatedSize <= MAX) { // appropriately sized and also not NaN fast method that can't deal with NaN } else { // NaN comes here, which is OK slow method that isn't affected by NaN } Using Float.compare() also happens to work the way you've used it, but it isn't as clear because the method arbitrarily decides to make NaN a really big number, which isn't intuitive and so the NaN behavior of your technique should be documented so that some well meaning engineer doesn't try to optimize it by removing the method call and losing the NaN protection. Personally I tend to design my floating point comparisons so that NaN falls into the case that can handle that condition and then add a comment showing where we end up if a value is NaN so we know it is intentional, something like: // we end up here if <foo> is NaN // (possibly a comment as to why NaN isn't an issue here if it isn't obvious)

Incorporated Jim's review comments: http://cr.openjdk.java.net/~arajkumar/8155903/webrev.03

Thanks Kevin. I will take a look.

Regarding the test, this might cause problems in headless mode (e.g., when running tests on an automated build machine). Tests that require rendering should be done in the :systemTests project (tests/system directory). We have other examples that use snapshot there.

Hello @Jim ([~flar]), Please review the fix http://cr.openjdk.java.net/~arajkumar/8155903/webrev.01 Now I have added the regression test case using javafx Canvas node(instead of html5 canvas), but the test case has been added under "web" instead of "graphics" because I couldn't do force paint on javafx Canvas node using Node::snapshot(..) in test.javafx.scene.canvas.CanvasTest. Thanks.

[~arajkumar] You should ask Jim Graham to review this fix.

Issue is related divide by zero in floating point division. Due to this, it returns Infinity and Infinity is typecasted to integer, which intern returns Integer.MAX_VALUE. int nGradients = (int)((normalizedIntervals[i]/Imin)*255f); In the problematic case, above expression evaluated as (int)((1.0f / 0.0f.)*255f). I'm proposing a below solution, diff -r c1e46a31f2c0 modules/graphics/src/main/java/com/sun/prism/impl/paint/MultipleGradientContext.java --- a/modules/graphics/src/main/java/com/sun/prism/impl/paint/MultipleGradientContext.java Wed Apr 27 16:32:42 2016 +0100 +++ b/modules/graphics/src/main/java/com/sun/prism/impl/paint/MultipleGradientContext.java Tue May 03 15:16:47 2016 +0100 @@ -254,11 +254,14 @@ //the eventual size of the single array int gradientsTot = 1; + // Is Imin zero? + final boolean canDivideByImin = Float.isFinite(1.0f/Imin); // for every interval (transition between 2 colors) for (int i = 0; i < gradients.length; i++) { // create an array whose size is based on the ratio to the // smallest interval - int nGradients = (int)((normalizedIntervals[i]/Imin)*255f); + int nGradients = canDivideByImin + ? (int)((normalizedIntervals[i]/Imin)*255f) : 0; gradientsTot += nGradients; gradients[i] = new int[nGradients];

It is working with prism software rendering. (-Dprism.order=sw). Looks like something wrong with the d3d backend.