8144938: Handle properly coordinate overflow in Marlin Renderer
authorlbourges
Wed, 23 Mar 2016 21:20:25 +0100
changeset 36902 bb30d89aa00e
parent 36901 49e6f3f15290
child 36903 addc2e9d4bd4
8144938: Handle properly coordinate overflow in Marlin Renderer Summary: skip point coordinates with NaN/Infinity values Reviewed-by: flar, prr
jdk/src/java.desktop/share/classes/sun/java2d/marlin/MarlinRenderingEngine.java
jdk/src/java.desktop/share/classes/sun/java2d/marlin/Stroker.java
jdk/src/java.desktop/share/classes/sun/java2d/marlin/TransformingPathConsumer2D.java
jdk/src/java.desktop/share/classes/sun/java2d/marlin/Version.java
jdk/src/java.desktop/share/classes/sun/java2d/pipe/AAShapePipe.java
jdk/test/sun/java2d/marlin/CrashNaNTest.java
--- a/jdk/src/java.desktop/share/classes/sun/java2d/marlin/MarlinRenderingEngine.java	Wed Mar 23 17:25:27 2016 +0530
+++ b/jdk/src/java.desktop/share/classes/sun/java2d/marlin/MarlinRenderingEngine.java	Wed Mar 23 21:20:25 2016 +0100
@@ -51,6 +51,9 @@
 
     private static final float MIN_PEN_SIZE = 1f / NORM_SUBPIXELS;
 
+    static final float UPPER_BND = Float.MAX_VALUE / 2.0f;
+    static final float LOWER_BND = -UPPER_BND;
+
     /**
      * Public constructor
      */
@@ -279,7 +282,7 @@
                         float dashphase,
                         PathConsumer2D pc2d)
     {
-        // We use strokerat and outat so that in Stroker and Dasher we can work only
+        // We use strokerat so that in Stroker and Dasher we can work only
         // with the pre-transformation coordinates. This will repeat a lot of
         // computations done in the path iterator, but the alternative is to
         // work with transformed paths and compute untransformed coordinates
@@ -289,15 +292,11 @@
         // However, if a path's width is constant after a transformation,
         // we can skip all this untransforming.
 
-        // If normalization is off we save some transformations by not
-        // transforming the input to pisces. Instead, we apply the
-        // transformation after the path processing has been done.
-        // We can't do this if normalization is on, because it isn't a good
-        // idea to normalize before the transformation is applied.
+        // As pathTo() will check transformed coordinates for invalid values
+        // (NaN / Infinity) to ignore such points, it is necessary to apply the
+        // transformation before the path processing.
         AffineTransform strokerat = null;
-        AffineTransform outat = null;
 
-        PathIterator pi;
         int dashLen = -1;
         boolean recycleDashes = false;
 
@@ -333,6 +332,7 @@
             // leave a bit of room for error.
             if (nearZero(a*b + c*d) && nearZero(a*a + c*c - (b*b + d*d))) {
                 final float scale = (float) Math.sqrt(a*a + c*c);
+
                 if (dashes != null) {
                     recycleDashes = true;
                     dashLen = dashes.length;
@@ -349,48 +349,35 @@
                     System.arraycopy(dashes, 0, newDashes, 0, dashLen);
                     dashes = newDashes;
                     for (int i = 0; i < dashLen; i++) {
-                        dashes[i] = scale * dashes[i];
+                        dashes[i] *= scale;
                     }
-                    dashphase = scale * dashphase;
+                    dashphase *= scale;
                 }
-                width = scale * width;
-                pi = getNormalizingPathIterator(rdrCtx, normalize,
-                                                src.getPathIterator(at));
+                width *= scale;
 
-                // by now strokerat == null && outat == null. Input paths to
+                // by now strokerat == null. Input paths to
                 // stroker (and maybe dasher) will have the full transform at
                 // applied to them and nothing will happen to the output paths.
             } else {
-                if (normalize != NormMode.OFF) {
-                    strokerat = at;
-                    pi = getNormalizingPathIterator(rdrCtx, normalize,
-                                                    src.getPathIterator(at));
+                strokerat = at;
 
-                    // by now strokerat == at && outat == null. Input paths to
-                    // stroker (and maybe dasher) will have the full transform at
-                    // applied to them, then they will be normalized, and then
-                    // the inverse of *only the non translation part of at* will
-                    // be applied to the normalized paths. This won't cause problems
-                    // in stroker, because, suppose at = T*A, where T is just the
-                    // translation part of at, and A is the rest. T*A has already
-                    // been applied to Stroker/Dasher's input. Then Ainv will be
-                    // applied. Ainv*T*A is not equal to T, but it is a translation,
-                    // which means that none of stroker's assumptions about its
-                    // input will be violated. After all this, A will be applied
-                    // to stroker's output.
-                } else {
-                    outat = at;
-                    pi = src.getPathIterator(null);
-                    // outat == at && strokerat == null. This is because if no
-                    // normalization is done, we can just apply all our
-                    // transformations to stroker's output.
-                }
+                // by now strokerat == at. Input paths to
+                // stroker (and maybe dasher) will have the full transform at
+                // applied to them, then they will be normalized, and then
+                // the inverse of *only the non translation part of at* will
+                // be applied to the normalized paths. This won't cause problems
+                // in stroker, because, suppose at = T*A, where T is just the
+                // translation part of at, and A is the rest. T*A has already
+                // been applied to Stroker/Dasher's input. Then Ainv will be
+                // applied. Ainv*T*A is not equal to T, but it is a translation,
+                // which means that none of stroker's assumptions about its
+                // input will be violated. After all this, A will be applied
+                // to stroker's output.
             }
         } else {
             // either at is null or it's the identity. In either case
             // we don't transform the path.
-            pi = getNormalizingPathIterator(rdrCtx, normalize,
-                                            src.getPathIterator(null));
+            at = null;
         }
 
         if (useSimplifier) {
@@ -399,12 +386,7 @@
             pc2d = rdrCtx.simplifier.init(pc2d);
         }
 
