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