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);
+    }
+  }
+}