-        // by now, at least one of outat and strokerat will be null. Unless at is not
-        // a constant multiple of an orthogonal transformation, they will both be
-        // null. In other cases, outat == at if normalization is off, and if
-        // normalization is on, strokerat == at.
         final TransformingPathConsumer2D transformerPC2D = rdrCtx.transformerPC2D;
-        pc2d = transformerPC2D.transformConsumer(pc2d, outat);
         pc2d = transformerPC2D.deltaTransformConsumer(pc2d, strokerat);
 
         pc2d = rdrCtx.stroker.init(pc2d, width, caps, join, miterlimit);
@@ -417,18 +399,22 @@
                                       recycleDashes);
         }
         pc2d = transformerPC2D.inverseDeltaTransformConsumer(pc2d, strokerat);
+
+        final PathIterator pi = getNormalizingPathIterator(rdrCtx, normalize,
+                                    src.getPathIterator(at));
+
         pathTo(rdrCtx, pi, pc2d);
 
         /*
          * Pipeline seems to be:
-         *    shape.getPathIterator
-         * -> NormalizingPathIterator
-         * -> inverseDeltaTransformConsumer
-         * -> Dasher
+         * shape.getPathIterator(at)
+         * -> (NormalizingPathIterator)
+         * -> (inverseDeltaTransformConsumer)
+         * -> (Dasher)
          * -> Stroker
-         * -> deltaTransformConsumer OR transformConsumer
+         * -> (deltaTransformConsumer)
          *
-         * -> CollinearSimplifier to remove redundant segments
+         * -> (CollinearSimplifier) to remove redundant segments
          *
          * -> pc2d = Renderer (bounding box)
          */
