7018932: Drawing very large coordinates with a dashed Stroke can cause Java to hang

Reviewed-by: serb, prr
This commit is contained in:
Laurent Bourgès 2021-01-12 20:51:54 +00:00
parent 5f7ccce0c0
commit e4df2098a8
7 changed files with 344 additions and 38 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2007, 2018, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2007, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -170,16 +170,20 @@ public final class DMarlinRenderingEngine extends RenderingEngine
* {@code antialias} boolean parameter is true.
* <p>
* The geometry of the widened path is forwarded to the indicated
* {@link DPathConsumer2D} object as it is calculated.
* {@link PathConsumer2D} object as it is calculated.
*
* @param src the source path to be widened
* @param bs the {@code BasicSroke} object specifying the
* @param at the transform to be applied to the shape and the
* stroke attributes
* @param bs the {@code BasicStroke} object specifying the
* decorations to be applied to the widened path
* @param thin true if the transformed stroke attributes are smaller
* than the minimum dropout pen width
* @param normalize indicates whether stroke normalization should
* be applied
* @param antialias indicates whether or not adjustments appropriate
* to antialiased rendering should be applied
* @param consumer the {@code DPathConsumer2D} instance to forward
* @param consumer the {@code PathConsumer2D} instance to forward
* the widened geometry to
* @since 1.7
*/
@ -192,13 +196,92 @@ public final class DMarlinRenderingEngine extends RenderingEngine
boolean antialias,
final PathConsumer2D consumer)
{
strokeTo(src, at, null, bs, thin, normalize, antialias, consumer);
}
/**
* Sends the geometry for a widened path as specified by the parameters
* to the specified consumer.
* <p>
* The specified {@code src} {@link Shape} is widened according
* to the parameters specified by the {@link BasicStroke} object.
* Adjustments are made to the path as appropriate for the
* {@link java.awt.RenderingHints#VALUE_STROKE_NORMALIZE} hint if the
* {@code normalize} boolean parameter is true.
* Adjustments are made to the path as appropriate for the
* {@link java.awt.RenderingHints#VALUE_ANTIALIAS_ON} hint if the
* {@code antialias} boolean parameter is true.
* <p>
* The geometry of the widened path is forwarded to the indicated
* {@link PathConsumer2D} object as it is calculated.
*
* @param src the source path to be widened
* @param at the transform to be applied to the shape and the
* stroke attributes
* @param clip the current clip in effect in device coordinates
* @param bs the {@code BasicStroke} object specifying the
* decorations to be applied to the widened path
* @param thin true if the transformed stroke attributes are smaller
* than the minimum dropout pen width
* @param normalize indicates whether stroke normalization should
* be applied
* @param antialias indicates whether or not adjustments appropriate
* to antialiased rendering should be applied
* @param consumer the {@code PathConsumer2D} instance to forward
* the widened geometry to
* @since 17
*/
/* @Override (only for 17+) */
public void strokeTo(Shape src,
AffineTransform at,
Region clip,
BasicStroke bs,
boolean thin,
boolean normalize,
boolean antialias,
final PathConsumer2D consumer)
{
// Test if at is identity:
final AffineTransform _at = (at != null && !at.isIdentity()) ? at
: null;
final NormMode norm = (normalize) ?
((antialias) ? NormMode.ON_WITH_AA : NormMode.ON_NO_AA)
: NormMode.OFF;
final DRendererContext rdrCtx = getRendererContext();
try {
strokeTo(rdrCtx, src, at, bs, thin, norm, antialias,
if ((clip != null) &&
(DO_CLIP || (DO_CLIP_RUNTIME_ENABLE && MarlinProperties.isDoClipAtRuntime()))) {
// Define the initial clip bounds:
final double[] clipRect = rdrCtx.clipRect;
// Adjust the clipping rectangle with the renderer offsets
final double rdrOffX = 0.25d; // LBO: is it correct for AA or non AA cases ?
final double rdrOffY = 0.25d; // see NearestPixelQuarter (depends on normalization ?)
// add a small rounding error:
final double margin = 1e-3d;
clipRect[0] = clip.getLoY()
- margin + rdrOffY;
clipRect[1] = clip.getLoY() + clip.getHeight()
+ margin + rdrOffY;
clipRect[2] = clip.getLoX()
- margin + rdrOffX;
clipRect[3] = clip.getLoX() + clip.getWidth()
+ margin + rdrOffX;
if (MarlinConst.DO_LOG_CLIP) {
MarlinUtils.logInfo("clipRect (clip): "
+ Arrays.toString(rdrCtx.clipRect));
}
// Enable clipping:
rdrCtx.doClip = true;
}
strokeTo(rdrCtx, src, _at, bs, thin, norm, antialias,
rdrCtx.p2dAdapter.init(consumer));
} finally {
// recycle the DRendererContext instance

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2007, 2018, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2007, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -173,8 +173,12 @@ public final class MarlinRenderingEngine extends RenderingEngine
* {@link PathConsumer2D} object as it is calculated.
*
* @param src the source path to be widened
* @param bs the {@code BasicSroke} object specifying the
* @param at the transform to be applied to the shape and the
* stroke attributes
* @param bs the {@code BasicStroke} object specifying the
* decorations to be applied to the widened path
* @param thin true if the transformed stroke attributes are smaller
* than the minimum dropout pen width
* @param normalize indicates whether stroke normalization should
* be applied
* @param antialias indicates whether or not adjustments appropriate
@ -192,13 +196,92 @@ public final class MarlinRenderingEngine extends RenderingEngine
boolean antialias,
final PathConsumer2D consumer)
{
strokeTo(src, at, null, bs, thin, normalize, antialias, consumer);
}
/**
* Sends the geometry for a widened path as specified by the parameters
* to the specified consumer.
* <p>
* The specified {@code src} {@link Shape} is widened according
* to the parameters specified by the {@link BasicStroke} object.
* Adjustments are made to the path as appropriate for the
* {@link java.awt.RenderingHints#VALUE_STROKE_NORMALIZE} hint if the
* {@code normalize} boolean parameter is true.
* Adjustments are made to the path as appropriate for the
* {@link java.awt.RenderingHints#VALUE_ANTIALIAS_ON} hint if the
* {@code antialias} boolean parameter is true.
* <p>
* The geometry of the widened path is forwarded to the indicated
* {@link PathConsumer2D} object as it is calculated.
*
* @param src the source path to be widened
* @param at the transform to be applied to the shape and the
* stroke attributes
* @param clip the current clip in effect in device coordinates
* @param bs the {@code BasicStroke} object specifying the
* decorations to be applied to the widened path
* @param thin true if the transformed stroke attributes are smaller
* than the minimum dropout pen width
* @param normalize indicates whether stroke normalization should
* be applied
* @param antialias indicates whether or not adjustments appropriate
* to antialiased rendering should be applied
* @param consumer the {@code PathConsumer2D} instance to forward
* the widened geometry to
* @since 17
*/
/* @Override (only for 17+) */
public void strokeTo(Shape src,
AffineTransform at,
Region clip,
BasicStroke bs,
boolean thin,
boolean normalize,
boolean antialias,
final PathConsumer2D consumer)
{
// Test if at is identity:
final AffineTransform _at = (at != null && !at.isIdentity()) ? at
: null;
final NormMode norm = (normalize) ?
((antialias) ? NormMode.ON_WITH_AA : NormMode.ON_NO_AA)
: NormMode.OFF;
final RendererContext rdrCtx = getRendererContext();
try {
strokeTo(rdrCtx, src, at, bs, thin, norm, antialias, consumer);
if ((clip != null) &&
(DO_CLIP || (DO_CLIP_RUNTIME_ENABLE && MarlinProperties.isDoClipAtRuntime()))) {
// Define the initial clip bounds:
final float[] clipRect = rdrCtx.clipRect;
// Adjust the clipping rectangle with the renderer offsets
final float rdrOffX = 0.25f; // LBO: is it correct for AA or non AA cases ?
final float rdrOffY = 0.25f; // see NearestPixelQuarter (depends on normalization ?)
// add a small rounding error:
final float margin = 1e-3f;
clipRect[0] = clip.getLoY()
- margin + rdrOffY;
clipRect[1] = clip.getLoY() + clip.getHeight()
+ margin + rdrOffY;
clipRect[2] = clip.getLoX()
- margin + rdrOffX;
clipRect[3] = clip.getLoX() + clip.getWidth()
+ margin + rdrOffX;
if (MarlinConst.DO_LOG_CLIP) {
MarlinUtils.logInfo("clipRect (clip): "
+ Arrays.toString(rdrCtx.clipRect));
}
// Enable clipping:
rdrCtx.doClip = true;
}
strokeTo(rdrCtx, src, _at, bs, thin, norm, antialias, consumer);
} finally {
// recycle the RendererContext instance
returnRendererContext(rdrCtx);

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2007, 2018, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2007, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -880,6 +880,7 @@ final class TransformingPathConsumer2D {
}
}
/* note: CurveClipSplitter uses double-precision for higher accuracy */
static final class CurveClipSplitter {
static final float LEN_TH = MarlinProperties.getSubdividerMinLength();
@ -898,22 +899,22 @@ final class TransformingPathConsumer2D {
final float[] clipRect;
// clip rectangle (ymin, ymax, xmin, xmax) including padding:
final float[] clipRectPad = new float[4];
final double[] clipRectPad = new double[4];
private boolean init_clipRectPad = false;
// This is where the curve to be processed is put. We give it
// enough room to store all curves.
final float[] middle = new float[MAX_N_CURVES * 8 + 2];
final double[] middle = new double[MAX_N_CURVES * 8 + 2];
// t values at subdivision points
private final float[] subdivTs = new float[MAX_N_CURVES];
private final double[] subdivTs = new double[MAX_N_CURVES];
// dirty curve
private final Curve curve;
private final DCurve curve;
CurveClipSplitter(final RendererContext rdrCtx) {
this.rdrCtx = rdrCtx;
this.clipRect = rdrCtx.clipRect;
this.curve = rdrCtx.curve;
this.curve = /* rdrCtx.curve */ new DCurve(); // double-precision curve
}
void init() {
@ -935,7 +936,7 @@ final class TransformingPathConsumer2D {
// adjust padded clip rectangle (ymin, ymax, xmin, xmax):
// add a rounding error (curve subdivision ~ 0.1px):
final float[] _clipRect = clipRect;
final float[] _clipRectPad = clipRectPad;
final double[] _clipRectPad = clipRectPad;
_clipRectPad[0] = _clipRect[0] - CLIP_RECT_PADDING;
_clipRectPad[1] = _clipRect[1] + CLIP_RECT_PADDING;
@ -961,7 +962,7 @@ final class TransformingPathConsumer2D {
return false;
}
final float[] mid = middle;
final double[] mid = middle;
mid[0] = x0; mid[1] = y0;
mid[2] = x1; mid[3] = y1;
@ -982,7 +983,7 @@ final class TransformingPathConsumer2D {
return false;
}
final float[] mid = middle;
final double[] mid = middle;
mid[0] = x0; mid[1] = y0;
mid[2] = x1; mid[3] = y1;
mid[4] = x2; mid[5] = y2;
@ -1005,7 +1006,7 @@ final class TransformingPathConsumer2D {
return false;
}
final float[] mid = middle;
final double[] mid = middle;
mid[0] = x0; mid[1] = y0;
mid[2] = x1; mid[3] = y1;
mid[4] = x2; mid[5] = y2;
@ -1017,15 +1018,15 @@ final class TransformingPathConsumer2D {
private boolean subdivideAtIntersections(final int type, final int outCodeOR,
final PathConsumer2D out)
{
final float[] mid = middle;
final float[] subTs = subdivTs;
final double[] mid = middle;
final double[] subTs = subdivTs;
if (init_clipRectPad) {
init_clipRectPad = false;
initPaddedClip();
}
final int nSplits = Helpers.findClipPoints(curve, mid, subTs, type,
final int nSplits = DHelpers.findClipPoints(curve, mid, subTs, type,
outCodeOR, clipRectPad);
if (TRACE) {
@ -1036,12 +1037,12 @@ final class TransformingPathConsumer2D {
// only curve support shortcut
return false;
}
float prevT = 0.0f;
double prevT = 0.0d;
for (int i = 0, off = 0; i < nSplits; i++, off += type) {
final float t = subTs[i];
final double t = subTs[i];
Helpers.subdivideAt((t - prevT) / (1.0f - prevT),
DHelpers.subdivideAt((t - prevT) / (1.0d - prevT),
mid, off, mid, off, type);
prevT = t;
}
@ -1055,19 +1056,19 @@ final class TransformingPathConsumer2D {
return true;
}
static void emitCurrent(final int type, final float[] pts,
static void emitCurrent(final int type, final double[] pts,
final int off, final PathConsumer2D out)
{
// if instead of switch (perf + most probable cases first)
if (type == 8) {
out.curveTo(pts[off + 2], pts[off + 3],
pts[off + 4], pts[off + 5],
pts[off + 6], pts[off + 7]);
out.curveTo((float)pts[off + 2], (float)pts[off + 3],
(float)pts[off + 4], (float)pts[off + 5],
(float)pts[off + 6], (float)pts[off + 7]);
} else if (type == 4) {
out.lineTo(pts[off + 2], pts[off + 3]);
out.lineTo((float)pts[off + 2], (float)pts[off + 3]);
} else {
out.quadTo(pts[off + 2], pts[off + 3],
pts[off + 4], pts[off + 5]);
out.quadTo((float)pts[off + 2], (float)pts[off + 3],
(float)pts[off + 4], (float)pts[off + 5]);
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1999, 2018, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1999, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -261,7 +261,8 @@ public class LoopPipe
ShapeSpanIterator sr = new ShapeSpanIterator(false);
try {
sr.setOutputArea(sg2d.getCompClip());
final Region clip = sg2d.getCompClip();
sr.setOutputArea(clip);
sr.setRule(PathIterator.WIND_NON_ZERO);
BasicStroke bs = (BasicStroke) sg2d.stroke;
@ -270,7 +271,7 @@ public class LoopPipe
(sg2d.strokeHint != SunHints.INTVAL_STROKE_PURE);
RenderEngine.strokeTo(s,
sg2d.transform, bs,
sg2d.transform, clip, bs,
thin, normalize, false, sr);
} catch (Throwable t) {
sr.dispose();

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2007, 2018, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2007, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -200,8 +200,12 @@ public abstract class RenderingEngine {
* {@link PathConsumer2D} object as it is calculated.
*
* @param src the source path to be widened
* @param bs the {@code BasicSroke} object specifying the
* @param at the transform to be applied to the shape and the
* stroke attributes
* @param bs the {@code BasicStroke} object specifying the
* decorations to be applied to the widened path
* @param thin true if the transformed stroke attributes are smaller
* than the minimum dropout pen width
* @param normalize indicates whether stroke normalization should
* be applied
* @param antialias indicates whether or not adjustments appropriate
@ -218,6 +222,53 @@ public abstract class RenderingEngine {
boolean antialias,
PathConsumer2D consumer);
/**
* Sends the geometry for a widened path as specified by the parameters
* to the specified consumer.
* <p>
* The specified {@code src} {@link Shape} is widened according
* to the parameters specified by the {@link BasicStroke} object.
* The clip region can be optionally given to let the renderer only
* send geometries overlapping the clip region.
* Adjustments are made to the path as appropriate for the
* {@link java.awt.RenderingHints#VALUE_STROKE_NORMALIZE} hint if the
* {@code normalize} boolean parameter is true.
* Adjustments are made to the path as appropriate for the
* {@link java.awt.RenderingHints#VALUE_ANTIALIAS_ON} hint if the
* {@code antialias} boolean parameter is true.
* <p>
* The geometry of the widened path is forwarded to the indicated
* {@link PathConsumer2D} object as it is calculated.
*
* @param src the source path to be widened
* @param at the transform to be applied to the shape and the
* stroke attributes
* @param clip the current clip in effect in device coordinates
* @param bs the {@code BasicStroke} object specifying the
* decorations to be applied to the widened path
* @param thin true if the transformed stroke attributes are smaller
* than the minimum dropout pen width
* @param normalize indicates whether stroke normalization should
* be applied
* @param antialias indicates whether or not adjustments appropriate
* to antialiased rendering should be applied
* @param consumer the {@code PathConsumer2D} instance to forward
* the widened geometry to
* @since 17
*/
public void strokeTo(Shape src,
AffineTransform at,
Region clip,
BasicStroke bs,
boolean thin,
boolean normalize,
boolean antialias,
final PathConsumer2D consumer)
{
// As default implementation, call the strokeTo() method without the clip region.
strokeTo(src, at, bs, thin, normalize, antialias, consumer);
}
/**
* Construct an antialiased tile generator for the given shape with
* the given rendering attributes and store the bounds of the tile
@ -428,6 +479,28 @@ public abstract class RenderingEngine {
target.strokeTo(src, at, bs, thin, normalize, antialias, consumer);
}
public void strokeTo(Shape src,
AffineTransform at,
Region clip,
BasicStroke bs,
boolean thin,
boolean normalize,
boolean antialias,
PathConsumer2D consumer)
{
System.out.println(name+".strokeTo("+
src.getClass().getName()+", "+
at+", "+
clip+", "+
bs+", "+
(thin ? "thin" : "wide")+", "+
(normalize ? "normalized" : "pure")+", "+
(antialias ? "AA" : "non-AA")+", "+
consumer.getClass().getName()+")");
target.strokeTo(src, at, clip, bs, thin, normalize, antialias, consumer);
}
public float getMinimumAAPenSize() {
System.out.println(name+".getMinimumAAPenSize()");
return target.getMinimumAAPenSize();

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1998, 2018, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1998, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -42,7 +42,6 @@ import sun.awt.SunHints;
* perform the actual rendering.
*/
public abstract class SpanShapeRenderer implements ShapeDrawPipe {
static final RenderingEngine RenderEngine = RenderingEngine.getInstance();
public static class Composite extends SpanShapeRenderer {
CompositePipe comppipe;

View File

@ -0,0 +1,66 @@
/*
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import java.awt.BasicStroke;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Stroke;
import java.awt.geom.Line2D;
import java.awt.image.BufferedImage;
import static java.awt.image.BufferedImage.TYPE_INT_ARGB_PRE;
/**
* @test
* @bug 7018932
* @summary fix LoopPipe.getStrokedSpans() performance (clipping enabled by Marlin renderer)
* @run main/othervm/timeout=10 -Dsun.java2d.renderer=sun.java2d.marlin.MarlinRenderingEngine StrokedLinePerf
* @run main/othervm/timeout=10 -Dsun.java2d.renderer=sun.java2d.marlin.DMarlinRenderingEngine StrokedLinePerf
*/
public class StrokedLinePerf {
public static void main(String[] unused) {
BufferedImage bi = new BufferedImage(400, 400, TYPE_INT_ARGB_PRE);
test(bi, true);
test(bi, false);
}
private static void test(BufferedImage bi, boolean useAA) {
final long start = System.nanoTime();
final Graphics2D g2d = bi.createGraphics();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
(useAA) ? RenderingHints.VALUE_ANTIALIAS_ON
: RenderingHints.VALUE_ANTIALIAS_OFF);
Stroke stroke = new BasicStroke(2.0f, 1, 0, 1.0f, new float[]{0.0f, 4.0f}, 0.0f);
g2d.setStroke(stroke);
//Large values to trigger crash / infinite loop.
g2d.draw(new Line2D.Double(4.0, 1.794369841E9, 567.0, -2.147483648E9));
System.out.println("StrokedLinePerf(" + useAA + "): Test duration= " + (1e-6 * (System.nanoTime() - start)) + " ms.");
g2d.dispose();
}
}