Merge "Disallow merge when invoke-super resolves to private method"
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index 3d30b23..72728a8 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -161,6 +161,27 @@
     blocks.addAll(newBlocks);
   }
 
+  public boolean verifySplitCriticalEdges() {
+    for (BasicBlock block : blocks) {
+      // If there are multiple incoming edges, check each has a split block.
+      List<BasicBlock> predecessors = block.getPredecessors();
+      if (predecessors.size() > 1) {
+        for (BasicBlock predecessor : predecessors) {
+          assert predecessor.hasOneNormalExit();
+          assert predecessor.getSuccessors().get(0) == block;
+        }
+      }
+      // If there are outgoing exceptional edges, check that each has a split block.
+      if (block.hasCatchHandlers()) {
+        for (BasicBlock handler : block.getCatchHandlers().getUniqueTargets()) {
+          assert handler.getPredecessors().size() == 1;
+          assert handler.getPredecessors().get(0) == block;
+        }
+      }
+    }
+    return true;
+  }
+
   /**
    * Trace blocks and attempt to put fallthrough blocks immediately after the block that
    * falls through. When we fail to do that we create a new fallthrough block with an explicit
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index 6735863..963cf41 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -93,6 +93,8 @@
 import it.unimi.dsi.fastutil.ints.IntIterator;
 import it.unimi.dsi.fastutil.ints.IntList;
 import it.unimi.dsi.fastutil.ints.IntSet;
+import it.unimi.dsi.fastutil.objects.Reference2IntMap;
+import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
@@ -142,6 +144,13 @@
     }
   }
 
+  private static class SplitBlockWorklistItem extends WorklistItem {
+
+    public SplitBlockWorklistItem(BasicBlock block) {
+      super(block, -1);
+    }
+  }
+
   /**
    * Representation of lists of values that can be used as keys in maps. A list of
    * values is equal to another list of values if it contains exactly the same values
@@ -224,6 +233,10 @@
       return all;
     }
 
+    boolean hasJustOneNormalExit() {
+      return normalSuccessors.size() == 1 && exceptionalSuccessors.isEmpty();
+    }
+
     BlockInfo split(
         int blockStartOffset, int fallthroughOffset, Int2ReferenceMap<BlockInfo> targets) {
       BlockInfo fallthroughInfo = new BlockInfo();
@@ -279,6 +292,7 @@
 
   // Mapping from instruction offsets to basic-block targets.
   private final Int2ReferenceSortedMap<BlockInfo> targets = new Int2ReferenceAVLTreeMap<>();
+  private final Reference2IntMap<BasicBlock> offsets = new Reference2IntOpenHashMap<>();
 
   // Worklist of reachable blocks.
   private final Queue<Integer> traceBlocksWorklist = new LinkedList<>();
@@ -364,7 +378,9 @@
     source.setUp();
 
     // Create entry block (at a non-targetable address).
-    targets.put(INITIAL_BLOCK_OFFSET, new BlockInfo());
+    BlockInfo initialBlockInfo = new BlockInfo();
+    targets.put(INITIAL_BLOCK_OFFSET, initialBlockInfo);
+    offsets.put(initialBlockInfo.block, INITIAL_BLOCK_OFFSET);
 
     // Process reachable code paths starting from instruction 0.
     int instCount = source.instructionCount();
@@ -430,9 +446,8 @@
     // Package up the IR code.
     IRCode ir = new IRCode(options, method, blocks, valueNumberGenerator, hasDebugPositions);
 
-    // Split critical edges to make sure that we have a place to insert phi moves if
-    // necessary.
-    ir.splitCriticalEdges();
+    // Verify critical edges are split so we have a place to insert phi moves if necessary.
+    assert ir.verifySplitCriticalEdges();
 
     for (BasicBlock block : blocks) {
       block.deduplicatePhis();
@@ -546,6 +561,12 @@
       // Process synthesized move-exception block specially.
       if (item instanceof MoveExceptionWorklistItem) {
         processMoveExceptionItem((MoveExceptionWorklistItem) item);
+        closeCurrentBlockGuaranteedNotToNeedEdgeSplitting();
+        continue;
+      }
+      // Split blocks are just pending close.
+      if (item instanceof SplitBlockWorklistItem) {
+        closeCurrentBlockGuaranteedNotToNeedEdgeSplitting();
         continue;
       }
       // Build IR for each dex instruction in the block.
@@ -581,7 +602,6 @@
     BasicBlock targetBlock = getTarget(moveExceptionItem.targetOffset);
     currentBlock.link(targetBlock);
     addToWorklist(targetBlock, targetIndex);
-    closeCurrentBlock();
   }
 
   // Helper to resolve switch payloads and build switch instructions (dex code only).
@@ -1502,7 +1522,7 @@
   public void addThrow(int value) {
     Value in = readRegister(value, ValueType.OBJECT);
     addInstruction(new Throw(in));
-    closeCurrentBlock();
+    closeCurrentBlockGuaranteedNotToNeedEdgeSplitting();
   }
 
   public void addOr(NumericType type, int dest, int left, int right) {
@@ -1794,24 +1814,47 @@
     if (!throwingInstructionInCurrentBlock) {
       return;
     }
-    BasicBlock block = new BasicBlock();
+    // Note that when splitting the block in two we must update the CFG information so that we can
+    // correctly identify if the normal exits of the constructed block must be split once it is
+    // closed.
+    int currentBlockOffset = getOffset(currentBlock);
+    BlockInfo currentBlockInfo = getBlockInfo(currentBlockOffset);
+
+    BlockInfo info = new BlockInfo();
+    BasicBlock block = info.block;
     block.setNumber(nextBlockNumber++);
     blocks.add(block);
-    block.incrementUnfilledPredecessorCount();
+
+    // Compute some unused offset for the new block and link it in the CFG.
     int freshOffset = INITIAL_BLOCK_OFFSET - 1;
     while (targets.containsKey(freshOffset)) {
       freshOffset--;
     }
-    targets.put(freshOffset, null);
+    targets.put(freshOffset, info);
+    offsets.put(block, freshOffset);
+
+    // Copy over the exceptional successors.
     for (int offset : source.getCurrentCatchHandlers().getUniqueTargets()) {
+      info.addExceptionalSuccessor(offset);
       BlockInfo target = targets.get(offset);
       assert !target.block.isSealed();
       target.block.incrementUnfilledPredecessorCount();
       target.addExceptionalPredecessor(freshOffset);
     }
+
+    // Move all normal successors to the new block.
+    currentBlockInfo.normalSuccessors.forEach(info::addNormalSuccessor);
+    currentBlockInfo.normalSuccessors.clear();
+
+    // Link the two blocks.
     addInstruction(new Goto());
     currentBlock.link(block);
-    closeCurrentBlock();
+    block.incrementUnfilledPredecessorCount();
+    currentBlockInfo.addNormalSuccessor(freshOffset);
+    info.addNormalPredecessor(currentBlockOffset);
+
+    // The new block can only have a single predecessor and so we don't need to split between them.
+    closeCurrentBlockGuaranteedNotToNeedEdgeSplitting();
     setCurrentBlock(block);
   }
 
@@ -1886,6 +1929,7 @@
         info = new BlockInfo();
       }
       targets.put(offset, info);
+      offsets.put(info.block, offset);
     }
     return info;
   }
@@ -1959,11 +2003,23 @@
 
   // Private block helpers.
 
+  private BlockInfo getBlockInfo(int offset) {
+    return targets.get(offset);
+  }
+
+  private BlockInfo getBlockInfo(BasicBlock block) {
+    return getBlockInfo(getOffset(block));
+  }
+
   private BasicBlock getTarget(int offset) {
     return targets.get(offset).block;
   }
 
-  private void closeCurrentBlock() {
+  private int getOffset(BasicBlock block) {
+    return offsets.getInt(block);
+  }
+
+  private void closeCurrentBlockGuaranteedNotToNeedEdgeSplitting() {
     // TODO(zerny): To ensure liveness of locals throughout the entire block, we might want to
     // insert reads before closing the block. It is unclear if we can rely on a local-end to ensure
     // liveness in all blocks where the local should be live.
@@ -1973,6 +2029,33 @@
     throwingInstructionInCurrentBlock = false;
   }
 
+  private void closeCurrentBlock() {
+    assert currentBlock != null;
+    BlockInfo info = getBlockInfo(currentBlock);
+    if (!info.hasJustOneNormalExit()) {
+      // Exceptional edges are always split on construction, so no need to split edges to them.
+      for (int successorOffset : info.normalSuccessors) {
+        BlockInfo successorInfo = getBlockInfo(successorOffset);
+        if (successorInfo.predecessorCount() > 1) {
+          BasicBlock splitBlock = createSplitEdgeBlock(currentBlock, successorInfo.block);
+          ssaWorklist.add(new SplitBlockWorklistItem(splitBlock));
+        }
+      }
+    }
+    closeCurrentBlockGuaranteedNotToNeedEdgeSplitting();
+  }
+
+  private static BasicBlock createSplitEdgeBlock(BasicBlock source, BasicBlock target) {
+    BasicBlock splitBlock = new BasicBlock();
+    splitBlock.add(new Goto());
+    splitBlock.incrementUnfilledPredecessorCount();
+    splitBlock.getPredecessors().add(source);
+    splitBlock.getSuccessors().add(target);
+    source.replaceSuccessor(target, splitBlock);
+    target.replacePredecessor(source, splitBlock);
+    return splitBlock;
+  }
+
   private void closeCurrentBlockWithFallThrough(BasicBlock nextBlock) {
     assert currentBlock != null;
     addInstruction(new Goto());
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 dce8aa6..590b0c5 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
@@ -782,6 +782,10 @@
     }
     codeRewriter.identifyParameterUsages(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 22e3c32..5066f87 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
@@ -63,6 +63,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;
@@ -3008,4 +3009,61 @@
       }
     }
   }
+
+  // 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) {
+              Value newValue = code.createValue(
+                  instruction.outValue().outType(), instruction.getLocalInfo());
+              InvokeStatic invokeIsNaN =
+                  new InvokeStatic(javaLangDoubleisNaN.get(), newValue, 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 ec54641..57a9150 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -631,4 +631,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);
+    }
+  }
+}
diff --git a/third_party/gmail/gmail_android_170604.16.tar.gz.sha1 b/third_party/gmail/gmail_android_170604.16.tar.gz.sha1
index f57ba90..9d9985c 100644
--- a/third_party/gmail/gmail_android_170604.16.tar.gz.sha1
+++ b/third_party/gmail/gmail_android_170604.16.tar.gz.sha1
@@ -1 +1 @@
-161c569821a5c9b4cb8e99de764f3449191af084
\ No newline at end of file
+a6d49ef4fb2094672a6f6be039c971727cc9fd34
\ No newline at end of file
diff --git a/third_party/youtube/youtube.android_12.22.tar.gz.sha1 b/third_party/youtube/youtube.android_12.22.tar.gz.sha1
index 8f6813c..056ff59 100644
--- a/third_party/youtube/youtube.android_12.22.tar.gz.sha1
+++ b/third_party/youtube/youtube.android_12.22.tar.gz.sha1
@@ -1 +1 @@
-73c4880898d734064815d0426d8fe84ee6d075b4
\ No newline at end of file
+57b5c53a80ba010d1faef7da1b643f8c72b3e4e8
\ No newline at end of file