@@ -642,27 +628,109 @@
     private static void pathToLoop(final float[] coords, final PathIterator pi,
                                    final PathConsumer2D pc2d)
     {
+        // ported from DuctusRenderingEngine.feedConsumer() but simplified:
+        // - removed skip flag = !subpathStarted
+        // - removed pathClosed (ie subpathStarted not set to false)
+        boolean subpathStarted = false;
+
         for (; !pi.isDone(); pi.next()) {
             switch (pi.currentSegment(coords)) {
-                case PathIterator.SEG_MOVETO:
+            case PathIterator.SEG_MOVETO:
+                /* Checking SEG_MOVETO coordinates if they are out of the
+                 * [LOWER_BND, UPPER_BND] range. This check also handles NaN
+                 * and Infinity values. Skipping next path segment in case of
+                 * invalid data.
+                 */
+                if (coords[0] < UPPER_BND && coords[0] > LOWER_BND &&
+                    coords[1] < UPPER_BND && coords[1] > LOWER_BND)
+                {
                     pc2d.moveTo(coords[0], coords[1]);
-                    continue;
-                case PathIterator.SEG_LINETO:
-                    pc2d.lineTo(coords[0], coords[1]);
-                    continue;
-                case PathIterator.SEG_QUADTO:
-                    pc2d.quadTo(coords[0], coords[1],
-                                coords[2], coords[3]);
-                    continue;
-                case PathIterator.SEG_CUBICTO:
-                    pc2d.curveTo(coords[0], coords[1],
-                                 coords[2], coords[3],
-                                 coords[4], coords[5]);
-                    continue;
-                case PathIterator.SEG_CLOSE:
+                    subpathStarted = true;
+                }
+                break;
+            case PathIterator.SEG_LINETO:
+                /* Checking SEG_LINETO coordinates if they are out of the
+                 * [LOWER_BND, UPPER_BND] range. This check also handles NaN
+                 * and Infinity values. Ignoring current path segment in case
+                 * of invalid data. If segment is skipped its endpoint
+                 * (if valid) is used to begin new subpath.
+                 */
+                if (coords[0] < UPPER_BND && coords[0] > LOWER_BND &&
+                    coords[1] < UPPER_BND && coords[1] > LOWER_BND)
+                {
+                    if (subpathStarted) {
+                        pc2d.lineTo(coords[0], coords[1]);
+                    } else {
+                        pc2d.moveTo(coords[0], coords[1]);
+                        subpathStarted = true;
+                    }
+                }
+                break;
+            case PathIterator.SEG_QUADTO:
+                // Quadratic curves take two points
+                /* Checking SEG_QUADTO coordinates if they are out of the
+                 * [LOWER_BND, UPPER_BND] range. This check also handles NaN
+                 * and Infinity values. Ignoring current path segment in case
+                 * of invalid endpoints's data. Equivalent to the SEG_LINETO
+                 * if endpoint coordinates are valid but there are invalid data
+                 * among other coordinates
+                 */
+                if (coords[2] < UPPER_BND && coords[2] > LOWER_BND &&
+                    coords[3] < UPPER_BND && coords[3] > LOWER_BND)
+                {
+                    if (subpathStarted) {
+                        if (coords[0] < UPPER_BND && coords[0] > LOWER_BND &&
+                            coords[1] < UPPER_BND && coords[1] > LOWER_BND)
+                        {
+                            pc2d.quadTo(coords[0], coords[1],
+                                        coords[2], coords[3]);
+                        } else {
+                            pc2d.lineTo(coords[2], coords[3]);
+                        }
+                    } else {
+                        pc2d.moveTo(coords[2], coords[3]);
+                        subpathStarted = true;
+                    }
+                }
+                break;
+            case PathIterator.SEG_CUBICTO:
+                // Cubic curves take three points
+                /* Checking SEG_CUBICTO coordinates if they are out of the
+                 * [LOWER_BND, UPPER_BND] range. This check also handles NaN
+                 * and Infinity values. Ignoring current path segment in case
+                 * of invalid endpoints's data. Equivalent to the SEG_LINETO
+                 * if endpoint coordinates are valid but there are invalid data
+                 * among other coordinates
+                 */
+                if (coords[4] < UPPER_BND && coords[4] > LOWER_BND &&
+                    coords[5] < UPPER_BND && coords[5] > LOWER_BND)
+                {
+                    if (subpathStarted) {
+                        if (coords[0] < UPPER_BND && coords[0] > LOWER_BND &&
+                            coords[1] < UPPER_BND && coords[1] > LOWER_BND &&
+                            coords[2] < UPPER_BND && coords[2] > LOWER_BND &&
+                            coords[3] < UPPER_BND && coords[3] > LOWER_BND)
+                        {
+                            pc2d.curveTo(coords[0], coords[1],
+                                         coords[2], coords[3],
+                                         coords[4], coords[5]);
+                        } else {
+                            pc2d.lineTo(coords[4], coords[5]);
+                        }
+                    } else {
+                        pc2d.moveTo(coords[4], coords[5]);
+                        subpathStarted = true;
+                    }
+                }
+                break;
+            case PathIterator.SEG_CLOSE:
+                if (subpathStarted) {
                     pc2d.closePath();
-                    continue;
-                default:
+                    // do not set subpathStarted to false
+                    // in case of missing moveTo() after close()
+                }
+                break;
+            default:
             }
         }
         pc2d.pathDone();
--- a/jdk/src/java.desktop/share/classes/sun/java2d/marlin/Stroker.java	Wed Mar 23 17:25:27 2016 +0530
+++ b/jdk/src/java.desktop/share/classes/sun/java2d/marlin/Stroker.java	Wed Mar 23 21:20:25 2016 +0100
@@ -338,11 +338,6 @@
     }
 
     private void drawRoundCap(float cx, float cy, float mx, float my) {
-        // the first and second arguments of the following two calls
-        // are really will be ignored by emitCurveTo (because of the false),
-        // but we put them in anyway, as opposed to just giving it 4 zeroes,
-        // because it's just 4 additions and it's not good to rely on this
-        // sort of assumption (right now it's true, but that may change).
         emitCurveTo(cx+mx-C*my, cy+my+C*mx,
                     cx-my+C*mx, cy+mx+C*my,
                     cx-my,      cy+mx);
--- a/jdk/src/java.desktop/share/classes/sun/java2d/marlin/TransformingPathConsumer2D.java	Wed Mar 23 17:25:27 2016 +0530
+++ b/jdk/src/java.desktop/share/classes/sun/java2d/marlin/TransformingPathConsumer2D.java	Wed Mar 23 21:20:25 2016 +0100
@@ -35,7 +35,7 @@
         // used by RendererContext
     }
 
