Version 1.2.31
Workaround a Dalvik JIT bug exposed by the SVG code in the support library
CL: https://r8-review.googlesource.com/c/r8/+/23189
Avoid creating an unused out value for Dalvik workaround
CL: https://r8-review.googlesource.com/c/r8/+/23401
Bug: 77496850
Change-Id: Ia52488ff5ecc3910bbdbd1c5f306be3b0df22c49
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index a6a6702..35b3491 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
// This field is accessed from release scripts using simple pattern matching.
// Therefore, changing this field could break our release scripts.
- public static final String LABEL = "1.2.30";
+ public static final String LABEL = "1.2.31";
private Version() {
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 50fc1c9..65c59ae 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -759,6 +759,10 @@
// Analysis must be done after method is rewritten by logArgumentTypes()
codeRewriter.identifyClassInlinerEligibility(method, code, feedback);
+ if (options.canHaveNumberConversionRegisterAllocationBug()) {
+ codeRewriter.workaroundNumberConversionRegisterAllocationBug(code);
+ }
+
printMethod(code, "Optimized IR (SSA)");
finalizeIR(method, code, feedback);
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index ae57f48..bc083c9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -55,6 +55,7 @@
import com.android.tools.r8.ir.code.InvokeDirect;
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.InvokeNewArray;
+import com.android.tools.r8.ir.code.InvokeStatic;
import com.android.tools.r8.ir.code.InvokeVirtual;
import com.android.tools.r8.ir.code.MemberType;
import com.android.tools.r8.ir.code.NewArrayEmpty;
@@ -2782,4 +2783,59 @@
}
}
}
+
+ // See comment for InternalOptions.canHaveNumberConversionRegisterAllocationBug().
+ public void workaroundNumberConversionRegisterAllocationBug(IRCode code) {
+ final Supplier<DexMethod> javaLangDoubleisNaN = Suppliers.memoize(() ->
+ dexItemFactory.createMethod(
+ dexItemFactory.createString("Ljava/lang/Double;"),
+ dexItemFactory.createString("isNaN"),
+ dexItemFactory.booleanDescriptor,
+ new DexString[]{dexItemFactory.doubleDescriptor}));
+
+ ListIterator<BasicBlock> blocks = code.listIterator();
+ while (blocks.hasNext()) {
+ BasicBlock block = blocks.next();
+ InstructionListIterator it = block.listIterator();
+ while (it.hasNext()) {
+ Instruction instruction = it.next();
+ if (instruction.isArithmeticBinop() || instruction.isNeg()) {
+ for (Value value : instruction.inValues()) {
+ // Insert a call to Double.isNaN on each value which come from a number conversion
+ // to double and flows into an arithmetic instruction. This seems to break the traces
+ // in the Dalvik JIT and avoid the bug where the generated ARM code can clobber float
+ // values in a single-precision registers with double values written to
+ // double-precision registers. See b/77496850 for examples.
+ if (!value.isPhi()
+ && value.definition.isNumberConversion()
+ && value.definition.asNumberConversion().to == NumericType.DOUBLE) {
+ InvokeStatic invokeIsNaN =
+ new InvokeStatic(javaLangDoubleisNaN.get(), null, ImmutableList.of(value));
+ invokeIsNaN.setPosition(instruction.getPosition());
+
+ // Insert the invoke before the current instruction.
+ it.previous();
+ BasicBlock blockWithInvokeNaN =
+ block.hasCatchHandlers() ? it.split(code, blocks) : block;
+ if (blockWithInvokeNaN != block) {
+ // If we split, add the invoke at the end of the original block.
+ it = block.listIterator(block.getInstructions().size());
+ it.previous();
+ it.add(invokeIsNaN);
+ // Continue iteration in the split block.
+ block = blockWithInvokeNaN;
+ it = block.listIterator();
+ } else {
+ // Otherwise, add it to the current block.
+ it.add(invokeIsNaN);
+ }
+ // Skip over the instruction causing the invoke to be inserted.
+ Instruction temp = it.next();
+ assert temp == instruction;
+ }
+ }
+ }
+ }
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 5f0ec45..e2c8ff9 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -629,4 +629,12 @@
public boolean canHaveArtStringNewInitBug() {
return minApiLevel <= AndroidApiLevel.P.getLevel();
}
+
+ // Dalvik tracing JIT may perform invalid optimizations when int/float values are converted to
+ // double and used in arithmetic operations.
+ //
+ // See b/77496850.
+ public boolean canHaveNumberConversionRegisterAllocationBug() {
+ return minApiLevel <= AndroidApiLevel.K.getLevel();
+ }
}
diff --git a/src/test/java/com/android/tools/r8/regress/b77496850/B77496850.java b/src/test/java/com/android/tools/r8/regress/b77496850/B77496850.java
new file mode 100644
index 0000000..03614b9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b77496850/B77496850.java
@@ -0,0 +1,507 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.regress.b77496850;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.code.InvokeStatic;
+import com.android.tools.r8.dex.Marker.Tool;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import org.junit.Test;
+
+public class B77496850 extends TestBase {
+
+ static final String LOGTAG = "";
+
+ // Mock class for the code in PathParser below.
+ public static class Path {
+ void rLineTo(int x, int y) {
+ }
+ void cubicTo(float a, float b, float c, float d, float e, float f) {
+ }
+ }
+
+ // Mock class for the code in PathParser below.
+ public static class Log {
+ static void w(String x, String y) {
+ }
+ }
+
+ // Code copied from Android support library:
+ // https://android.googlesource.com/platform/frameworks/support/+/9791ac540f94c318f6602123d7000bfc55909b81/compat/src/main/java/android/support/v4/graphics/PathParser.java
+ public static class PathParser {
+
+ private static void drawArc(Path p,
+ float x0,
+ float y0,
+ float x1,
+ float y1,
+ float a,
+ float b,
+ float theta,
+ boolean isMoreThanHalf,
+ boolean isPositiveArc) {
+ /* Convert rotation angle from degrees to radians */
+ double thetaD = Math.toRadians(theta);
+ /* Pre-compute rotation matrix entries */
+ double cosTheta = Math.cos(thetaD);
+ double sinTheta = Math.sin(thetaD);
+ /* Transform (x0, y0) and (x1, y1) into unit space */
+ /* using (inverse) rotation, followed by (inverse) scale */
+ double x0p = (x0 * cosTheta + y0 * sinTheta) / a;
+ double y0p = (-x0 * sinTheta + y0 * cosTheta) / b;
+ double x1p = (x1 * cosTheta + y1 * sinTheta) / a;
+ double y1p = (-x1 * sinTheta + y1 * cosTheta) / b;
+ /* Compute differences and averages */
+ double dx = x0p - x1p;
+ double dy = y0p - y1p;
+ double xm = (x0p + x1p) / 2;
+ double ym = (y0p + y1p) / 2;
+ /* Solve for intersecting unit circles */
+ double dsq = dx * dx + dy * dy;
+ if (dsq == 0.0) {
+ Log.w(LOGTAG, " Points are coincident");
+ return; /* Points are coincident */
+ }
+ double disc = 1.0 / dsq - 1.0 / 4.0;
+ if (disc < 0.0) {
+ Log.w(LOGTAG, "Points are too far apart " + dsq);
+ float adjust = (float) (Math.sqrt(dsq) / 1.99999);
+ drawArc(p, x0, y0, x1, y1, a * adjust,
+ b * adjust, theta, isMoreThanHalf, isPositiveArc);
+ return; /* Points are too far apart */
+ }
+ double s = Math.sqrt(disc);
+ double sdx = s * dx;
+ double sdy = s * dy;
+ double cx;
+ double cy;
+ if (isMoreThanHalf == isPositiveArc) {
+ cx = xm - sdy;
+ cy = ym + sdx;
+ } else {
+ cx = xm + sdy;
+ cy = ym - sdx;
+ }
+ double eta0 = Math.atan2((y0p - cy), (x0p - cx));
+ double eta1 = Math.atan2((y1p - cy), (x1p - cx));
+ double sweep = (eta1 - eta0);
+ if (isPositiveArc != (sweep >= 0)) {
+ if (sweep > 0) {
+ sweep -= 2 * Math.PI;
+ } else {
+ sweep += 2 * Math.PI;
+ }
+ }
+ cx *= a;
+ cy *= b;
+ double tcx = cx;
+ cx = cx * cosTheta - cy * sinTheta;
+ cy = tcx * sinTheta + cy * cosTheta;
+ arcToBezier(p, cx, cy, a, b, x0, y0, thetaD, eta0, sweep);
+ }
+
+ /**
+ * Converts an arc to cubic Bezier segments and records them in p.
+ *
+ * @param p The target for the cubic Bezier segments
+ * @param cx The x coordinate center of the ellipse
+ * @param cy The y coordinate center of the ellipse
+ * @param a The radius of the ellipse in the horizontal direction
+ * @param b The radius of the ellipse in the vertical direction
+ * @param e1x E(eta1) x coordinate of the starting point of the arc
+ * @param e1y E(eta2) y coordinate of the starting point of the arc
+ * @param theta The angle that the ellipse bounding rectangle makes with horizontal plane
+ * @param start The start angle of the arc on the ellipse
+ * @param sweep The angle (positive or negative) of the sweep of the arc on the ellipse
+ */
+ private static void arcToBezier(Path p,
+ double cx,
+ double cy,
+ double a,
+ double b,
+ double e1x,
+ double e1y,
+ double theta,
+ double start,
+ double sweep) {
+ // Taken from equations at: http://spaceroots.org/documents/ellipse/node8.html
+ // and http://www.spaceroots.org/documents/ellipse/node22.html
+ // Maximum of 45 degrees per cubic Bezier segment
+ int numSegments = (int) Math.ceil(Math.abs(sweep * 4 / Math.PI));
+ double eta1 = start;
+ double cosTheta = Math.cos(theta);
+ double sinTheta = Math.sin(theta);
+ double cosEta1 = Math.cos(eta1);
+ double sinEta1 = Math.sin(eta1);
+ double ep1x = (-a * cosTheta * sinEta1) - (b * sinTheta * cosEta1);
+ double ep1y = (-a * sinTheta * sinEta1) + (b * cosTheta * cosEta1);
+ double anglePerSegment = sweep / numSegments;
+ for (int i = 0; i < numSegments; i++) {
+ double eta2 = eta1 + anglePerSegment;
+ double sinEta2 = Math.sin(eta2);
+ double cosEta2 = Math.cos(eta2);
+ double e2x = cx + (a * cosTheta * cosEta2) - (b * sinTheta * sinEta2);
+ double e2y = cy + (a * sinTheta * cosEta2) + (b * cosTheta * sinEta2);
+ double ep2x = -a * cosTheta * sinEta2 - b * sinTheta * cosEta2;
+ double ep2y = -a * sinTheta * sinEta2 + b * cosTheta * cosEta2;
+ double tanDiff2 = Math.tan((eta2 - eta1) / 2);
+ double alpha =
+ Math.sin(eta2 - eta1) * (Math.sqrt(4 + (3 * tanDiff2 * tanDiff2)) - 1) / 3;
+ double q1x = e1x + alpha * ep1x;
+ double q1y = e1y + alpha * ep1y;
+ double q2x = e2x - alpha * ep2x;
+ double q2y = e2y - alpha * ep2y;
+ // Adding this no-op call to workaround a proguard related issue.
+ p.rLineTo(0, 0);
+ p.cubicTo((float) q1x,
+ (float) q1y,
+ (float) q2x,
+ (float) q2y,
+ (float) e2x,
+ (float) e2y);
+ eta1 = eta2;
+ e1x = e2x;
+ e1y = e2y;
+ ep1x = ep2x;
+ ep1y = ep2y;
+ }
+ }
+ }
+
+ // Same code as PathParser above, but with exception handlers in the two methods.
+ public static class PathParserWithExceptionHandler {
+
+ private static void drawArc(Path p,
+ float x0,
+ float y0,
+ float x1,
+ float y1,
+ float a,
+ float b,
+ float theta,
+ boolean isMoreThanHalf,
+ boolean isPositiveArc) {
+ try {
+ /* Convert rotation angle from degrees to radians */
+ double thetaD = Math.toRadians(theta);
+ /* Pre-compute rotation matrix entries */
+ double cosTheta = Math.cos(thetaD);
+ double sinTheta = Math.sin(thetaD);
+ /* Transform (x0, y0) and (x1, y1) into unit space */
+ /* using (inverse) rotation, followed by (inverse) scale */
+ double x0p = (x0 * cosTheta + y0 * sinTheta) / a;
+ double y0p = (-x0 * sinTheta + y0 * cosTheta) / b;
+ double x1p = (x1 * cosTheta + y1 * sinTheta) / a;
+ double y1p = (-x1 * sinTheta + y1 * cosTheta) / b;
+ /* Compute differences and averages */
+ double dx = x0p - x1p;
+ double dy = y0p - y1p;
+ double xm = (x0p + x1p) / 2;
+ double ym = (y0p + y1p) / 2;
+ /* Solve for intersecting unit circles */
+ double dsq = dx * dx + dy * dy;
+ if (dsq == 0.0) {
+ Log.w(LOGTAG, " Points are coincident");
+ return; /* Points are coincident */
+ }
+ double disc = 1.0 / dsq - 1.0 / 4.0;
+ if (disc < 0.0) {
+ Log.w(LOGTAG, "Points are too far apart " + dsq);
+ float adjust = (float) (Math.sqrt(dsq) / 1.99999);
+ drawArc(p, x0, y0, x1, y1, a * adjust,
+ b * adjust, theta, isMoreThanHalf, isPositiveArc);
+ return; /* Points are too far apart */
+ }
+ double s = Math.sqrt(disc);
+ double sdx = s * dx;
+ double sdy = s * dy;
+ double cx;
+ double cy;
+ if (isMoreThanHalf == isPositiveArc) {
+ cx = xm - sdy;
+ cy = ym + sdx;
+ } else {
+ cx = xm + sdy;
+ cy = ym - sdx;
+ }
+ double eta0 = Math.atan2((y0p - cy), (x0p - cx));
+ double eta1 = Math.atan2((y1p - cy), (x1p - cx));
+ double sweep = (eta1 - eta0);
+ if (isPositiveArc != (sweep >= 0)) {
+ if (sweep > 0) {
+ sweep -= 2 * Math.PI;
+ } else {
+ sweep += 2 * Math.PI;
+ }
+ }
+ cx *= a;
+ cy *= b;
+ double tcx = cx;
+ cx = cx * cosTheta - cy * sinTheta;
+ cy = tcx * sinTheta + cy * cosTheta;
+ arcToBezier(p, cx, cy, a, b, x0, y0, thetaD, eta0, sweep);
+ } catch (Throwable t) {
+ // Ignore.
+ }
+ }
+
+ /**
+ * Converts an arc to cubic Bezier segments and records them in p.
+ *
+ * @param p The target for the cubic Bezier segments
+ * @param cx The x coordinate center of the ellipse
+ * @param cy The y coordinate center of the ellipse
+ * @param a The radius of the ellipse in the horizontal direction
+ * @param b The radius of the ellipse in the vertical direction
+ * @param e1x E(eta1) x coordinate of the starting point of the arc
+ * @param e1y E(eta2) y coordinate of the starting point of the arc
+ * @param theta The angle that the ellipse bounding rectangle makes with horizontal plane
+ * @param start The start angle of the arc on the ellipse
+ * @param sweep The angle (positive or negative) of the sweep of the arc on the ellipse
+ */
+ private static void arcToBezier(Path p,
+ double cx,
+ double cy,
+ double a,
+ double b,
+ double e1x,
+ double e1y,
+ double theta,
+ double start,
+ double sweep) {
+ try {
+ // Taken from equations at: http://spaceroots.org/documents/ellipse/node8.html
+ // and http://www.spaceroots.org/documents/ellipse/node22.html
+ // Maximum of 45 degrees per cubic Bezier segment
+ int numSegments = (int) Math.ceil(Math.abs(sweep * 4 / Math.PI));
+ double eta1 = start;
+ double cosTheta = Math.cos(theta);
+ double sinTheta = Math.sin(theta);
+ double cosEta1 = Math.cos(eta1);
+ double sinEta1 = Math.sin(eta1);
+ double ep1x = (-a * cosTheta * sinEta1) - (b * sinTheta * cosEta1);
+ double ep1y = (-a * sinTheta * sinEta1) + (b * cosTheta * cosEta1);
+ double anglePerSegment = sweep / numSegments;
+ for (int i = 0; i < numSegments; i++) {
+ double eta2 = eta1 + anglePerSegment;
+ double sinEta2 = Math.sin(eta2);
+ double cosEta2 = Math.cos(eta2);
+ double e2x = cx + (a * cosTheta * cosEta2) - (b * sinTheta * sinEta2);
+ double e2y = cy + (a * sinTheta * cosEta2) + (b * cosTheta * sinEta2);
+ double ep2x = -a * cosTheta * sinEta2 - b * sinTheta * cosEta2;
+ double ep2y = -a * sinTheta * sinEta2 + b * cosTheta * cosEta2;
+ double tanDiff2 = Math.tan((eta2 - eta1) / 2);
+ double alpha =
+ Math.sin(eta2 - eta1) * (Math.sqrt(4 + (3 * tanDiff2 * tanDiff2)) - 1) / 3;
+ double q1x = e1x + alpha * ep1x;
+ double q1y = e1y + alpha * ep1y;
+ double q2x = e2x - alpha * ep2x;
+ double q2y = e2y - alpha * ep2y;
+ // Adding this no-op call to workaround a proguard related issue.
+ p.rLineTo(0, 0);
+ p.cubicTo((float) q1x,
+ (float) q1y,
+ (float) q2x,
+ (float) q2y,
+ (float) e2x,
+ (float) e2y);
+ eta1 = eta2;
+ e1x = e2x;
+ e1y = e2y;
+ ep1x = ep2x;
+ ep1y = ep2y;
+ }
+ } catch (Throwable t) {
+ // Ignore.
+ }
+ }
+
+ }
+
+ // Reproduction from b/77496850.
+ public static class Reproduction {
+ public int test() {
+ int count = 0;
+ for (int i = 0; i < 1000; i++){
+ count += arcToBezier(1.0, 1.0, 2.0);
+ }
+ return count;
+ }
+
+ private static int arcToBezier(double a, double b, double sweep) {
+ int count = 0;
+
+ int numSegments = (int) sweep;
+
+ double cosTheta = 0.5;
+ double sinTheta = 0.5;
+ double cosEta1 = 0.5;
+ double sinEta1 = 0.5;
+ double ep1x = (-a * cosTheta * sinEta1) - (b * sinTheta * cosEta1);
+ double anglePerSegment = sweep / numSegments;
+
+ for (int i = 0; i < numSegments; i++) {
+ count++;
+ }
+ if (numSegments != count) {
+ return 1;
+ }
+ return 0;
+ }
+
+ public static void main(String[] args) {
+ for (int i = 0; i < 100; i++) {
+ System.out.println(new Reproduction().test());
+ }
+ }
+ }
+
+ // Reproduction from b/77496850 with exception handler.
+ public static class ReproductionWithExceptionHandler {
+ public int test() {
+ int count = 0;
+ for (int i = 0; i < 1000; i++){
+ count += arcToBezier(1.0, 1.0, 2.0);
+ }
+ return count;
+ }
+
+ private static int arcToBezier(double a, double b, double sweep) {
+ try {
+ int count = 0;
+
+ int numSegments = (int) sweep;
+
+ double cosTheta = 0.5;
+ double sinTheta = 0.5;
+ double cosEta1 = 0.5;
+ double sinEta1 = 0.5;
+ double ep1x = (-a * cosTheta * sinEta1) - (b * sinTheta * cosEta1);
+ double anglePerSegment = sweep / numSegments;
+
+ for (int i = 0; i < numSegments; i++) {
+ count++;
+ }
+ if (numSegments != count) {
+ return 1;
+ }
+ return 0;
+ } catch (Throwable t) {
+ return 1;
+ }
+ }
+
+ public static void main(String[] args) {
+ for (int i = 0; i < 100; i++) {
+ System.out.println(new Reproduction().test());
+ }
+ }
+ }
+
+ private int countInvokeDoubleIsNan(DexItemFactory factory, DexCode code) {
+ int count = 0;
+ DexMethod doubleIsNaN = factory.createMethod(
+ factory.createString("Ljava/lang/Double;"),
+ factory.createString("isNaN"),
+ factory.booleanDescriptor,
+ new DexString[]{factory.doubleDescriptor});
+ for (int i = 0; i < code.instructions.length; i++) {
+ if (code.instructions[i] instanceof InvokeStatic) {
+ InvokeStatic invoke = (InvokeStatic) code.instructions[i];
+ if (invoke.getMethod() == doubleIsNaN) {
+ count++;
+ }
+ }
+ }
+ return count;
+ }
+
+ private void checkPathParserMethods(AndroidApp app, Class testClass, int a, int b)
+ throws Exception {
+ DexInspector inspector = new DexInspector(app);
+ DexItemFactory factory = inspector.getFactory();
+ ClassSubject clazz = inspector.clazz(testClass);
+ MethodSubject drawArc = clazz.method(
+ "void",
+ "drawArc",
+ ImmutableList.of(
+ getClass().getCanonicalName() + "$Path",
+ "float", "float", "float", "float", "float", "float", "float", "boolean", "boolean"));
+ MethodSubject arcToBezier = clazz.method(
+ "void",
+ "arcToBezier",
+ ImmutableList.of(
+ getClass().getCanonicalName() + "$Path",
+ "double", "double", "double", "double", "double", "double",
+ "double", "double", "double"));
+ assertEquals(a, countInvokeDoubleIsNan(factory, drawArc.getMethod().getCode().asDexCode()));
+ assertEquals(b, countInvokeDoubleIsNan(factory, arcToBezier.getMethod().getCode().asDexCode()));
+ }
+
+ private void runTestPathParser(
+ Tool compiler, Class testClass, AndroidApiLevel apiLevel, int a, int b)
+ throws Exception {
+ AndroidApp app = readClasses(Path.class, Log.class, testClass);
+ if (compiler == Tool.D8) {
+ app = compileWithD8(app, o -> o.minApiLevel = apiLevel.getLevel());
+ } else {
+ assert compiler == Tool.R8;
+ app = compileWithR8(app, "-keep class * { *; }", o -> o.minApiLevel = apiLevel.getLevel());
+ }
+ checkPathParserMethods(app, testClass, a, b);
+ }
+
+ @Test
+ public void testSupportLibraryPathParser() throws Exception{
+ for (Tool tool : Tool.values()) {
+ runTestPathParser(tool, PathParser.class, AndroidApiLevel.K, 14, 1);
+ runTestPathParser(tool, PathParser.class, AndroidApiLevel.L, 0, 0);
+ runTestPathParser(tool, PathParserWithExceptionHandler.class, AndroidApiLevel.K, 14, 1);
+ runTestPathParser(tool, PathParserWithExceptionHandler.class, AndroidApiLevel.L, 0, 0);
+ }
+ }
+
+ private void runTestReproduction(
+ Tool compiler, Class testClass, AndroidApiLevel apiLevel, int expectedInvokeDoubleIsNanCount)
+ throws Exception {
+ AndroidApp app = readClasses(testClass);
+ if (compiler == Tool.D8) {
+ app = compileWithD8(app, o -> o.minApiLevel = apiLevel.getLevel());
+ } else {
+ assert compiler == Tool.R8;
+ app = compileWithR8(app, "-keep class * { *; }", o -> o.minApiLevel = apiLevel.getLevel());
+ }
+ DexInspector inspector = new DexInspector(app);
+ DexItemFactory factory = inspector.getFactory();
+ ClassSubject clazz = inspector.clazz(testClass);
+ MethodSubject arcToBezier = clazz.method(
+ "int", "arcToBezier", ImmutableList.of("double", "double", "double"));
+ assertEquals(
+ expectedInvokeDoubleIsNanCount,
+ countInvokeDoubleIsNan(factory, arcToBezier.getMethod().getCode().asDexCode()));
+ }
+
+ @Test
+ public void testReproduction() throws Exception{
+ for (Tool tool : Tool.values()) {
+ runTestReproduction(tool, Reproduction.class, AndroidApiLevel.K, tool == Tool.D8 ? 1 : 0);
+ runTestReproduction(tool, Reproduction.class, AndroidApiLevel.L, 0);
+ runTestReproduction(
+ tool, ReproductionWithExceptionHandler.class, AndroidApiLevel.K, tool == Tool.D8 ? 1 : 0);
+ runTestReproduction(tool, ReproductionWithExceptionHandler.class, AndroidApiLevel.L, 0);
+ }
+ }
+}