Merge "Apply graph lense to DexValueMethodType and DexValueType in LensCodeRewriter"
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
index 8ab2c1a..5bed061 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
@@ -6,7 +6,6 @@
import static com.android.tools.r8.ir.code.IRCode.INSTRUCTION_NUMBER_DELTA;
import com.android.tools.r8.errors.CompilationError;
-import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.graph.DebugLocalInfo.PrintLevel;
import com.android.tools.r8.graph.DexItemFactory;
@@ -468,7 +467,7 @@
return instruction;
}
}
- throw new Unreachable();
+ return null;
}
public void clearUserInfo() {
diff --git a/src/main/java/com/android/tools/r8/ir/code/CanonicalPositions.java b/src/main/java/com/android/tools/r8/ir/code/CanonicalPositions.java
new file mode 100644
index 0000000..8db07f6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/code/CanonicalPositions.java
@@ -0,0 +1,75 @@
+// 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.ir.code;
+
+import com.android.tools.r8.graph.DexMethod;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Maintains a set of canonical positions. Also supports appending a new caller at the end of the
+ * caller chain of a Position.
+ */
+public class CanonicalPositions {
+ private final Position callerPosition;
+ private final boolean preserveCaller;
+ private final Map<Position, Position> canonicalPositions;
+ private final Position preamblePosition;
+
+ /**
+ * For callerPosition and preserveCaller see canonicalizeCallerPosition. initialCapacity will be
+ * passed to the HashMap constructor.
+ */
+ public CanonicalPositions(
+ Position callerPosition, boolean preserveCaller, int initialCapacity, DexMethod method) {
+ canonicalPositions = new HashMap<>(initialCapacity);
+ this.preserveCaller = preserveCaller;
+ this.callerPosition = callerPosition;
+ if (callerPosition != null) {
+ canonicalPositions.put(callerPosition, callerPosition);
+ }
+ preamblePosition =
+ callerPosition == null
+ ? Position.synthetic(0, method, null)
+ : new Position(0, null, method, callerPosition);
+ canonicalPositions.put(preamblePosition, preamblePosition);
+ }
+
+ public Position getPreamblePosition() {
+ return preamblePosition;
+ }
+
+ /**
+ * Update the internal set if this is the first occurence of the position's value and return
+ * canonical instance of position.
+ */
+ public Position getCanonical(Position position) {
+ Position canonical = canonicalPositions.putIfAbsent(position, position);
+ return canonical != null ? canonical : position;
+ }
+
+ /**
+ * Append callerPosition (supplied in constructor) to the end of caller's caller chain and return
+ * the canonical instance. Always returns null if preserveCaller (also supplied in constructor) is
+ * false.
+ */
+ public Position canonicalizeCallerPosition(Position caller) {
+ if (!preserveCaller) {
+ return null;
+ }
+
+ if (caller == null) {
+ return callerPosition;
+ }
+ if (caller.callerPosition == null && callerPosition == null) {
+ return getCanonical(caller);
+ }
+ Position callerOfCaller = canonicalizeCallerPosition(caller.callerPosition);
+ return getCanonical(
+ caller.isNone()
+ ? Position.noneWithMethod(caller.method, callerOfCaller)
+ : new Position(caller.line, caller.file, caller.method, callerOfCaller));
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
index b59794b..e0c47da 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
@@ -40,6 +40,7 @@
import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.ir.code.CanonicalPositions;
import com.android.tools.r8.ir.code.CatchHandlers;
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.ValueType;
@@ -68,25 +69,20 @@
private Instruction currentDexInstruction = null;
private Position currentPosition = null;
- private Map<Position, Position> canonicalPositions = null;
+ private final CanonicalPositions canonicalPositions;
private final List<ValueType> argumentTypes;
private List<DexDebugEntry> debugEntries = null;
// In case of inlining the position of the invoke in the caller.
- private final Position callerPosition;
private final DexMethod method;
- private final boolean preserveCaller;
- private final Position preamblePosition;
public DexSourceCode(
DexCode code, DexEncodedMethod method, Position callerPosition, boolean preserveCaller) {
this.code = code;
this.proto = method.method.proto;
this.accessFlags = method.accessFlags;
- this.callerPosition = callerPosition;
this.method = method.method;
- this.preserveCaller = preserveCaller;
argumentTypes = computeArgumentTypes();
DexDebugInfo info = code.getDebugInfo();
@@ -94,18 +90,11 @@
debugEntries = info.computeEntries(method.method);
}
canonicalPositions =
- new HashMap<>(
- 1
- + (callerPosition == null ? 0 : 1)
- + (debugEntries == null ? 0 : debugEntries.size()));
- if (callerPosition != null) {
- canonicalPositions.put(callerPosition, callerPosition);
- }
- preamblePosition =
- callerPosition == null
- ? Position.synthetic(0, this.method, null)
- : new Position(0, null, this.method, callerPosition);
- canonicalPositions.put(preamblePosition, preamblePosition);
+ new CanonicalPositions(
+ callerPosition,
+ preserveCaller,
+ 1 + (callerPosition == null ? 0 : 1) + (debugEntries == null ? 0 : debugEntries.size()),
+ this.method);
}
@Override
@@ -148,7 +137,7 @@
@Override
public void buildPrelude(IRBuilder builder) {
- currentPosition = preamblePosition;
+ currentPosition = canonicalPositions.getPreamblePosition();
if (code.incomingRegisterSize == 0) {
return;
}
@@ -197,7 +186,9 @@
@Override
public Position getDebugPositionAtOffset(int offset) {
DexDebugEntry entry = getDebugEntryAtOffset(offset);
- return entry == null ? preamblePosition : getCanonicalPositionAppendCaller(entry);
+ return entry == null
+ ? canonicalPositions.getPreamblePosition()
+ : getCanonicalPositionAppendCaller(entry);
}
@Override
@@ -250,7 +241,7 @@
int offset = instructionOffset(instructionIndex);
DexDebugEntry entry = getDebugEntryAtOffset(offset);
if (entry == null) {
- currentPosition = preamblePosition;
+ currentPosition = canonicalPositions.getPreamblePosition();
} else {
currentPosition = getCanonicalPositionAppendCaller(entry);
if (entry.lineEntry && entry.address == offset) {
@@ -259,39 +250,17 @@
}
}
- private Position getCanonicalPosition(Position position) {
- Position canonical = canonicalPositions.putIfAbsent(position, position);
- return canonical != null ? canonical : position;
- }
-
- private Position canonicalizeCallerPosition(Position caller) {
- // We are not supposed to get here from getCanonicalPositionAppendCaller if !preserveCaller.
- assert preserveCaller;
-
- if (caller == null) {
- return callerPosition;
- }
- if (caller.callerPosition == null && callerPosition == null) {
- return getCanonicalPosition(caller);
- }
- Position callerOfCaller = canonicalizeCallerPosition(caller.callerPosition);
- return getCanonicalPosition(
- caller.isNone()
- ? Position.noneWithMethod(caller.method, callerOfCaller)
- : new Position(caller.line, caller.file, caller.method, callerOfCaller));
- }
-
private Position getCanonicalPositionAppendCaller(DexDebugEntry entry) {
// If this instruction has already been inlined then this.method must be the outermost caller.
assert entry.callerPosition == null
|| entry.callerPosition.getOutermostCaller().method == method;
- return getCanonicalPosition(
+ return canonicalPositions.getCanonical(
new Position(
entry.line,
entry.sourceFile,
entry.method,
- preserveCaller ? canonicalizeCallerPosition(entry.callerPosition) : null));
+ canonicalPositions.canonicalizeCallerPosition(entry.callerPosition)));
}
@Override
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 63843ee..d5f923e 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
@@ -46,6 +46,7 @@
import com.android.tools.r8.ir.optimize.NonNullTracker;
import com.android.tools.r8.ir.optimize.Outliner;
import com.android.tools.r8.ir.optimize.PeepholeOptimizer;
+import com.android.tools.r8.ir.optimize.RedundantFieldLoadElimination;
import com.android.tools.r8.ir.optimize.classinliner.ClassInliner;
import com.android.tools.r8.ir.optimize.lambda.LambdaMerger;
import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
@@ -104,6 +105,8 @@
private final Devirtualizer devirtualizer;
private final CovariantReturnTypeAnnotationTransformer covariantReturnTypeAnnotationTransformer;
+ private final boolean enableWholeProgramOptimizations;
+
private final OptimizationFeedback ignoreOptimizationFeedback = new OptimizationFeedbackIgnore();
private DexString highestSortingString;
@@ -134,6 +137,7 @@
options.processCovariantReturnTypeAnnotations
? new CovariantReturnTypeAnnotationTransformer(this, appInfo.dexItemFactory)
: null;
+ this.enableWholeProgramOptimizations = enableWholeProgramOptimizations;
if (enableWholeProgramOptimizations) {
assert appInfo.hasLiveness();
this.nonNullTracker = new NonNullTracker();
@@ -695,6 +699,7 @@
codeRewriter.rewriteSwitch(code);
codeRewriter.processMethodsNeverReturningNormally(code);
codeRewriter.simplifyIf(code, typeEnvironment);
+ new RedundantFieldLoadElimination(appInfo, code, enableWholeProgramOptimizations).run();
if (options.testing.invertConditionals) {
invertConditionalsForTesting(code);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
new file mode 100644
index 0000000..dee935e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
@@ -0,0 +1,222 @@
+// 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.ir.optimize;
+
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.DominatorTree;
+import com.android.tools.r8.ir.code.FieldInstruction;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.Phi;
+import com.android.tools.r8.ir.code.Value;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * Eliminate redundant field loads.
+ *
+ * <p>Simple algorithm that goes through all blocks in one pass in dominator order and propagates
+ * active field sets across control-flow edges where the target has only one predecessor.
+ */
+// TODO(ager): Evaluate speed/size for computing active field sets in a fixed-point computation.
+public class RedundantFieldLoadElimination {
+
+ private final AppInfo appInfo;
+ private final DexEncodedMethod method;
+ private final IRCode code;
+ private final boolean enableWholeProgramOptimizations;
+ private final DominatorTree dominatorTree;
+
+ // Maps keeping track of fields that have an already loaded value at basic block entry.
+ private final HashMap<BasicBlock, HashMap<FieldAndObject, Instruction>>
+ activeInstanceFieldsAtEntry = new HashMap<>();
+ private final HashMap<BasicBlock, HashMap<DexField, Instruction>> activeStaticFieldsAtEntry =
+ new HashMap<>();
+
+ // Maps keeping track of fields with already loaded values for the current block during
+ // elimination.
+ private HashMap<FieldAndObject, Instruction> activeInstanceFields;
+ private HashMap<DexField, Instruction> activeStaticFields;
+
+ public RedundantFieldLoadElimination(
+ AppInfo appInfo, IRCode code, boolean enableWholeProgramOptimizations) {
+ this.appInfo = appInfo;
+ this.method = code.method;
+ this.code = code;
+ this.enableWholeProgramOptimizations = enableWholeProgramOptimizations;
+ dominatorTree = new DominatorTree(code);
+ }
+
+ private static class FieldAndObject {
+ private final DexField field;
+ private final Value object;
+
+ private FieldAndObject(DexField field, Value receiver) {
+ this.field = field;
+ this.object = receiver;
+ }
+
+ @Override
+ public int hashCode() {
+ return field.hashCode() * 7 + object.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof FieldAndObject)) {
+ return false;
+ }
+ FieldAndObject o = (FieldAndObject) other;
+ return o.object == object && o.field == field;
+ }
+ }
+
+ private boolean couldBeVolatile(DexField field) {
+ if (!enableWholeProgramOptimizations && field.getHolder() != method.method.getHolder()) {
+ return true;
+ }
+ DexEncodedField definition = appInfo.definitionFor(field);
+ return definition == null || definition.accessFlags.isVolatile();
+ }
+
+ public void run() {
+ for (BasicBlock block : dominatorTree.getSortedBlocks()) {
+ activeInstanceFields =
+ activeInstanceFieldsAtEntry.containsKey(block)
+ ? activeInstanceFieldsAtEntry.get(block)
+ : new HashMap<>();
+ activeStaticFields =
+ activeStaticFieldsAtEntry.containsKey(block)
+ ? activeStaticFieldsAtEntry.get(block)
+ : new HashMap<>();
+ InstructionListIterator it = block.listIterator();
+ while (it.hasNext()) {
+ Instruction instruction = it.next();
+ if (instruction.isFieldInstruction()) {
+ DexField field = instruction.asFieldInstruction().getField();
+ if (instruction.isInstancePut() || instruction.isStaticPut()) {
+ killActiveFields(instruction.asFieldInstruction());
+ } else if (couldBeVolatile(field)) {
+ assert instruction.isInstanceGet() || instruction.isStaticGet();
+ killAllActiveFields();
+ } else {
+ assert instruction.isInstanceGet() || instruction.isStaticGet();
+ assert !couldBeVolatile(field);
+ if (instruction.isInstanceGet() && !instruction.outValue().hasLocalInfo()) {
+ Value object = instruction.asInstanceGet().object();
+ FieldAndObject fieldAndObject = new FieldAndObject(field, object);
+ if (activeInstanceFields.containsKey(fieldAndObject)) {
+ Instruction active = activeInstanceFields.get(fieldAndObject);
+ eliminateRedundantRead(it, instruction, active);
+ } else {
+ activeInstanceFields.put(fieldAndObject, instruction);
+ }
+ } else if (instruction.isStaticGet() && !instruction.outValue().hasLocalInfo()) {
+ if (activeStaticFields.containsKey(field)) {
+ Instruction active = activeStaticFields.get(field);
+ eliminateRedundantRead(it, instruction, active);
+ } else {
+ // A field get on a different class can cause <clinit> to run and change static
+ // field values.
+ killActiveFields(instruction.asFieldInstruction());
+ activeStaticFields.put(field, instruction);
+ }
+ }
+ }
+ }
+ if (instruction.isMonitor() || instruction.isInvokeMethod()) {
+ killAllActiveFields();
+ }
+ }
+ propagateActiveFieldsFrom(block);
+ }
+ assert code.isConsistentSSA();
+ }
+
+ private void propagateActiveFieldsFrom(BasicBlock block) {
+ for (BasicBlock successor : block.getSuccessors()) {
+ // Allow propagation across exceptional edges, just be careful not to propagate if the
+ // throwing instruction is a field instruction.
+ if (successor.getPredecessors().size() == 1) {
+ if (block.hasCatchSuccessor(successor)) {
+ Instruction exceptionalExit = block.exceptionalExit();
+ if (exceptionalExit != null && exceptionalExit.isFieldInstruction()) {
+ killActiveFieldsForExceptionalExit(exceptionalExit.asFieldInstruction());
+ }
+ }
+ assert !activeInstanceFieldsAtEntry.containsKey(successor);
+ activeInstanceFieldsAtEntry.put(successor, new HashMap<>(activeInstanceFields));
+ assert !activeStaticFieldsAtEntry.containsKey(successor);
+ activeStaticFieldsAtEntry.put(successor, new HashMap<>(activeStaticFields));
+ }
+ }
+ }
+
+ private void killAllActiveFields() {
+ activeInstanceFields.clear();
+ activeStaticFields.clear();
+ }
+
+ private void killActiveFields(FieldInstruction instruction) {
+ DexField field = instruction.getField();
+ if (instruction.isInstancePut()) {
+ // Remove all the field/object pairs that refer to this field to make sure
+ // that we are conservative.
+ List<FieldAndObject> keysToRemove = new ArrayList<>();
+ for (FieldAndObject key : activeInstanceFields.keySet()) {
+ if (key.field == field) {
+ keysToRemove.add(key);
+ }
+ }
+ keysToRemove.forEach((k) -> activeInstanceFields.remove(k));
+ } else if (instruction.isInstanceGet()) {
+ Value object = instruction.asInstanceGet().object();
+ FieldAndObject fieldAndObject = new FieldAndObject(field, object);
+ activeInstanceFields.remove(fieldAndObject);
+ } else if (instruction.isStaticPut()) {
+ if (field.clazz != code.method.method.holder) {
+ // Accessing a static field on a different object could cause <clinit> to run which
+ // could modify any static field on any other object.
+ activeStaticFields.clear();
+ } else {
+ activeStaticFields.remove(field);
+ }
+ } else if (instruction.isStaticGet()) {
+ if (field.clazz != code.method.method.holder) {
+ // Accessing a static field on a different object could cause <clinit> to run which
+ // could modify any static field on any other object.
+ activeStaticFields.clear();
+ }
+ }
+ }
+
+ // If a field get instruction throws an exception it did not have an effect on the
+ // value of the field. Therefore, when propagating across exceptional edges for a
+ // field get instruction we have to exclude that field from the set of known
+ // field values.
+ private void killActiveFieldsForExceptionalExit(FieldInstruction instruction) {
+ DexField field = instruction.getField();
+ if (instruction.isInstanceGet()) {
+ Value object = instruction.asInstanceGet().object();
+ FieldAndObject fieldAndObject = new FieldAndObject(field, object);
+ activeInstanceFields.remove(fieldAndObject);
+ } else if (instruction.isStaticGet()) {
+ activeStaticFields.remove(field);
+ }
+ }
+
+ private void eliminateRedundantRead(
+ InstructionListIterator it, Instruction redundant, Instruction active) {
+ redundant.outValue().replaceUsers(active.outValue());
+ it.removeOrReplaceByDebugLocalRead();
+ active.outValue().uniquePhiUsers().forEach(Phi::removeTrivialPhi);
+ }
+}
diff --git a/src/test/examples/uninitializedfinal/UninitializedFinalFieldLeak.java b/src/test/examples/uninitializedfinal/UninitializedFinalFieldLeak.java
new file mode 100644
index 0000000..781d2b4
--- /dev/null
+++ b/src/test/examples/uninitializedfinal/UninitializedFinalFieldLeak.java
@@ -0,0 +1,57 @@
+// 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 uninitializedfinal;
+
+// Test that leaks an instance before its final field has been initialized to a thread that
+// reads that field. This tests that redundant field load elimination does not eliminate
+// field reads (even of final fields) that cross a monitor operation.
+public class UninitializedFinalFieldLeak {
+
+ public static class PollingThread extends Thread {
+ public int result = 0;
+ UninitializedFinalFieldLeak f;
+
+ PollingThread(UninitializedFinalFieldLeak f) {
+ this.f = f;
+ }
+
+ // Read the field a number of times. Then lock on the object to await field initialization.
+ public void run() {
+ result += f.i;
+ result += f.i;
+ result += f.i;
+ f.threadReadsDone = true;
+ synchronized (f) {
+ result += f.i;
+ }
+ // The right result is 42. Reading the uninitialized 0 three times and then
+ // reading the initialized value. It is safe to remove the two redundant loads
+ // before the monitor operation.
+ System.out.println(result);
+ }
+ }
+
+ public final int i;
+ public volatile boolean threadReadsDone = false;
+
+ public UninitializedFinalFieldLeak() throws InterruptedException {
+ // Leak the object to a thread and start the thread with the lock on the object taken.
+ // Then allow the other thread to run and read the uninitialized field.
+ // Finally, initialize the field and release the lock.
+ PollingThread t = new PollingThread(this);
+ synchronized (this) {
+ t.start();
+ while (!threadReadsDone) {
+ Thread.yield();
+ }
+ i = 42;
+ }
+ t.join();
+ }
+
+ public static void main(String[] args) throws InterruptedException {
+ new UninitializedFinalFieldLeak();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
index 36f7594..221d427 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
@@ -45,9 +45,11 @@
"instancevariable.InstanceVariable",
"instanceofstring.InstanceofString",
"invoke.Invoke",
+ "invokeempty.InvokeEmpty",
"jumbostring.JumboString",
"loadconst.LoadConst",
"loop.UdpServer",
+ "nestedtrycatches.NestedTryCatches",
"newarray.NewArray",
"regalloc.RegAlloc",
"returns.Returns",
@@ -58,9 +60,7 @@
"throwing.Throwing",
"trivial.Trivial",
"trycatch.TryCatch",
- "nestedtrycatches.NestedTryCatches",
"trycatchmany.TryCatchMany",
- "invokeempty.InvokeEmpty",
"regress.Regress",
"regress2.Regress2",
"regress_37726195.Regress",
@@ -82,6 +82,7 @@
"enclosingmethod_proguarded.Main",
"interfaceinlining.Main",
"switchmaps.Switches",
+ "uninitializedfinal.UninitializedFinalFieldLeak",
};
List<String[]> fullTestList = new ArrayList<>(tests.length * 2);
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 7b4f199..8adddfd 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -166,6 +166,17 @@
}
/**
+ * Copy test classes to the specified directory.
+ */
+ protected void copyTestClasses(Path dest, Class... classes) throws IOException {
+ for (Class clazz : classes) {
+ Path path = dest.resolve(clazz.getCanonicalName().replace('.', '/') + ".class");
+ Files.createDirectories(path.getParent());
+ Files.copy(ToolHelper.getClassFileForTestClass(clazz), path);
+ }
+ }
+
+ /**
* Create a temporary JAR file containing the specified test classes.
*/
protected Path jarTestClasses(Class... classes) throws IOException {
@@ -518,7 +529,11 @@
}
protected ProcessResult runOnJavaRaw(String main, byte[]... classes) throws IOException {
- Path file = writeToZip(Arrays.asList(classes));
+ return runOnJavaRaw(main, Arrays.asList(classes));
+ }
+
+ protected ProcessResult runOnJavaRaw(String main, List<byte[]> classes) throws IOException {
+ Path file = writeToZip(classes);
return ToolHelper.runJavaNoVerify(file, main);
}
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 200a1e7..2667ce2 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -745,10 +745,6 @@
return parts;
}
- public static Path getPackageDirectoryForTestClass(Class clazz) {
- return getPackageDirectoryForTestPackage(clazz.getPackage());
- }
-
public static List<Path> getClassFilesForTestPackage(Package pkg) throws IOException {
Path dir = ToolHelper.getPackageDirectoryForTestPackage(pkg);
return Files.walk(dir)
@@ -762,6 +758,11 @@
Paths.get("", parts.toArray(new String[parts.size() - 1])));
}
+ public static Path getFileNameForTestClass(Class clazz) {
+ List<String> parts = getNamePartsForTestClass(clazz);
+ return Paths.get("", parts.toArray(new String[parts.size() - 1]));
+ }
+
public static String getJarEntryForTestClass(Class clazz) {
List<String> parts = getNamePartsForTestClass(clazz);
return String.join("/", parts);
diff --git a/src/test/java/com/android/tools/r8/debug/ArrayDimensionGreaterThanSevenTest.java b/src/test/java/com/android/tools/r8/debug/ArrayDimensionGreaterThanSevenTest.java
new file mode 100644
index 0000000..68277dd
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/ArrayDimensionGreaterThanSevenTest.java
@@ -0,0 +1,41 @@
+// 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.debug;
+
+public class ArrayDimensionGreaterThanSevenTest {
+
+ public static float foo(int x) {
+ try {
+ float[] fs1 = new float[] {42f};
+ float[][] fs2 = new float[][] {fs1};
+ float[][][] fs3 = new float[][][] {fs2};
+ float[][][][] fs4 = new float[][][][] {fs3};
+ float[][][][][] fs5 = new float[][][][][] {fs4};
+ float[][][][][][] fs6 = new float[][][][][][] {fs5};
+ float[][][][][][][] fs7 = new float[][][][][][][] {fs6};
+ float[][][][][][][][] fs8 = new float[][][][][][][][] {fs7};
+ while (x-- > 0) {
+ try {
+ fs8 = x == 0 ? fs8 : null;
+ fs7 = x == 1 ? fs8[1] : fs8[0];
+ fs6 = x == 2 ? fs7[1] : fs7[0];
+ fs5 = x == 3 ? fs6[1] : fs6[0];
+ fs4 = x == 4 ? fs5[1] : fs5[0];
+ fs3 = x == 5 ? fs4[1] : fs4[0];
+ fs2 = x == 6 ? fs3[1] : fs3[0];
+ fs1 = x == 7 ? fs2[1] : fs2[0];
+ } catch (NullPointerException e) {
+ System.out.println("null pointer");
+ }
+ }
+ } catch (RuntimeException e) {
+ return -1f;
+ }
+ return 42;
+ }
+
+ public static void main(String[] args) {
+ System.out.println(foo(args.length + 1));
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/ArrayDimensionGreaterThanSevenTestDump.java b/src/test/java/com/android/tools/r8/debug/ArrayDimensionGreaterThanSevenTestDump.java
new file mode 100644
index 0000000..5cd652d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/ArrayDimensionGreaterThanSevenTestDump.java
@@ -0,0 +1,590 @@
+package com.android.tools.r8.debug;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+public class ArrayDimensionGreaterThanSevenTestDump implements Opcodes {
+
+ public static byte[] dump() throws Exception {
+
+ ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(
+ V1_8,
+ ACC_PUBLIC + ACC_SUPER,
+ "com/android/tools/r8/debug/ArrayDimensionGreaterThanSevenTest",
+ null,
+ "java/lang/Object",
+ null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "foo", "(I)F", null, null);
+ mv.visitCode();
+ Label l0 = new Label();
+ Label l1 = new Label();
+ Label l2 = new Label();
+ mv.visitTryCatchBlock(l0, l1, l2, "java/lang/NullPointerException");
+ Label l3 = new Label();
+ Label l4 = new Label();
+ Label l5 = new Label();
+ mv.visitTryCatchBlock(l3, l4, l5, "java/lang/RuntimeException");
+ mv.visitLabel(l3);
+ mv.visitInsn(ICONST_1);
+ mv.visitIntInsn(NEWARRAY, T_FLOAT);
+ mv.visitInsn(DUP);
+ mv.visitInsn(ICONST_0);
+ mv.visitLdcInsn(new Float("42.0"));
+ mv.visitInsn(FASTORE);
+ mv.visitVarInsn(ASTORE, 1);
+ mv.visitInsn(ICONST_1);
+ mv.visitTypeInsn(ANEWARRAY, "[F");
+ mv.visitInsn(DUP);
+ mv.visitInsn(ICONST_0);
+ mv.visitVarInsn(ALOAD, 1);
+ mv.visitInsn(AASTORE);
+ mv.visitVarInsn(ASTORE, 2);
+ mv.visitInsn(ICONST_1);
+ mv.visitTypeInsn(ANEWARRAY, "[[F");
+ mv.visitInsn(DUP);
+ mv.visitInsn(ICONST_0);
+ mv.visitVarInsn(ALOAD, 2);
+ mv.visitInsn(AASTORE);
+ mv.visitVarInsn(ASTORE, 3);
+ mv.visitInsn(ICONST_1);
+ mv.visitTypeInsn(ANEWARRAY, "[[[F");
+ mv.visitInsn(DUP);
+ mv.visitInsn(ICONST_0);
+ mv.visitVarInsn(ALOAD, 3);
+ mv.visitInsn(AASTORE);
+ mv.visitVarInsn(ASTORE, 4);
+ mv.visitInsn(ICONST_1);
+ mv.visitTypeInsn(ANEWARRAY, "[[[[F");
+ mv.visitInsn(DUP);
+ mv.visitInsn(ICONST_0);
+ mv.visitVarInsn(ALOAD, 4);
+ mv.visitInsn(AASTORE);
+ mv.visitVarInsn(ASTORE, 5);
+ mv.visitInsn(ICONST_1);
+ mv.visitTypeInsn(ANEWARRAY, "[[[[[F");
+ mv.visitInsn(DUP);
+ mv.visitInsn(ICONST_0);
+ mv.visitVarInsn(ALOAD, 5);
+ mv.visitInsn(AASTORE);
+ mv.visitVarInsn(ASTORE, 6);
+ mv.visitInsn(ICONST_1);
+ mv.visitTypeInsn(ANEWARRAY, "[[[[[[F");
+ mv.visitInsn(DUP);
+ mv.visitInsn(ICONST_0);
+ mv.visitVarInsn(ALOAD, 6);
+ mv.visitInsn(AASTORE);
+ mv.visitVarInsn(ASTORE, 7);
+ mv.visitInsn(ICONST_1);
+ mv.visitTypeInsn(ANEWARRAY, "[[[[[[[F");
+ mv.visitInsn(DUP);
+ mv.visitInsn(ICONST_0);
+ mv.visitVarInsn(ALOAD, 7);
+ mv.visitInsn(AASTORE);
+ mv.visitVarInsn(ASTORE, 8);
+ Label l6 = new Label();
+ mv.visitLabel(l6);
+ // mv.visitFrame(Opcodes.F_FULL, 9, new Object[] {Opcodes.INTEGER, "[F", "[[F", "[[[F",
+ // "[[[[F", "[[[[[F", "[[[[[[F", "[[[[[[[F", "[[[[[[[[F"}, 0, new Object[] {});
+ mv.visitFrame(
+ Opcodes.F_NEW,
+ 9,
+ new Object[] {
+ Opcodes.INTEGER,
+ "[F",
+ "[[F",
+ "[[[F",
+ "[[[[F",
+ "[[[[[F",
+ "[[[[[[F",
+ "[[[[[[[F",
+ "[[[[[[[[F"
+ },
+ 0,
+ new Object[] {});
+ mv.visitVarInsn(ILOAD, 0);
+ mv.visitIincInsn(0, -1);
+ mv.visitJumpInsn(IFLE, l4);
+ mv.visitLabel(l0);
+ mv.visitVarInsn(ILOAD, 0);
+ Label l7 = new Label();
+ mv.visitJumpInsn(IFNE, l7);
+ mv.visitVarInsn(ALOAD, 8);
+ Label l8 = new Label();
+ mv.visitJumpInsn(GOTO, l8);
+ mv.visitLabel(l7);
+ // mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+ mv.visitFrame(
+ Opcodes.F_NEW,
+ 9,
+ new Object[] {
+ Opcodes.INTEGER,
+ "[F",
+ "[[F",
+ "[[[F",
+ "[[[[F",
+ "[[[[[F",
+ "[[[[[[F",
+ "[[[[[[[F",
+ "[[[[[[[[F"
+ },
+ 0,
+ new Object[] {});
+ mv.visitInsn(ACONST_NULL);
+ mv.visitTypeInsn(CHECKCAST, "[[[[[[[[F");
+ mv.visitLabel(l8);
+ // mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"[[[[[[[[F"});
+ mv.visitFrame(
+ Opcodes.F_NEW,
+ 9,
+ new Object[] {
+ Opcodes.INTEGER,
+ "[F",
+ "[[F",
+ "[[[F",
+ "[[[[F",
+ "[[[[[F",
+ "[[[[[[F",
+ "[[[[[[[F",
+ "[[[[[[[[F"
+ },
+ 1,
+ new Object[] {"[[[[[[[[F"});
+ mv.visitVarInsn(ASTORE, 8);
+ mv.visitVarInsn(ILOAD, 0);
+ mv.visitInsn(ICONST_1);
+ Label l9 = new Label();
+ mv.visitJumpInsn(IF_ICMPNE, l9);
+ mv.visitVarInsn(ALOAD, 8);
+ mv.visitInsn(ICONST_1);
+ mv.visitInsn(AALOAD);
+ Label l10 = new Label();
+ mv.visitJumpInsn(GOTO, l10);
+ mv.visitLabel(l9);
+ // mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+ mv.visitFrame(
+ Opcodes.F_NEW,
+ 9,
+ new Object[] {
+ Opcodes.INTEGER,
+ "[F",
+ "[[F",
+ "[[[F",
+ "[[[[F",
+ "[[[[[F",
+ "[[[[[[F",
+ "[[[[[[[F",
+ "[[[[[[[[F"
+ },
+ 0,
+ new Object[] {});
+ mv.visitVarInsn(ALOAD, 8);
+ mv.visitInsn(ICONST_0);
+ mv.visitInsn(AALOAD);
+ mv.visitLabel(l10);
+ // mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"[[[[[[[F"});
+ mv.visitFrame(
+ Opcodes.F_NEW,
+ 9,
+ new Object[] {
+ Opcodes.INTEGER,
+ "[F",
+ "[[F",
+ "[[[F",
+ "[[[[F",
+ "[[[[[F",
+ "[[[[[[F",
+ "[[[[[[[F",
+ "[[[[[[[[F"
+ },
+ 1,
+ new Object[] {"[[[[[[[F"});
+ mv.visitVarInsn(ASTORE, 7);
+ mv.visitVarInsn(ILOAD, 0);
+ mv.visitInsn(ICONST_2);
+ Label l11 = new Label();
+ mv.visitJumpInsn(IF_ICMPNE, l11);
+ mv.visitVarInsn(ALOAD, 7);
+ mv.visitInsn(ICONST_1);
+ mv.visitInsn(AALOAD);
+ Label l12 = new Label();
+ mv.visitJumpInsn(GOTO, l12);
+ mv.visitLabel(l11);
+ // mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+ mv.visitFrame(
+ Opcodes.F_NEW,
+ 9,
+ new Object[] {
+ Opcodes.INTEGER,
+ "[F",
+ "[[F",
+ "[[[F",
+ "[[[[F",
+ "[[[[[F",
+ "[[[[[[F",
+ "[[[[[[[F",
+ "[[[[[[[[F"
+ },
+ 0,
+ new Object[] {});
+ mv.visitVarInsn(ALOAD, 7);
+ mv.visitInsn(ICONST_0);
+ mv.visitInsn(AALOAD);
+ mv.visitLabel(l12);
+ // mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"[[[[[[F"});
+ mv.visitFrame(
+ Opcodes.F_NEW,
+ 9,
+ new Object[] {
+ Opcodes.INTEGER,
+ "[F",
+ "[[F",
+ "[[[F",
+ "[[[[F",
+ "[[[[[F",
+ "[[[[[[F",
+ "[[[[[[[F",
+ "[[[[[[[[F"
+ },
+ 1,
+ new Object[] {"[[[[[[F"});
+ mv.visitVarInsn(ASTORE, 6);
+ mv.visitVarInsn(ILOAD, 0);
+ mv.visitInsn(ICONST_3);
+ Label l13 = new Label();
+ mv.visitJumpInsn(IF_ICMPNE, l13);
+ mv.visitVarInsn(ALOAD, 6);
+ mv.visitInsn(ICONST_1);
+ mv.visitInsn(AALOAD);
+ Label l14 = new Label();
+ mv.visitJumpInsn(GOTO, l14);
+ mv.visitLabel(l13);
+ // mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+ mv.visitFrame(
+ Opcodes.F_NEW,
+ 9,
+ new Object[] {
+ Opcodes.INTEGER,
+ "[F",
+ "[[F",
+ "[[[F",
+ "[[[[F",
+ "[[[[[F",
+ "[[[[[[F",
+ "[[[[[[[F",
+ "[[[[[[[[F"
+ },
+ 0,
+ new Object[] {});
+ mv.visitVarInsn(ALOAD, 6);
+ mv.visitInsn(ICONST_0);
+ mv.visitInsn(AALOAD);
+ mv.visitLabel(l14);
+ // mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"[[[[[F"});
+ mv.visitFrame(
+ Opcodes.F_NEW,
+ 9,
+ new Object[] {
+ Opcodes.INTEGER,
+ "[F",
+ "[[F",
+ "[[[F",
+ "[[[[F",
+ "[[[[[F",
+ "[[[[[[F",
+ "[[[[[[[F",
+ "[[[[[[[[F"
+ },
+ 1,
+ new Object[] {"[[[[[F"});
+ mv.visitVarInsn(ASTORE, 5);
+ mv.visitVarInsn(ILOAD, 0);
+ mv.visitInsn(ICONST_4);
+ Label l15 = new Label();
+ mv.visitJumpInsn(IF_ICMPNE, l15);
+ mv.visitVarInsn(ALOAD, 5);
+ mv.visitInsn(ICONST_1);
+ mv.visitInsn(AALOAD);
+ Label l16 = new Label();
+ mv.visitJumpInsn(GOTO, l16);
+ mv.visitLabel(l15);
+ // mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+ mv.visitFrame(
+ Opcodes.F_NEW,
+ 9,
+ new Object[] {
+ Opcodes.INTEGER,
+ "[F",
+ "[[F",
+ "[[[F",
+ "[[[[F",
+ "[[[[[F",
+ "[[[[[[F",
+ "[[[[[[[F",
+ "[[[[[[[[F"
+ },
+ 0,
+ new Object[] {});
+ mv.visitVarInsn(ALOAD, 5);
+ mv.visitInsn(ICONST_0);
+ mv.visitInsn(AALOAD);
+ mv.visitLabel(l16);
+ // mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"[[[[F"});
+ mv.visitFrame(
+ Opcodes.F_NEW,
+ 9,
+ new Object[] {
+ Opcodes.INTEGER,
+ "[F",
+ "[[F",
+ "[[[F",
+ "[[[[F",
+ "[[[[[F",
+ "[[[[[[F",
+ "[[[[[[[F",
+ "[[[[[[[[F"
+ },
+ 1,
+ new Object[] {"[[[[F"});
+ mv.visitVarInsn(ASTORE, 4);
+ mv.visitVarInsn(ILOAD, 0);
+ mv.visitInsn(ICONST_5);
+ Label l17 = new Label();
+ mv.visitJumpInsn(IF_ICMPNE, l17);
+ mv.visitVarInsn(ALOAD, 4);
+ mv.visitInsn(ICONST_1);
+ mv.visitInsn(AALOAD);
+ Label l18 = new Label();
+ mv.visitJumpInsn(GOTO, l18);
+ mv.visitLabel(l17);
+ // mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+ mv.visitFrame(
+ Opcodes.F_NEW,
+ 9,
+ new Object[] {
+ Opcodes.INTEGER,
+ "[F",
+ "[[F",
+ "[[[F",
+ "[[[[F",
+ "[[[[[F",
+ "[[[[[[F",
+ "[[[[[[[F",
+ "[[[[[[[[F"
+ },
+ 0,
+ new Object[] {});
+ mv.visitVarInsn(ALOAD, 4);
+ mv.visitInsn(ICONST_0);
+ mv.visitInsn(AALOAD);
+ mv.visitLabel(l18);
+ // mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"[[[F"});
+ mv.visitFrame(
+ Opcodes.F_NEW,
+ 9,
+ new Object[] {
+ Opcodes.INTEGER,
+ "[F",
+ "[[F",
+ "[[[F",
+ "[[[[F",
+ "[[[[[F",
+ "[[[[[[F",
+ "[[[[[[[F",
+ "[[[[[[[[F"
+ },
+ 1,
+ new Object[] {"[[[F"});
+ mv.visitVarInsn(ASTORE, 3);
+ mv.visitVarInsn(ILOAD, 0);
+ mv.visitIntInsn(BIPUSH, 6);
+ Label l19 = new Label();
+ mv.visitJumpInsn(IF_ICMPNE, l19);
+ mv.visitVarInsn(ALOAD, 3);
+ mv.visitInsn(ICONST_1);
+ mv.visitInsn(AALOAD);
+ Label l20 = new Label();
+ mv.visitJumpInsn(GOTO, l20);
+ mv.visitLabel(l19);
+ // mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+ mv.visitFrame(
+ Opcodes.F_NEW,
+ 9,
+ new Object[] {
+ Opcodes.INTEGER,
+ "[F",
+ "[[F",
+ "[[[F",
+ "[[[[F",
+ "[[[[[F",
+ "[[[[[[F",
+ "[[[[[[[F",
+ "[[[[[[[[F"
+ },
+ 0,
+ new Object[] {});
+ mv.visitVarInsn(ALOAD, 3);
+ mv.visitInsn(ICONST_0);
+ mv.visitInsn(AALOAD);
+ mv.visitLabel(l20);
+ // mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"[[F"});
+ mv.visitFrame(
+ Opcodes.F_NEW,
+ 9,
+ new Object[] {
+ Opcodes.INTEGER,
+ "[F",
+ "[[F",
+ "[[[F",
+ "[[[[F",
+ "[[[[[F",
+ "[[[[[[F",
+ "[[[[[[[F",
+ "[[[[[[[[F"
+ },
+ 1,
+ new Object[] {"[[F"});
+ mv.visitVarInsn(ASTORE, 2);
+ mv.visitVarInsn(ILOAD, 0);
+ mv.visitIntInsn(BIPUSH, 7);
+ Label l21 = new Label();
+ mv.visitJumpInsn(IF_ICMPNE, l21);
+ mv.visitVarInsn(ALOAD, 2);
+ mv.visitInsn(ICONST_1);
+ mv.visitInsn(AALOAD);
+ Label l22 = new Label();
+ mv.visitJumpInsn(GOTO, l22);
+ mv.visitLabel(l21);
+ // mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+ mv.visitFrame(
+ Opcodes.F_NEW,
+ 9,
+ new Object[] {
+ Opcodes.INTEGER,
+ "[F",
+ "[[F",
+ "[[[F",
+ "[[[[F",
+ "[[[[[F",
+ "[[[[[[F",
+ "[[[[[[[F",
+ "[[[[[[[[F"
+ },
+ 0,
+ new Object[] {});
+ mv.visitVarInsn(ALOAD, 2);
+ mv.visitInsn(ICONST_0);
+ mv.visitInsn(AALOAD);
+ mv.visitLabel(l22);
+ // mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"[F"});
+ mv.visitFrame(
+ Opcodes.F_NEW,
+ 9,
+ new Object[] {
+ Opcodes.INTEGER,
+ "[F",
+ "[[F",
+ "[[[F",
+ "[[[[F",
+ "[[[[[F",
+ "[[[[[[F",
+ "[[[[[[[F",
+ "[[[[[[[[F"
+ },
+ 1,
+ new Object[] {"[F"});
+ mv.visitVarInsn(ASTORE, 1);
+ mv.visitLabel(l1);
+ mv.visitJumpInsn(GOTO, l6);
+ mv.visitLabel(l2);
+ // mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[]
+ // {"java/lang/NullPointerException"});
+ mv.visitFrame(
+ Opcodes.F_NEW,
+ 9,
+ new Object[] {
+ Opcodes.INTEGER,
+ "[F",
+ "[[F",
+ "[[[F",
+ "[[[[F",
+ "[[[[[F",
+ "[[[[[[F",
+ "[[[[[[[F",
+ "[[[[[[[[F"
+ },
+ 1,
+ new Object[] {"java/lang/NullPointerException"});
+ mv.visitVarInsn(ASTORE, 9);
+ mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ mv.visitLdcInsn("null pointer");
+ mv.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+ mv.visitJumpInsn(GOTO, l6);
+ mv.visitLabel(l4);
+ // mv.visitFrame(Opcodes.F_FULL, 1, new Object[] {Opcodes.INTEGER}, 0, new Object[] {});
+ mv.visitFrame(Opcodes.F_NEW, 1, new Object[] {Opcodes.INTEGER}, 0, new Object[] {});
+ Label l23 = new Label();
+ mv.visitJumpInsn(GOTO, l23);
+ mv.visitLabel(l5);
+ // mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"java/lang/RuntimeException"});
+ mv.visitFrame(
+ Opcodes.F_NEW,
+ 1,
+ new Object[] {Opcodes.INTEGER},
+ 1,
+ new Object[] {"java/lang/RuntimeException"});
+ mv.visitVarInsn(ASTORE, 1);
+ mv.visitLdcInsn(new Float("-1.0"));
+ mv.visitInsn(FRETURN);
+ mv.visitLabel(l23);
+ // mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+ mv.visitFrame(Opcodes.F_NEW, 1, new Object[] {Opcodes.INTEGER}, 0, new Object[] {});
+ mv.visitLdcInsn(new Float("42.0"));
+ mv.visitInsn(FRETURN);
+ mv.visitMaxs(4, 10);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+ mv.visitCode();
+ mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitInsn(ARRAYLENGTH);
+ mv.visitInsn(ICONST_1);
+ mv.visitInsn(IADD);
+ mv.visitMethodInsn(
+ INVOKESTATIC,
+ "com/android/tools/r8/debug/ArrayDimensionGreaterThanSevenTest",
+ "foo",
+ "(I)F",
+ false);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(F)V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(3, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/ArrayDimensionGreaterThanSevenTestRunner.java b/src/test/java/com/android/tools/r8/debug/ArrayDimensionGreaterThanSevenTestRunner.java
new file mode 100644
index 0000000..7866e1b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/ArrayDimensionGreaterThanSevenTestRunner.java
@@ -0,0 +1,82 @@
+// 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.debug;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.debug.DebugTestBase.JUnit3Wrapper.DebuggeeState;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.InternalOptions;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.function.Consumer;
+import java.util.stream.Stream;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class ArrayDimensionGreaterThanSevenTestRunner extends DebugTestBase {
+
+ private static final Class CLASS = ArrayDimensionGreaterThanSevenTest.class;
+ private static final String NAME = CLASS.getCanonicalName();
+
+ private DebugTestConfig getR8CfConfig(String s, Consumer<InternalOptions> optionsConsumer)
+ throws IOException, com.android.tools.r8.CompilationFailedException {
+ Path cfOut = temp.getRoot().toPath().resolve(s);
+ ToolHelper.runR8(
+ R8Command.builder()
+ .addClassProgramData(ToolHelper.getClassAsBytes(CLASS), Origin.unknown())
+ .setMode(CompilationMode.DEBUG)
+ .setOutput(cfOut, OutputMode.ClassFile)
+ .build(),
+ optionsConsumer);
+ return new CfDebugTestConfig(cfOut);
+ }
+
+ private Stream<DebuggeeState> createStream(DebugTestConfig config) throws Exception {
+ return streamDebugTest(config, NAME, ANDROID_FILTER);
+ }
+
+ @Test
+ @Ignore("b/111296969")
+ // Once R8 does not use expanded frames this can be enabled again.
+ public void test() throws Exception {
+ DebugTestConfig cfConfig = new CfDebugTestConfig().addPaths(ToolHelper.getClassPathForTests());
+ DebugTestConfig d8Config = new D8DebugTestConfig().compileAndAddClasses(temp, CLASS);
+ DebugTestConfig r8JarConfig =
+ getR8CfConfig("r8jar.jar", options -> options.enableCfFrontend = false);
+ DebugTestConfig r8CfConfig =
+ getR8CfConfig("r8cf.jar", options -> options.enableCfFrontend = true);
+ new DebugStreamComparator()
+ .add("CF", createStream(cfConfig))
+ .add("R8/CF", createStream(r8CfConfig))
+ .add("R8/Jar", createStream(r8JarConfig))
+ .add("D8", createStream(d8Config))
+ .compare();
+ }
+
+ @Test
+ // Verify that ASM fails when using expanded frames directly.
+ // See b/111296969
+ public void runTestOnAsmDump() throws Exception {
+ Path out = temp.getRoot().toPath().resolve("out.jar");
+ ArchiveConsumer consumer = new ArchiveConsumer(out);
+ consumer.accept(
+ ArrayDimensionGreaterThanSevenTestDump.dump(),
+ DescriptorUtils.javaTypeToDescriptor(NAME),
+ null);
+ consumer.finished(null);
+ ProcessResult result = ToolHelper.runJava(out, NAME);
+ assertEquals("Expected ASM to fail when using visitFrame(F_NEW, ...)", 1, result.exitCode);
+ assertThat(result.stderr, containsString("java.lang.NoClassDefFoundError: F"));
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/DebugStreamComparator.java b/src/test/java/com/android/tools/r8/debug/DebugStreamComparator.java
index eb85a7c..3a83e30 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugStreamComparator.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugStreamComparator.java
@@ -214,7 +214,8 @@
try {
if (done) {
assertTrue(
- "Not all streams completed at the same time",
+ "Not all streams completed at the same time. "
+ + "Set 'DebugTestBase.DEBUG_TEST = true' to aid in diagnosing the issue.",
states.stream().allMatch(Objects::isNull));
return;
} else {
diff --git a/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java b/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
index bda9ac5..6877617 100644
--- a/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
+++ b/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
@@ -10,11 +10,22 @@
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.graph.invokesuper2.C0;
+import com.android.tools.r8.graph.invokesuper2.C1;
+import com.android.tools.r8.graph.invokesuper2.C2;
+import com.android.tools.r8.graph.invokesuper2.I0;
+import com.android.tools.r8.graph.invokesuper2.I1;
+import com.android.tools.r8.graph.invokesuper2.I2;
+import com.android.tools.r8.graph.invokesuper2.I3;
+import com.android.tools.r8.graph.invokesuper2.I4;
+import com.android.tools.r8.graph.invokesuper2.Main;
import com.android.tools.r8.smali.SmaliBuilder;
import com.android.tools.r8.smali.SmaliTestBase;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.DexInspector;
import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
import java.util.Collections;
import org.junit.Test;
@@ -200,6 +211,63 @@
AndroidApp processedApp = processApplication(application);
assertEquals("42", runArt(processedApp));
}
+
+ @Test
+ public void testLookupSuperTarget() throws Exception {
+ String pkg = Main.class.getPackage().getName().replace('.', '/');
+
+ AndroidApp.Builder builder = AndroidApp.builder();
+ for (Class clazz : new Class[]{
+ I0.class, I1.class, I2.class, I3.class, I4.class,
+ C0.class, C1.class, C2.class,
+ Main.class}) {
+ builder.addProgramFiles(ToolHelper.getClassFileForTestClass(clazz));
+ // At least java.lang.Object is needed as interface method lookup have special handling
+ // of methods on java.lang.Object.
+ builder.addLibraryFiles(ToolHelper.getDefaultAndroidJar());
+ }
+ AndroidApp application = builder.build();
+ AppInfo appInfo = getAppInfo(application);
+ DexItemFactory factory = appInfo.dexItemFactory;
+
+ DexType i0 = factory.createType("L" + pkg + "/I0;");
+ DexType i1 = factory.createType("L" + pkg + "/I1;");
+ DexType i2 = factory.createType("L" + pkg + "/I2;");
+ DexType i3 = factory.createType("L" + pkg + "/I3;");
+ DexType i4 = factory.createType("L" + pkg + "/I4;");
+ DexType c0 = factory.createType("L" + pkg + "/C0;");
+ DexType c1 = factory.createType("L" + pkg + "/C1;");
+ DexType c2 = factory.createType("L" + pkg + "/C2;");
+
+ DexProto mProto = factory.createProto(factory.intType);
+ DexString m = factory.createString("m");
+ DexMethod mOnC0 = factory.createMethod(c0, mProto, m);
+ DexMethod mOnC1 = factory.createMethod(c1, mProto, m);
+ DexMethod mOnI0 = factory.createMethod(i0, mProto, m);
+ DexMethod mOnI1 = factory.createMethod(i1, mProto, m);
+ DexMethod mOnI2 = factory.createMethod(i2, mProto, m);
+ DexMethod mOnI3 = factory.createMethod(i3, mProto, m);
+ DexMethod mOnI4 = factory.createMethod(i4, mProto, m);
+
+ assertEquals(mOnI0, appInfo.lookupSuperTarget(mOnC0, c1).method);
+ assertEquals(mOnI1, appInfo.lookupSuperTarget(mOnI1, c1).method);
+ assertEquals(mOnI2, appInfo.lookupSuperTarget(mOnI2, c1).method);
+
+ assertEquals(mOnI0, appInfo.lookupSuperTarget(mOnC1, c2).method);
+ assertEquals(mOnI1, appInfo.lookupSuperTarget(mOnI3, c2).method);
+ assertEquals(mOnI2, appInfo.lookupSuperTarget(mOnI4, c2).method);
+
+ // Copy classes to run on the Java VM.
+ Path out = temp.newFolder().toPath();
+ copyTestClasses(out, I0.class, I1.class, I2.class, I3.class, I4.class);
+ copyTestClasses(out, C0.class, C1.class, C2.class, Main.class);
+ ProcessResult result = ToolHelper.runJava(out, Main.class.getCanonicalName());
+ assertEquals(0, result.exitCode);
+
+ // Process the application and expect the same result on Art.
+ AndroidApp processedApp = processApplication(application);
+ assertEquals(result.stdout, runArt(processedApp, Main.class.getCanonicalName()));
+ }
}
diff --git a/src/test/java/com/android/tools/r8/graph/invokesuper2/C0.java b/src/test/java/com/android/tools/r8/graph/invokesuper2/C0.java
new file mode 100644
index 0000000..8e92fb4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokesuper2/C0.java
@@ -0,0 +1,8 @@
+// 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.graph.invokesuper2;
+
+public class C0 implements I0 {
+}
diff --git a/src/test/java/com/android/tools/r8/graph/invokesuper2/C1.java b/src/test/java/com/android/tools/r8/graph/invokesuper2/C1.java
new file mode 100644
index 0000000..ff98da9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokesuper2/C1.java
@@ -0,0 +1,17 @@
+// 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.graph.invokesuper2;
+
+public class C1 extends C0 implements I1, I2 {
+ public int m() {
+ // super.m() becomes: invokespecial com/android/tools/r8/graph/invokesuper2/C0.m()I
+ System.out.println(super.m());
+ // I1.super.m() becomes: invokespecial com/android/tools/r8/graph/invokesuper2/I1.m:()I
+ System.out.println(I1.super.m());
+ // I2.super.m() becomes: invokespecial com/android/tools/r8/graph/invokesuper2/I2.m:()I
+ System.out.println(I2.super.m());
+ return 3;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/invokesuper2/C2.java b/src/test/java/com/android/tools/r8/graph/invokesuper2/C2.java
new file mode 100644
index 0000000..abdf7ff
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokesuper2/C2.java
@@ -0,0 +1,17 @@
+// 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.graph.invokesuper2;
+
+public class C2 extends C0 implements I3, I4 {
+ public int m() {
+ // super.m() becomes: invokespecial com/android/tools/r8/graph/invokesuper2/C0.m()I
+ System.out.println(super.m());
+ // I1.super.m() becomes: invokespecial com/android/tools/r8/graph/invokesuper2/I3.m:()I
+ System.out.println(I3.super.m());
+ // I2.super.m() becomes: invokespecial com/android/tools/r8/graph/invokesuper2/I4.m:()I
+ System.out.println(I4.super.m());
+ return 3;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/invokesuper2/I0.java b/src/test/java/com/android/tools/r8/graph/invokesuper2/I0.java
new file mode 100644
index 0000000..02b59a6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokesuper2/I0.java
@@ -0,0 +1,11 @@
+// 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.graph.invokesuper2;
+
+public interface I0 {
+ default int m() {
+ return 0;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/invokesuper2/I1.java b/src/test/java/com/android/tools/r8/graph/invokesuper2/I1.java
new file mode 100644
index 0000000..0593d90
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokesuper2/I1.java
@@ -0,0 +1,11 @@
+// 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.graph.invokesuper2;
+
+public interface I1 extends I0 {
+ default int m() {
+ return 1;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/invokesuper2/I2.java b/src/test/java/com/android/tools/r8/graph/invokesuper2/I2.java
new file mode 100644
index 0000000..22956c3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokesuper2/I2.java
@@ -0,0 +1,11 @@
+// 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.graph.invokesuper2;
+
+public interface I2 extends I0 {
+ default int m() {
+ return 2;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/invokesuper2/I3.java b/src/test/java/com/android/tools/r8/graph/invokesuper2/I3.java
new file mode 100644
index 0000000..e64154e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokesuper2/I3.java
@@ -0,0 +1,8 @@
+// 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.graph.invokesuper2;
+
+public interface I3 extends I1 {
+}
diff --git a/src/test/java/com/android/tools/r8/graph/invokesuper2/I4.java b/src/test/java/com/android/tools/r8/graph/invokesuper2/I4.java
new file mode 100644
index 0000000..ddc5ad0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokesuper2/I4.java
@@ -0,0 +1,8 @@
+// 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.graph.invokesuper2;
+
+public interface I4 extends I2 {
+}
diff --git a/src/test/java/com/android/tools/r8/graph/invokesuper2/Main.java b/src/test/java/com/android/tools/r8/graph/invokesuper2/Main.java
new file mode 100644
index 0000000..6a2a3b9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokesuper2/Main.java
@@ -0,0 +1,13 @@
+// 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.graph.invokesuper2;
+
+public class Main {
+
+ public static void main(String[] args) {
+ System.out.println(new C1().m());
+ System.out.println(new C2().m());
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java b/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
index 82381dd..9c15ce7 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
@@ -353,12 +353,16 @@
return out.toByteArray();
}
- public List<byte[]> buildClasses() throws Exception {
- List<byte[]> result = new ArrayList<>();
+ public ImmutableList.Builder<byte[]> buildClasses(ImmutableList.Builder<byte[]> builder)
+ throws Exception {
for (ClassBuilder clazz : classes) {
- result.add(compile(clazz));
+ builder.add(compile(clazz));
}
- return result;
+ return builder;
+ }
+
+ public List<byte[]> buildClasses() throws Exception {
+ return buildClasses(ImmutableList.builder()).build();
}
public AndroidApp build() throws Exception {
diff --git a/src/test/java/com/android/tools/r8/regress/b111250398/B111250398.java b/src/test/java/com/android/tools/r8/regress/b111250398/B111250398.java
new file mode 100644
index 0000000..90fba5f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b111250398/B111250398.java
@@ -0,0 +1,342 @@
+// 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.b111250398;
+
+import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.code.Iget;
+import com.android.tools.r8.code.IgetObject;
+import com.android.tools.r8.code.Sget;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.graph.DexField;
+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.FieldSubject;
+import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.collect.ImmutableList;
+import java.util.Arrays;
+import org.junit.Test;
+
+// Copy of javax.inject.Provider.
+interface Provider<T> {
+ T get();
+}
+
+// Copy of dagger.internal.SingleClass.
+final class SingleCheck<T> implements Provider<T> {
+ private static final Object UNINITIALIZED = new Object();
+
+ private volatile Provider<T> provider;
+ private volatile Object instance = UNINITIALIZED;
+
+ private SingleCheck(Provider<T> provider) {
+ assert provider != null;
+ this.provider = provider;
+ }
+
+ @SuppressWarnings("unchecked") // cast only happens when result comes from the delegate provider
+ @Override
+ public T get() {
+ Object local = instance;
+ if (local == UNINITIALIZED) {
+ // provider is volatile and might become null after the check, so retrieve the provider first
+ Provider<T> providerReference = provider;
+ if (providerReference == null) {
+ // The provider was null, so the instance must already be set
+ local = instance;
+ } else {
+ local = providerReference.get();
+ instance = local;
+
+ // Null out the reference to the provider. We are never going to need it again, so we can
+ // make it eligible for GC.
+ provider = null;
+ }
+ }
+ return (T) local;
+ }
+
+ // This method is not relevant for the test.
+ /*
+ public static <P extends Provider<T>, T> Provider<T> provider(P provider) {
+ // If a scoped @Binds delegates to a scoped binding, don't cache the value again.
+ if (provider instanceof SingleCheck || provider instanceof DoubleCheck) {
+ return provider;
+ }
+ return new SingleCheck<T>(checkNotNull(provider));
+ }
+ */
+}
+
+// Several field gets on non-volatile and volatile fields on the same class.
+class A {
+ int t;
+ int f;
+ static int sf;
+ volatile int v;
+ static volatile int sv;
+
+ public void mf() {
+ t = f;
+ t = f;
+ t = f;
+ t = f;
+ t = f;
+ }
+
+ public void msf() {
+ t = sf;
+ t = sf;
+ t = sf;
+ t = sf;
+ t = sf;
+ }
+
+ public void mv() {
+ t = v;
+ t = v;
+ t = v;
+ t = v;
+ t = v;
+ }
+
+ public void msv() {
+ t = sv;
+ t = sv;
+ t = sv;
+ t = sv;
+ t = sv;
+ }
+}
+
+// Several field gets on non-volatile and volatile fields on different class.
+class B {
+ int t;
+
+ public void mf(A a) {
+ t = a.f;
+ t = a.f;
+ t = a.f;
+ t = a.f;
+ t = a.f;
+ }
+
+ public void msf() {
+ t = A.sf;
+ t = A.sf;
+ t = A.sf;
+ t = A.sf;
+ t = A.sf;
+ }
+
+ public void mv(A a) {
+ t = a.v;
+ t = a.v;
+ t = a.v;
+ t = a.v;
+ t = a.v;
+ }
+
+ public void msv() {
+ t = A.sv;
+ t = A.sv;
+ t = A.sv;
+ t = A.sv;
+ t = A.sv;
+ }
+}
+
+// Modified sample from http://tutorials.jenkov.com/java-concurrency/volatile.html.
+class C {
+ private int years;
+ private int months;
+ private volatile int days;
+
+ public int totalDays() {
+ int total = this.days;
+ total += months * 30;
+ total += years * 365;
+ return total;
+ }
+
+ public int totalDaysTimes2() {
+ int total = this.days;
+ total += months * 30;
+ total += years * 365;
+ total += this.days;
+ total += months * 30;
+ total += years * 365;
+ return total;
+ }
+
+ public int totalDaysTimes3() {
+ int total = this.days;
+ total += months * 30;
+ total += years * 365;
+ total += this.days;
+ total += months * 30;
+ total += years * 365;
+ total += this.days;
+ total += months * 30;
+ total += years * 365;
+ return total;
+ }
+
+ public void update(int years, int months, int days){
+ this.years = years;
+ this.months = months;
+ this.days = days;
+ }
+}
+
+public class B111250398 extends TestBase {
+
+ private void releaseMode(InternalOptions options) {
+ options.debug = false;
+ }
+
+ private long countIget(DexCode code, DexField field) {
+ return Arrays.stream(code.instructions)
+ .filter(instruction -> instruction instanceof Iget)
+ .map(instruction -> (Iget) instruction)
+ .filter(get -> get.getField() == field)
+ .count();
+ }
+
+ private long countSget(DexCode code, DexField field) {
+ return Arrays.stream(code.instructions)
+ .filter(instruction -> instruction instanceof Sget)
+ .map(instruction -> (Sget) instruction)
+ .filter(get -> get.getField() == field)
+ .count();
+ }
+
+ private long countIgetObject(MethodSubject method, FieldSubject field) {
+ return Arrays.stream(method.getMethod().getCode().asDexCode().instructions)
+ .filter(instruction -> instruction instanceof IgetObject)
+ .map(instruction -> (IgetObject) instruction)
+ .filter(get -> get.getField() == field.getField().field)
+ .count();
+ }
+
+ private void check(DexInspector inspector, int mfOnBGets, int msfOnBGets) {
+ ClassSubject classA = inspector.clazz(A.class);
+ assertThat(classA, isPresent());
+ MethodSubject mfOnA = classA.method("void", "mf", ImmutableList.of());
+ assertThat(mfOnA, isPresent());
+ MethodSubject msfOnA = classA.method("void", "msf", ImmutableList.of());
+ assertThat(msfOnA, isPresent());
+ MethodSubject mvOnA = classA.method("void", "mv", ImmutableList.of());
+ assertThat(mvOnA, isPresent());
+ MethodSubject msvOnA = classA.method("void", "msv", ImmutableList.of());
+ assertThat(msvOnA, isPresent());
+ FieldSubject fOnA = classA.field("int", "f");
+ assertThat(fOnA, isPresent());
+ FieldSubject sfOnA = classA.field("int", "sf");
+ assertThat(sfOnA, isPresent());
+ FieldSubject vOnA = classA.field("int", "v");
+ assertThat(vOnA, isPresent());
+ FieldSubject svOnA = classA.field("int", "sv");
+ assertThat(svOnA, isPresent());
+ ClassSubject classB = inspector.clazz(B.class);
+ assertThat(classB, isPresent());
+ MethodSubject mfOnB = classB.method("void", "mf", ImmutableList.of(classA.getOriginalName()));
+ assertThat(mfOnB, isPresent());
+ MethodSubject msfOnB = classB.method("void", "msf", ImmutableList.of());
+ assertThat(msfOnB, isPresent());
+ MethodSubject mvOnB = classB.method("void", "mv", ImmutableList.of(classA.getOriginalName()));
+ assertThat(mvOnB, isPresent());
+ MethodSubject msvOnB = classB.method("void", "msv", ImmutableList.of());
+ assertThat(msvOnB, isPresent());
+ // Field load of volatile fields are never eliminated.
+ assertEquals(5, countIget(mvOnA.getMethod().getCode().asDexCode(), vOnA.getField().field));
+ assertEquals(5, countSget(msvOnA.getMethod().getCode().asDexCode(), svOnA.getField().field));
+ assertEquals(5, countIget(mvOnB.getMethod().getCode().asDexCode(), vOnA.getField().field));
+ assertEquals(5, countSget(msvOnB.getMethod().getCode().asDexCode(), svOnA.getField().field));
+ // For fields on the same class both separate compilation (D8) and whole program
+ // compilation (R8) will eliminate field loads on non-volatile fields.
+ assertEquals(1, countIget(mfOnA.getMethod().getCode().asDexCode(), fOnA.getField().field));
+ assertEquals(1, countSget(msfOnA.getMethod().getCode().asDexCode(), sfOnA.getField().field));
+ // For fields on other class both separate compilation (D8) and whole program
+ // compilation (R8) will differ in the eliminated field loads of non-volatile fields.
+ assertEquals(mfOnBGets,
+ countIget(mfOnB.getMethod().getCode().asDexCode(), fOnA.getField().field));
+ assertEquals(msfOnBGets,
+ countSget(msfOnB.getMethod().getCode().asDexCode(), sfOnA.getField().field));
+ }
+
+ @Test
+ public void testSeparateCompilation() throws Exception {
+ DexInspector inspector =
+ new DexInspector(compileWithD8(readClasses(A.class, B.class), this::releaseMode));
+ check(inspector, 5, 5);
+ }
+
+ @Test
+ public void testWholeProgram() throws Exception {
+ DexInspector inspector =
+ new DexInspector(compileWithR8(readClasses(A.class, B.class), this::releaseMode));
+ // The reason for getting two Igets in B.mf is that the first Iget inserts a NonNull
+ // instruction which creates a new value for the remaining Igets.
+ check(inspector, 2, 1);
+ }
+
+ private void checkMixed(AndroidApp app) throws Exception{
+ DexInspector inspector = new DexInspector(app);
+ ClassSubject classC = inspector.clazz(C.class);
+ assertThat(classC, isPresent());
+ MethodSubject totalDays = classC.method("int", "totalDays", ImmutableList.of());
+ assertThat(totalDays, isPresent());
+ MethodSubject totalDaysTimes2 = classC.method("int", "totalDaysTimes2", ImmutableList.of());
+ assertThat(totalDaysTimes2, isPresent());
+ MethodSubject totalDaysTimes3 = classC.method("int", "totalDaysTimes3", ImmutableList.of());
+ assertThat(totalDaysTimes3, isPresent());
+ FieldSubject years = classC.field("int", "years");
+ assertThat(years, isPresent());
+ FieldSubject months = classC.field("int", "months");
+ assertThat(months, isPresent());
+ FieldSubject days = classC.field("int", "days");
+ assertThat(days, isPresent());
+
+
+ for (FieldSubject field : new FieldSubject[]{years, months, days}) {
+ assertEquals(1,
+ countIget(totalDays.getMethod().getCode().asDexCode(), field.getField().field));
+ assertEquals(2,
+ countIget(totalDaysTimes2.getMethod().getCode().asDexCode(), field.getField().field));
+ assertEquals(3,
+ countIget(totalDaysTimes3.getMethod().getCode().asDexCode(), field.getField().field));
+ }
+ }
+
+ @Test
+ public void testMixedVolatileNonVolatile() throws Exception {
+ AndroidApp app = readClasses(C.class);
+ checkMixed(compileWithD8(app, this::releaseMode));
+ checkMixed(compileWithR8(app, this::releaseMode));
+ }
+
+ private void checkDaggerSingleProviderGet(AndroidApp app) throws Exception {
+ DexInspector inspector = new DexInspector(app);
+ MethodSubject get =
+ inspector.clazz(SingleCheck.class).method("java.lang.Object", "get", ImmutableList.of());
+ assertThat(get, isPresent());
+ FieldSubject instance =
+ inspector.clazz(SingleCheck.class).field("java.lang.Object", "instance");
+ assertEquals(2, countIgetObject(get, instance));
+ }
+
+ @Test
+ public void testDaggerSingleProvider() throws Exception {
+ AndroidApp app = readClasses(Provider.class, SingleCheck.class);
+ checkDaggerSingleProviderGet(compileWithD8(app, this::releaseMode));
+ checkDaggerSingleProviderGet(compileWithR8(app, this::releaseMode));
+ }
+}