-    // recycled PathConsumer2D instance from transformConsumer()
+    // recycled PathConsumer2D instance from wrapPath2d()
     private final Path2DWrapper        wp_Path2DWrapper        = new Path2DWrapper();
 
     PathConsumer2D wrapPath2d(Path2D.Float p2d)
@@ -43,46 +43,6 @@
         return wp_Path2DWrapper.init(p2d);
     }
 
-    // recycled PathConsumer2D instances from transformConsumer()
-    private final TranslateFilter      tx_TranslateFilter      = new TranslateFilter();
-    private final DeltaScaleFilter     tx_DeltaScaleFilter     = new DeltaScaleFilter();
-    private final ScaleFilter          tx_ScaleFilter          = new ScaleFilter();
-    private final DeltaTransformFilter tx_DeltaTransformFilter = new DeltaTransformFilter();
-    private final TransformFilter      tx_TransformFilter      = new TransformFilter();
-
-    PathConsumer2D transformConsumer(PathConsumer2D out,
-                                     AffineTransform at)
-    {
-        if (at == null) {
-            return out;
-        }
-        float mxx = (float) at.getScaleX();
-        float mxy = (float) at.getShearX();
-        float mxt = (float) at.getTranslateX();
-        float myx = (float) at.getShearY();
-        float myy = (float) at.getScaleY();
-        float myt = (float) at.getTranslateY();
-        if (mxy == 0f && myx == 0f) {
-            if (mxx == 1f && myy == 1f) {
-                if (mxt == 0f && myt == 0f) {
-                    return out;
-                } else {
-                    return tx_TranslateFilter.init(out, mxt, myt);
-                }
-            } else {
-                if (mxt == 0f && myt == 0f) {
-                    return tx_DeltaScaleFilter.init(out, mxx, myy);
-                } else {
-                    return tx_ScaleFilter.init(out, mxx, myy, mxt, myt);
-                }
-            }
-        } else if (mxt == 0f && myt == 0f) {
-            return tx_DeltaTransformFilter.init(out, mxx, mxy, myx, myy);
-        } else {
-            return tx_TransformFilter.init(out, mxx, mxy, mxt, myx, myy, myt);
-        }
-    }
-
     // recycled PathConsumer2D instances from deltaTransformConsumer()
     private final DeltaScaleFilter     dt_DeltaScaleFilter     = new DeltaScaleFilter();
     private final DeltaTransformFilter dt_DeltaTransformFilter = new DeltaTransformFilter();
@@ -97,6 +57,7 @@
         float mxy = (float) at.getShearX();
         float myx = (float) at.getShearY();
         float myy = (float) at.getScaleY();
+
         if (mxy == 0f && myx == 0f) {
             if (mxx == 1f && myy == 1f) {
                 return out;
@@ -122,6 +83,7 @@
         float mxy = (float) at.getShearX();
         float myx = (float) at.getShearY();
         float myy = (float) at.getScaleY();
+
         if (mxy == 0f && myx == 0f) {
             if (mxx == 1f && myy == 1f) {
                 return out;
@@ -138,197 +100,6 @@
         }
     }
 
-    static final class TranslateFilter implements PathConsumer2D {
-        private PathConsumer2D out;
-        private float tx, ty;
-
-        TranslateFilter() {}
-
-        TranslateFilter init(PathConsumer2D out,
-                             float tx, float ty)
-        {
-            this.out = out;
-            this.tx = tx;
-            this.ty = ty;
-            return this; // fluent API
-        }
-
-        @Override
-        public void moveTo(float x0, float y0) {
-            out.moveTo(x0 + tx, y0 + ty);
-        }
-
-        @Override
-        public void lineTo(float x1, float y1) {
-            out.lineTo(x1 + tx, y1 + ty);
-        }
-
-        @Override
-        public void quadTo(float x1, float y1,
-                           float x2, float y2)
-        {
-            out.quadTo(x1 + tx, y1 + ty,
-                       x2 + tx, y2 + ty);
-        }
-
-        @Override
-        public void curveTo(float x1, float y1,
-                            float x2, float y2,
-                            float x3, float y3)
-        {
-            out.curveTo(x1 + tx, y1 + ty,
-                        x2 + tx, y2 + ty,
-                        x3 + tx, y3 + ty);
-        }
-
-        @Override
-        public void closePath() {
-            out.closePath();
-        }
-
-        @Override
-        public void pathDone() {
-            out.pathDone();
-        }
-
-        @Override
-        public long getNativeConsumer() {
-            return 0;
-        }
-    }
-
-    static final class ScaleFilter implements PathConsumer2D {
-        private PathConsumer2D out;
-        private float sx, sy, tx, ty;
-
-        ScaleFilter() {}
-
-        ScaleFilter init(PathConsumer2D out,
-                         float sx, float sy,
-                         float tx, float ty)
-        {
-            this.out = out;
-            this.sx = sx;
-            this.sy = sy;
-            this.tx = tx;
-            this.ty = ty;
-            return this; // fluent API
-        }
-
-        @Override
-        public void moveTo(float x0, float y0) {
-            out.moveTo(x0 * sx + tx, y0 * sy + ty);
-        }
-
-        @Override
-        public void lineTo(float x1, float y1) {
-            out.lineTo(x1 * sx + tx, y1 * sy + ty);
-        }
-
-        @Override
-        public void quadTo(float x1, float y1,
-                           float x2, float y2)
-        {
-            out.quadTo(x1 * sx + tx, y1 * sy + ty,
-                       x2 * sx + tx, y2 * sy + ty);
-        }
-
-        @Override
-        public void curveTo(float x1, float y1,
-                            float x2, float y2,
-                            float x3, float y3)
-        {
-            out.curveTo(x1 * sx + tx, y1 * sy + ty,
-                        x2 * sx + tx, y2 * sy + ty,
-                        x3 * sx + tx, y3 * sy + ty);
-        }
-
-        @Override
-        public void closePath() {
-            out.closePath();
-        }
-
-        @Override
-        public void pathDone() {
-            out.pathDone();
-        }
-
-        @Override
-        public long getNativeConsumer() {
-            return 0;
-        }
-    }
-
-    static final class TransformFilter implements PathConsumer2D {
-        private PathConsumer2D out;
-        private float mxx, mxy, mxt, myx, myy, myt;
-
-        TransformFilter() {}
-
-        TransformFilter init(PathConsumer2D out,
-                             float mxx, float mxy, float mxt,
-                             float myx, float myy, float myt)
-        {
-            this.out = out;
-            this.mxx = mxx;
-            this.mxy = mxy;
-            this.mxt = mxt;
-            this.myx = myx;
-            this.myy = myy;
-            this.myt = myt;
-            return this; // fluent API
-        }
-
-        @Override
-        public void moveTo(float x0, float y0) {
-            out.moveTo(x0 * mxx + y0 * mxy + mxt,
-                       x0 * myx + y0 * myy + myt);
-        }
-
-        @Override
-        public void lineTo(float x1, float y1) {
-            out.lineTo(x1 * mxx + y1 * mxy + mxt,
-                       x1 * myx + y1 * myy + myt);
-        }
-
-        @Override
-        public void quadTo(float x1, float y1,
-                           float x2, float y2)
-        {
-            out.quadTo(x1 * mxx + y1 * mxy + mxt,
-                       x1 * myx + y1 * myy + myt,
-                       x2 * mxx + y2 * mxy + mxt,
-                       x2 * myx + y2 * myy + myt);
-        }
-
-        @Override
-        public void curveTo(float x1, float y1,
-                            float x2, float y2,
-                            float x3, float y3)
-        {
-            out.curveTo(x1 * mxx + y1 * mxy + mxt,
-                        x1 * myx + y1 * myy + myt,
-                        x2 * mxx + y2 * mxy + mxt,
-                        x2 * myx + y2 * myy + myt,
-                        x3 * mxx + y3 * mxy + mxt,
-                        x3 * myx + y3 * myy + myt);
-        }
-
-        @Override
-        public void closePath() {
-            out.closePath();
-        }
-
-        @Override
-        public void pathDone() {
-            out.pathDone();
-        }
-
-        @Override
-        public long getNativeConsumer() {
-            return 0;
-        }
-    }
 
     static final class DeltaScaleFilter implements PathConsumer2D {
         private PathConsumer2D out;
--- a/jdk/src/java.desktop/share/classes/sun/java2d/marlin/Version.java	Wed Mar 23 17:25:27 2016 +0530
+++ b/jdk/src/java.desktop/share/classes/sun/java2d/marlin/Version.java	Wed Mar 23 21:20:25 2016 +0100
@@ -27,7 +27,7 @@
 
 public final class Version {
 
-    private static final String version = "marlin-0.7.3.2-Unsafe-OpenJDK";
+    private static final String version = "marlin-0.7.3.3-Unsafe-OpenJDK";
 
     public static String getVersion() {
         return version;
--- a/jdk/src/java.desktop/share/classes/sun/java2d/pipe/AAShapePipe.java	Wed Mar 23 17:25:27 2016 +0530
+++ b/jdk/src/java.desktop/share/classes/sun/java2d/pipe/AAShapePipe.java	Wed Mar 23 21:20:25 2016 +0100
@@ -28,7 +28,6 @@
 import java.awt.Rectangle;
 import java.awt.Shape;
 import java.awt.geom.Rectangle2D;
-import java.util.concurrent.ConcurrentLinkedQueue;
 import sun.awt.SunHints;
 import sun.java2d.ReentrantContext;
 import sun.java2d.ReentrantContextProvider;
--- a/jdk/test/sun/java2d/marlin/CrashNaNTest.java	Wed Mar 23 17:25:27 2016 +0530
+++ b/jdk/test/sun/java2d/marlin/CrashNaNTest.java	Wed Mar 23 21:20:25 2016 +0100
@@ -21,14 +21,22 @@
  * questions.
  */
 
+import java.awt.BasicStroke;
 import java.awt.Color;
 import java.awt.Graphics2D;
 import java.awt.RenderingHints;
+import java.awt.Shape;
+import java.awt.Stroke;
 import java.awt.geom.Path2D;
+import java.awt.geom.PathIterator;
 import java.awt.image.BufferedImage;
+import java.awt.image.Raster;
 import java.io.File;
 import java.io.IOException;
+import static java.lang.Double.NEGATIVE_INFINITY;
+import static java.lang.Double.POSITIVE_INFINITY;
 import static java.lang.Double.NaN;
+import java.util.Arrays;
 import java.util.Locale;
 import java.util.logging.Handler;
 import java.util.logging.LogRecord;
@@ -37,8 +45,9 @@
 
 /**
  * @test
- * @bug 8149338
- * @summary Verifies that Marlin supports NaN coordinates and no JVM crash happens !
+ * @bug 8149338 8144938
+ * @summary Verifies that Marlin supports NaN coordinates (no JVM crash)
+ * but also it skips properly point coordinates with NaN / Infinity values
  * @run main CrashNaNTest
  */
 public class CrashNaNTest {
@@ -77,23 +86,108 @@
         System.setProperty("sun.java2d.renderer.useLogger", "true");
         System.setProperty("sun.java2d.renderer.doChecks", "true");
 
+        testFillDefaultAt();
+        testDrawComplexAt();
+
+        testPathClosed();
+
+        testStrokedShapes();
+    }
+
+    private static void testFillDefaultAt() {
         final int width = 400;
         final int height = 400;
 
         final BufferedImage image = new BufferedImage(width, height,
-                BufferedImage.TYPE_INT_ARGB);
+                                            BufferedImage.TYPE_INT_ARGB);
 
         final Graphics2D g2d = (Graphics2D) image.getGraphics();
         try {
             g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
-                    RenderingHints.VALUE_ANTIALIAS_ON);
+                                 RenderingHints.VALUE_ANTIALIAS_ON);
 
             g2d.setBackground(Color.WHITE);
             g2d.clearRect(0, 0, width, height);
 
             final Path2D.Double path = new Path2D.Double();
-            path.moveTo(30, 30);
-            path.lineTo(100, 100);
+            path.moveTo(100, 100);
+
+            for (int i = 0; i < 20000; i++) {
+                path.lineTo(110 + 0.01 * i, 110);
+                path.lineTo(111 + 0.01 * i, 100);
+            }
+
+            path.lineTo(NaN, 200);
+            path.lineTo(200, 200);
+            path.lineTo(200, NaN);
+            path.lineTo(300, 300);
+            path.lineTo(NaN, NaN);
+            path.lineTo(100, 200);
+            path.closePath();
+
+            final Path2D.Double path2 = new Path2D.Double();
+            path2.moveTo(0, 0);
+            path2.lineTo(100, height);
+            path2.lineTo(0, 200);
+            path2.closePath();
+
+            g2d.setColor(Color.BLUE);
+            g2d.fill(path);
+            g2d.setColor(Color.GREEN);
+            g2d.fill(path2);
+
+            g2d.setColor(Color.BLACK);
+            g2d.draw(path);
+            g2d.draw(path2);
+
+            if (SAVE_IMAGE) {
+                try {
+                    final File file = new File("CrashNaNTest-fill.png");
+                    System.out.println("Writing file: "
+                                       + file.getAbsolutePath());
+                    ImageIO.write(image, "PNG", file);
+                } catch (IOException ex) {
+                    System.out.println("Writing file failure:");
+                    ex.printStackTrace();
+                }
+            }
+
+            // Check image on few pixels:
+            final Raster raster = image.getData();
+
+            checkPixel(raster, 200, 195, Color.BLUE.getRGB());
+            checkPixel(raster, 105, 195, Color.BLUE.getRGB());
+            checkPixel(raster, 286, 290, Color.BLUE.getRGB());
+
+            checkPixel(raster, 108, 105, Color.WHITE.getRGB());
+            checkPixel(raster, 205, 200, Color.WHITE.getRGB());
+
+            checkPixel(raster, 5, 200, Color.GREEN.getRGB());
+
+        } finally {
+            g2d.dispose();
+        }
+    }
+
+    private static void testDrawComplexAt() {
+        final int width = 400;
+        final int height = 400;
+
+        final BufferedImage image = new BufferedImage(width, height,
+                                            BufferedImage.TYPE_INT_ARGB);
+
+        final Graphics2D g2d = (Graphics2D) image.getGraphics();
+        try {
+            g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+                                 RenderingHints.VALUE_ANTIALIAS_ON);
+            g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
+                                 RenderingHints.VALUE_STROKE_PURE);
+
+            g2d.setBackground(Color.WHITE);
+            g2d.clearRect(0, 0, width, height);
+
+            final Path2D.Double path = new Path2D.Double();
+            path.moveTo(100, 100);
 
             for (int i = 0; i < 20000; i++) {
                 path.lineTo(110 + 0.01 * i, 110);
@@ -105,39 +199,224 @@
             path.lineTo(200, NaN);
             path.lineTo(300, 300);
             path.lineTo(NaN, NaN);
-            path.lineTo(100, 100);
+            path.lineTo(100, 200);
             path.closePath();
 
             final Path2D.Double path2 = new Path2D.Double();
-            path2.moveTo(0,0);
-            path2.lineTo(width,height);
-            path2.lineTo(10, 10);
+            path2.moveTo(0, 0);
+            path2.lineTo(100, height);
+            path2.lineTo(0, 200);
             path2.closePath();
 
-            for (int i = 0; i < 1; i++) {
-                final long start = System.nanoTime();
-                g2d.setColor(Color.BLUE);
-                g2d.fill(path);
+            // Define an non-uniform transform:
+            g2d.scale(0.5, 1.0);
+            g2d.rotate(Math.PI / 31);
 
-                g2d.fill(path2);
-
-                final long time = System.nanoTime() - start;
-                System.out.println("paint: duration= " + (1e-6 * time) + " ms.");
-            }
+            g2d.setColor(Color.BLACK);
+            g2d.draw(path);
+            g2d.draw(path2);
 
             if (SAVE_IMAGE) {
                 try {
-                    final File file = new File("CrashNaNTest.png");
+                    final File file = new File("CrashNaNTest-draw.png");
                     System.out.println("Writing file: "
-                            + file.getAbsolutePath());
+                                       + file.getAbsolutePath());
+                    ImageIO.write(image, "PNG", file);
+                } catch (IOException ex) {
+                    System.out.println("Writing file failure:");
+                    ex.printStackTrace();
+                }
+            }
+
+            // Check image on few pixels:
+            final Raster raster = image.getData();
+
+            checkPixelNotWhite(raster, 40, 210);
+            checkPixelNotWhite(raster, 44, 110);
+            checkPixelNotWhite(raster, 60, 120);
+            checkPixelNotWhite(raster, 89, 219);
+            checkPixelNotWhite(raster, 28, 399);
+            checkPixelNotWhite(raster, 134, 329);
+
+        } finally {
+            g2d.dispose();
+        }
+    }
+    private static void testPathClosed() {
+        final int width = 100;
+        final int height = 100;
+
+        final BufferedImage image = new BufferedImage(width, height,
+                                            BufferedImage.TYPE_INT_ARGB);
+
+        final Graphics2D g2d = (Graphics2D) image.getGraphics();
+        try {
+            g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+                                 RenderingHints.VALUE_ANTIALIAS_ON);
+
+            g2d.setBackground(Color.WHITE);
+            g2d.clearRect(0, 0, width, height);
+
+            final Path2D.Double path = new Path2D.Double();
+            path.moveTo(40, 40);
+            path.lineTo(0,   0);
+            path.lineTo(80, 0);
+            path.closePath();
+            path.lineTo(80, 80);
+            path.lineTo(0, 80);
+            path.closePath();
+
+            g2d.setColor(Color.BLUE);
+            g2d.fill(path);
+
+            g2d.setColor(Color.BLACK);
+            g2d.draw(path);
+
+            if (SAVE_IMAGE) {
+                try {
+                    final File file = new File("CrashNaNTest-path-closed.png");
+                    System.out.println("Writing file: "
+                                       + file.getAbsolutePath());
                     ImageIO.write(image, "PNG", file);
                 } catch (IOException ex) {
                     System.out.println("Writing file failure:");
                     ex.printStackTrace();
                 }
             }
+
+            // Check image on few pixels:
+            final Raster raster = image.getData();
+
+            checkPixel(raster, 10, 05, Color.BLUE.getRGB());
+            checkPixel(raster, 70, 05, Color.BLUE.getRGB());
+            checkPixel(raster, 40, 35, Color.BLUE.getRGB());
+
+            checkPixel(raster, 10, 75, Color.BLUE.getRGB());
+            checkPixel(raster, 70, 75, Color.BLUE.getRGB());
+            checkPixel(raster, 40, 45, Color.BLUE.getRGB());
+
         } finally {
             g2d.dispose();
         }
     }
+
+    private static void testStrokedShapes() {
+        final Stroke stroke = new BasicStroke();
+
+        final Path2D.Double path = new Path2D.Double();
+        Shape s;
+
+        // Check filtering NaN values:
+        path.reset();
+        path.moveTo(100, NaN);
+        path.lineTo(NaN, 100);
+        path.lineTo(NaN, NaN);
+
+        path.quadTo(NaN, 100, NaN, 100);
+        path.quadTo(100, NaN, 100, NaN);
+        path.quadTo(NaN, NaN, NaN, NaN);
+
+        path.curveTo(NaN, 100, NaN, 100, NaN, 100);
+        path.curveTo(100, NaN, 100, NaN, 100, NaN);
+        path.curveTo(NaN, NaN, NaN, NaN, NaN, NaN);
+        path.closePath();
+
+        s = stroke.createStrokedShape(path);
+        checkEmptyPath(s);
+
+        // Check filtering +Infinity values:
+        path.reset();
+        path.moveTo(100, POSITIVE_INFINITY);
+        path.lineTo(POSITIVE_INFINITY, 100);
+        path.lineTo(POSITIVE_INFINITY, POSITIVE_INFINITY);
+
+        path.quadTo(POSITIVE_INFINITY, 100,
+                    POSITIVE_INFINITY, 100);
+        path.quadTo(100, POSITIVE_INFINITY,
+                    100, POSITIVE_INFINITY);
+        path.quadTo(POSITIVE_INFINITY, POSITIVE_INFINITY,
+                    POSITIVE_INFINITY, POSITIVE_INFINITY);
+
+        path.curveTo(POSITIVE_INFINITY, 100,
+                     POSITIVE_INFINITY, 100,
+                     POSITIVE_INFINITY, 100);
+        path.curveTo(100, POSITIVE_INFINITY,
+                     100, POSITIVE_INFINITY,
+                     100, POSITIVE_INFINITY);
+        path.curveTo(POSITIVE_INFINITY, POSITIVE_INFINITY,
+                     POSITIVE_INFINITY, POSITIVE_INFINITY,
+                     POSITIVE_INFINITY, POSITIVE_INFINITY);
+        path.closePath();
+
+        s = stroke.createStrokedShape(path);
+        checkEmptyPath(s);
+
+        // Check filtering -Infinity values:
+        path.reset();
+        path.moveTo(100, NEGATIVE_INFINITY);
+        path.lineTo(NEGATIVE_INFINITY, 100);
+        path.lineTo(NEGATIVE_INFINITY, NEGATIVE_INFINITY);
+
+        path.quadTo(NEGATIVE_INFINITY, 100,
+                    NEGATIVE_INFINITY, 100);
+        path.quadTo(100, NEGATIVE_INFINITY,
+                    100, NEGATIVE_INFINITY);
+        path.quadTo(NEGATIVE_INFINITY, NEGATIVE_INFINITY,
+                    NEGATIVE_INFINITY, NEGATIVE_INFINITY);
+
+        path.curveTo(NEGATIVE_INFINITY, 100,
+                     NEGATIVE_INFINITY, 100,
+                     NEGATIVE_INFINITY, 100);
+        path.curveTo(100, NEGATIVE_INFINITY,
+                     100, NEGATIVE_INFINITY,
+                     100, NEGATIVE_INFINITY);
+        path.curveTo(NEGATIVE_INFINITY, NEGATIVE_INFINITY,
+                     NEGATIVE_INFINITY, NEGATIVE_INFINITY,
+                     NEGATIVE_INFINITY, NEGATIVE_INFINITY);
+        path.closePath();
+
+        s = stroke.createStrokedShape(path);
+        checkEmptyPath(s);
+    }
+
+    private static void checkEmptyPath(final Shape s) {
+        final float[] coords = new float[6];
+        final PathIterator it = s.getPathIterator(null);
+
+        int n = 0;
+        for (; !it.isDone(); it.next()) {
+            int type = it.currentSegment(coords);
+            System.out.println("unexpected segment type= " + type
+                 + " with coords: " + Arrays.toString(coords));
+            n++;
+        }
+        if (n != 0) {
+            System.out.println("path elements = " + n);
+            throw new IllegalStateException("Not empty path: "
+                          + n + " path elements !");
+        }
+    }
+
+    private static void checkPixel(final Raster raster,
+                                   final int x, final int y,
+                                   final int expected) {
+
+        final int[] rgb = (int[]) raster.getDataElements(x, y, null);
+
+        if (rgb[0] != expected) {
+            throw new IllegalStateException("bad pixel at (" + x + ", " + y
+                          + ") = " + rgb[0] + " expected: " + expected);
+        }
+    }
+
+    private static void checkPixelNotWhite(final Raster raster,
+                                           final int x, final int y) {
+
+        final int[] rgb = (int[]) raster.getDataElements(x, y, null);
+
+        if (rgb[0] == Color.WHITE.getRGB()) {
+            throw new IllegalStateException("bad pixel at (" + x + ", " + y
+                          + ") is white (empty)");
+        }
+    }
 }