Merge "Register the correct target for a super invoke for generating uses"
diff --git a/build.gradle b/build.gradle
index e469e03..2e70b1e 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1199,10 +1199,10 @@
kotlin.Kotlinc.KotlinTargetVersion.values().each { kotlinTargetVersion ->
def name = dir.getName()
def taskName = "jar_kotlinR8TestResources_${name}_${kotlinTargetVersion}"
- def outputFile = "build/test/kotlinR8TestResources/${kotlinTargetVersion}/${name}.jar"
- def javaOutput = "build/test/kotlinR8TestResources/${kotlinTargetVersion}/${name}/java"
+ def outputFile = "build/test/r8KotlinTestResources/${kotlinTargetVersion}/${name}.jar"
+ def javaOutput = "build/test/r8KotlinTestResources/${kotlinTargetVersion}/${name}/java"
def javaOutputJarName = "${name}.java.jar"
- def javaOutputJarDir = "build/test/kotlinR8TestResources/${kotlinTargetVersion}"
+ def javaOutputJarDir = "build/test/r8KotlinTestResources/${kotlinTargetVersion}"
task "${taskName}Kotlin"(type: kotlin.Kotlinc) {
source = fileTree(dir: file("${examplesDir}/${name}"),
include: ['**/*.kt', '**/*.java'])
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 4288e55..908c4c5 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
// This field is accessed from release scripts using simple pattern matching.
// Therefore, changing this field could break our release scripts.
- public static final String LABEL = "1.4.19-dev";
+ public static final String LABEL = "1.4.20-dev";
private Version() {
}
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 648d644..5644ed7 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
@@ -98,10 +98,10 @@
// If this is the case, which holds for javac code, then we want to ensure that it remains so.
private boolean allThrowingInstructionsHavePositions;
+ // TODO(b/122257895): Update OptimizationInfo to capture instruction kinds of interest.
public final boolean hasDebugPositions;
-
- // TODO(jsjeon): maybe making similar to DexEncodedMethod.OptimizationInfo?
public boolean hasConstString;
+ public final boolean hasMonitorInstruction;
public final InternalOptions options;
@@ -113,6 +113,7 @@
LinkedList<BasicBlock> blocks,
ValueNumberGenerator valueNumberGenerator,
boolean hasDebugPositions,
+ boolean hasMonitorInstruction,
boolean hasConstString,
Origin origin) {
assert options != null;
@@ -121,6 +122,7 @@
this.blocks = blocks;
this.valueNumberGenerator = valueNumberGenerator;
this.hasDebugPositions = hasDebugPositions;
+ this.hasMonitorInstruction = hasMonitorInstruction;
this.hasConstString = hasConstString;
this.origin = origin;
// TODO(zerny): Remove or update this property now that all instructions have positions.
@@ -128,6 +130,7 @@
}
public void copyMetadataFromInlinee(IRCode inlinee) {
+ assert !inlinee.hasMonitorInstruction;
this.hasConstString |= inlinee.hasConstString;
}
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 c789424..007cac1 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
@@ -419,6 +419,9 @@
// Flag indicating if a const string is ever loaded.
private boolean hasConstString = false;
+ // Flag indicating if the code has a monitor instruction.
+ private boolean hasMonitorInstruction = false;
+
public IRBuilder(
DexEncodedMethod method,
AppInfo appInfo,
@@ -613,6 +616,7 @@
blocks,
valueNumberGenerator,
hasDebugPositions,
+ hasMonitorInstruction,
hasConstString,
origin);
@@ -1133,6 +1137,7 @@
Value in = readRegister(monitor, ValueTypeConstraint.OBJECT);
Monitor monitorEnter = new Monitor(type, in);
add(monitorEnter);
+ hasMonitorInstruction = true;
return monitorEnter;
}
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 7bc04cc..737ab05 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
@@ -1155,26 +1155,6 @@
private void computeKotlinNonNullParamHints(
OptimizationFeedback feedback, DexEncodedMethod method, IRCode code) {
- DexMethod originalSignature = graphLense().getOriginalMethodSignature(method.method);
- DexClass originalHolder = definitionFor(originalSignature.holder);
- if (!originalHolder.hasKotlinInfo()) {
- return;
- }
-
- // Use non-null parameter hints in Kotlin metadata if available.
- KotlinInfo kotlinInfo = originalHolder.getKotlinInfo();
- if (kotlinInfo.hasNonNullParameterHints()) {
- BitSet hintFromMetadata =
- kotlinInfo.lookupNonNullParameterHint(
- originalSignature.name.toString(), originalSignature.proto.toDescriptorString());
- if (hintFromMetadata != null) {
- if (hintFromMetadata.length() > 0) {
- feedback.setNonNullParamOrThrow(method, hintFromMetadata);
- }
- return;
- }
- }
- // Otherwise, fall back to inspecting the code.
List<Value> arguments = code.collectArguments(true);
BitSet paramsCheckedForNull = new BitSet();
DexMethod checkParameterIsNotNull =
@@ -1194,17 +1174,40 @@
}
InvokeMethod invoke = user.asInvokeMethod();
DexMethod invokedMethod =
- appView.graphLense().getOriginalMethodSignature(invoke.getInvokedMethod());
+ graphLense().getOriginalMethodSignature(invoke.getInvokedMethod());
+ // TODO(b/121377154): Make sure there is no other side-effect before argument's null check.
+ // E.g., is this the first method invocation inside the method?
if (invokedMethod == checkParameterIsNotNull && user.inValues().indexOf(argument) == 0) {
paramsCheckedForNull.set(index);
}
}
}
if (paramsCheckedForNull.length() > 0) {
+ // Check if collected information conforms to non-null parameter hints in Kotlin metadata.
+ DexMethod originalSignature = graphLense().getOriginalMethodSignature(method.method);
+ DexClass originalHolder = definitionFor(originalSignature.holder);
+ if (originalHolder.hasKotlinInfo()) {
+ KotlinInfo kotlinInfo = originalHolder.getKotlinInfo();
+ if (kotlinInfo.hasNonNullParameterHints()) {
+ BitSet hintFromMetadata =
+ kotlinInfo.lookupNonNullParameterHint(
+ originalSignature.name.toString(), originalSignature.proto.toDescriptorString());
+ if (hintFromMetadata != null && hintFromMetadata.length() > 0) {
+ if (!paramsCheckedForNull.equals(hintFromMetadata) && Log.ENABLED) {
+ Log.debug(getClass(), "Mismatching non-null param hints for %s: %s v.s. %s\n%s",
+ paramsCheckedForNull.toString(),
+ hintFromMetadata.toString(),
+ method.toSourceString(),
+ logCode(options, method));
+ }
+ }
+ }
+ }
feedback.setNonNullParamOrThrow(method, paramsCheckedForNull);
}
}
+ // TODO(b/121377154): Consider merging compute(Kotlin)?NonNullParamHints into one.
private void computeNonNullParamHints(
OptimizationFeedback feedback, DexEncodedMethod method, IRCode code) {
List<Value> arguments = code.collectArguments(true);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
index fc4c88b..4c437bd 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
@@ -60,6 +60,12 @@
if (!current.isConstNumber() && !current.isConstString()) {
continue;
}
+ // Do not canonicalize ConstString instructions if there are monitor operations in the code.
+ // That could lead to unbalanced locking and could lead to situations where OOM exceptions
+ // could leave a synchronized method without unlocking the monitor.
+ if (current.isConstString() && code.hasMonitorInstruction) {
+ continue;
+ }
// Constants with local info must not be canonicalized and must be filtered.
if (current.outValue().hasLocalInfo()) {
continue;
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java b/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
index ff38ebd..0251687 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
@@ -599,7 +599,12 @@
isRematerializable = true;
return;
}
- // TODO(ager): rematerialize const string as well.
+
+ // TODO(ager): rematerialize const string as well. However, in that case we have to be very
+ // careful with methods with synchronization. If we rematerialize in a block that has no
+ // other throwing instructions we can end up with lock-level verification issues. The
+ // rematerialized throwing const-string instruction is not covered by the catch range going
+ // to the monitor-exit instruction and we can leave the method without unlocking the monitor.
if (!value.isConstNumber()) {
return;
}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationUtils.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationUtils.java
index 53e8e66..0f1588f 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationUtils.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationUtils.java
@@ -92,6 +92,8 @@
}
public static ProguardKeepRule buildMethodKeepRule(DexClass clazz, DexEncodedMethod method) {
+ // TODO(b/122295241): These generated rules should be linked into the graph, eg, the method
+ // using identified reflection should be the source keeping the target alive.
assert clazz.type == method.method.getHolder();
ProguardKeepRule.Builder builder = ProguardKeepRule.builder();
builder.setOrigin(proguardCompatOrigin);
diff --git a/src/test/java/com/android/tools/r8/KotlinTestBase.java b/src/test/java/com/android/tools/r8/KotlinTestBase.java
index 9139583..63dd265 100644
--- a/src/test/java/com/android/tools/r8/KotlinTestBase.java
+++ b/src/test/java/com/android/tools/r8/KotlinTestBase.java
@@ -9,7 +9,9 @@
import java.nio.file.Paths;
public abstract class KotlinTestBase extends TestBase {
- private static final String RSRC = "kotlinR8TestResources";
+ // It is important that Kotlin is capitalized, otherwise the string will be relocated when
+ // building tests for r8lib with relocated dependencies.
+ private static final String RSRC = "r8KotlinTestResources";
protected final KotlinTargetVersion targetVersion;
@@ -17,18 +19,21 @@
this.targetVersion = targetVersion;
}
- protected Path getKotlinJarFile(String folder) {
+ protected static Path getKotlinJarFile(String folder, KotlinTargetVersion targetVersion) {
return Paths.get(ToolHelper.TESTS_BUILD_DIR, RSRC,
targetVersion.getFolderName(), folder + FileUtils.JAR_EXTENSION);
}
- protected Path getJavaJarFile(String folder) {
+ protected Path getKotlinJarFile(String folder) {
+ return getKotlinJarFile(folder, targetVersion);
+ }
+
+ protected static Path getJavaJarFile(String folder, KotlinTargetVersion targetVersion) {
return Paths.get(ToolHelper.TESTS_BUILD_DIR, RSRC,
targetVersion.getFolderName(), folder + ".java" + FileUtils.JAR_EXTENSION);
}
- protected Path getMappingfile(String folder, String mappingFileName) {
- return Paths.get(ToolHelper.TESTS_DIR, RSRC, folder, mappingFileName);
+ protected Path getJavaJarFile(String folder) {
+ return getJavaJarFile(folder, targetVersion);
}
-
}
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index 475f764..8f9eab9 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -40,6 +40,7 @@
private boolean enableClassInliningAnnotations = false;
private boolean enableMergeAnnotations = false;
private CollectingGraphConsumer graphConsumer = null;
+ private List<String> keepRules = new ArrayList<>();
@Override
R8TestBuilder self() {
@@ -53,6 +54,9 @@
if (enableInliningAnnotations || enableClassInliningAnnotations || enableMergeAnnotations) {
ToolHelper.allowTestProguardOptions(builder);
}
+ if (!keepRules.isEmpty()) {
+ builder.addProguardConfiguration(keepRules, Origin.unknown());
+ }
StringBuilder proguardMapBuilder = new StringBuilder();
builder.setDisableTreeShaking(!enableTreeShaking);
builder.setDisableMinification(!enableMinification);
@@ -75,7 +79,9 @@
@Override
public R8TestBuilder addKeepRules(Collection<String> rules) {
- builder.addProguardConfiguration(new ArrayList<>(rules), Origin.unknown());
+ // Delay adding the actual rules so that we only associate a single origin and unique lines to
+ // each actual rule.
+ keepRules.addAll(rules);
return self();
}
@@ -104,7 +110,7 @@
public R8TestBuilder enableInliningAnnotations() {
if (!enableInliningAnnotations) {
enableInliningAnnotations = true;
- addKeepRules(
+ addInternalKeepRules(
"-forceinline class * { @com.android.tools.r8.ForceInline *; }",
"-neverinline class * { @com.android.tools.r8.NeverInline *; }");
}
@@ -114,7 +120,7 @@
public R8TestBuilder enableClassInliningAnnotations() {
if (!enableClassInliningAnnotations) {
enableClassInliningAnnotations = true;
- addKeepRules("-neverclassinline @com.android.tools.r8.NeverClassInline class *");
+ addInternalKeepRules("-neverclassinline @com.android.tools.r8.NeverClassInline class *");
}
return self();
}
@@ -122,8 +128,7 @@
public R8TestBuilder enableMergeAnnotations() {
if (!enableMergeAnnotations) {
enableMergeAnnotations = true;
- addKeepRules(
- "-nevermerge @com.android.tools.r8.NeverMerge class *");
+ addInternalKeepRules("-nevermerge @com.android.tools.r8.NeverMerge class *");
}
return self();
}
@@ -150,4 +155,9 @@
builder.setMainDexKeptGraphConsumer(graphConsumer);
return self();
}
+
+ private void addInternalKeepRules(String... rules) {
+ // We don't add these to the keep-rule set for other test provided rules.
+ builder.addProguardConfiguration(Arrays.asList(rules), Origin.unknown());
+ }
}
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index 211c297..81f0407 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -7,7 +7,6 @@
import com.android.tools.r8.TestBase.Backend;
import com.android.tools.r8.references.MethodReference;
import com.android.tools.r8.references.TypeReference;
-import com.android.tools.r8.utils.StringUtils;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Arrays;
@@ -87,10 +86,7 @@
public T addKeepMainRule(String mainClass) {
return addKeepRules(
- StringUtils.joinLines(
- "-keep class " + mainClass + " {",
- " public static void main(java.lang.String[]);",
- "}"));
+ "-keep class " + mainClass + " { public static void main(java.lang.String[]); }");
}
public T addKeepMethodRules(MethodReference... methods) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
index 55c64ee..927bff2 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
@@ -132,7 +132,14 @@
AppInfo appInfo = new AppInfo(DexApplication.builder(options.itemFactory, null).build());
IRCode code =
new IRCode(
- options, null, blocks, new ValueNumberGenerator(), false, false, Origin.unknown());
+ options,
+ null,
+ blocks,
+ new ValueNumberGenerator(),
+ false,
+ false,
+ false,
+ Origin.unknown());
PeepholeOptimizer.optimize(code, new MockLinearScanRegisterAllocator(appInfo, code, options));
// Check that all four constant number instructions remain.
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
index 8db3a73..6f22e21 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
@@ -79,6 +79,7 @@
new ValueNumberGenerator(),
false,
false,
+ false,
Origin.unknown());
CodeRewriter.collapseTrivialGotos(null, code);
assertTrue(code.blocks.get(0).isTrivialGoto());
@@ -162,6 +163,7 @@
new ValueNumberGenerator(),
false,
false,
+ false,
Origin.unknown());
CodeRewriter.collapseTrivialGotos(null, code);
assertTrue(block0.getInstructions().get(1).isIf());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringInMonitorTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringInMonitorTest.java
new file mode 100644
index 0000000..0b049e3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringInMonitorTest.java
@@ -0,0 +1,204 @@
+// Copyright (c) 2019, 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.string;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionOffsetSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject.JumboStringMode;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.android.tools.r8.utils.codeinspector.RangeSubject;
+import com.android.tools.r8.utils.codeinspector.TryCatchSubject;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Streams;
+import java.util.Iterator;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+class StringInMonitorTestMain {
+ static final Object lock = new Object();
+
+ private static int foo = 0;
+
+ @NeverInline
+ private static synchronized void sync() throws OutOfMemoryError {
+ String bar = foo == 0 ? "bar" : "";
+ if (bar == "") {
+ System.out.println("bar");
+ throw new OutOfMemoryError();
+ }
+ }
+
+ @NeverInline
+ private static void oom() throws OutOfMemoryError {
+ System.out.println("oom");
+ if (System.currentTimeMillis() > 0) {
+ throw new OutOfMemoryError();
+ }
+ System.out.println("oom");
+ System.out.println("this-string-will-not-be-loaded.");
+ System.out.println("this-string-will-not-be-loaded.");
+ }
+
+ public static void main(String[] args) {
+ try {
+ synchronized (lock) {
+ System.out.println("1st sync");
+ sync();
+ oom();
+ System.out.println("1st sync");
+ }
+ } catch (OutOfMemoryError oom) {
+ // Pretend to recover from OOM
+ synchronized (lock) {
+ System.out.println("2nd sync");
+ }
+ }
+ }
+}
+
+@RunWith(Parameterized.class)
+public class StringInMonitorTest extends TestBase {
+ private final Backend backend;
+ private static final Class<?> MAIN = StringInMonitorTestMain.class;
+ private static final List<Class<?>> CLASSES = ImmutableList.of(NeverInline.class, MAIN);
+ private static final String JAVA_OUTPUT = StringUtils.lines(
+ "1st sync",
+ "oom",
+ "2nd sync"
+ );
+
+ @Parameterized.Parameters(name = "Backend: {0}")
+ public static Backend[] data() {
+ return Backend.values();
+ }
+
+ public StringInMonitorTest(Backend backend) {
+ this.backend = backend;
+ }
+
+ @Test
+ public void testJVMoutput() throws Exception {
+ assumeTrue("Only run JVM reference once (for CF backend)", backend == Backend.CF);
+ testForJvm().addTestClasspath().run(MAIN).assertSuccessWithOutput(JAVA_OUTPUT);
+ }
+
+ private void test(
+ TestRunResult result,
+ int expectedConstStringCount1,
+ int expectedConstStringCount2,
+ int expectedConstStringCount3) throws Exception {
+ CodeInspector codeInspector = result.inspector();
+ ClassSubject mainClass = codeInspector.clazz(MAIN);
+ MethodSubject mainMethod = mainClass.mainMethod();
+ assertThat(mainMethod, isPresent());
+
+ long count = Streams.stream(mainMethod.iterateInstructions(
+ i -> i.isConstString("1st sync", JumboStringMode.ALLOW))).count();
+ assertEquals(expectedConstStringCount1, count);
+
+ // TODO(b/122302789): CfInstruction#getOffset()
+ if (backend == Backend.DEX) {
+ Iterator<InstructionSubject> constStringIterator =
+ mainMethod.iterateInstructions(i -> i.isConstString(JumboStringMode.ALLOW));
+ // All const-string's in main(...) should be covered by try (or synthetic catch-all) region.
+ while (constStringIterator.hasNext()) {
+ InstructionSubject constString = constStringIterator.next();
+ InstructionOffsetSubject offsetSubject = constString.getOffset(mainMethod);
+ // const-string of interest is indirectly covered. See b/122285813 for reference.
+ Iterator<TryCatchSubject> catchAllIterator =
+ mainMethod.iterateTryCatches(TryCatchSubject::hasCatchAll);
+ boolean covered = false;
+ while (catchAllIterator.hasNext()) {
+ RangeSubject tryRange = catchAllIterator.next().getRange();
+ covered |= tryRange.includes(offsetSubject);
+ if (covered) {
+ break;
+ }
+ }
+ assertTrue(covered);
+ }
+ }
+
+ MethodSubject sync = mainClass.uniqueMethodWithName("sync");
+ assertThat(sync, isPresent());
+ count = Streams.stream(sync.iterateInstructions(
+ i -> i.isConstString("", JumboStringMode.ALLOW))).count();
+ assertEquals(expectedConstStringCount2, count);
+
+ // In CF, we don't explicitly add monitor-{enter|exit} and catch-all for synchronized methods.
+ if (backend == Backend.DEX) {
+ Iterator<InstructionSubject> constStringIterator =
+ sync.iterateInstructions(i -> i.isConstString(JumboStringMode.ALLOW));
+ // All const-string's in sync() should be covered by the synthetic catch-all regions.
+ while (constStringIterator.hasNext()) {
+ InstructionSubject constString = constStringIterator.next();
+ InstructionOffsetSubject offsetSubject = constString.getOffset(mainMethod);
+ Iterator<TryCatchSubject> catchAllIterator =
+ sync.iterateTryCatches(TryCatchSubject::hasCatchAll);
+ boolean covered = false;
+ while (catchAllIterator.hasNext()) {
+ RangeSubject tryRange = catchAllIterator.next().getRange();
+ covered |= tryRange.includes(offsetSubject);
+ if (covered) {
+ break;
+ }
+ }
+ assertTrue(covered);
+ }
+ }
+
+ MethodSubject oom = mainClass.uniqueMethodWithName("oom");
+ assertThat(oom, isPresent());
+ count = Streams.stream(oom.iterateInstructions(
+ i -> i.isConstString("this-string-will-not-be-loaded.", JumboStringMode.ALLOW))).count();
+ assertEquals(expectedConstStringCount3, count);
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ assumeTrue("Only run D8 for Dex backend", backend == Backend.DEX);
+ TestRunResult result = testForD8()
+ .debug()
+ .addProgramClasses(CLASSES)
+ .run(MAIN)
+ .assertSuccessWithOutput(JAVA_OUTPUT);
+ test(result, 2, 2, 1);
+
+ result = testForD8()
+ .release()
+ .addProgramClasses(CLASSES)
+ .run(MAIN)
+ .assertSuccessWithOutput(JAVA_OUTPUT);
+ test(result, 2, 2, 1);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ TestRunResult result = testForR8(backend)
+ .addProgramClasses(CLASSES)
+ .enableInliningAnnotations()
+ .addKeepMainRule(MAIN)
+ .run(MAIN)
+ .assertSuccessWithOutput(JAVA_OUTPUT);
+ // Due to the different behavior regarding constant canonicalization.
+ int expectedConstStringCount3 = backend == Backend.CF ? 2 : 1;
+ test(result, 2, 2, expectedConstStringCount3);
+ }
+
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java b/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
index 9380115..4229213 100644
--- a/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
+++ b/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
@@ -10,9 +10,9 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import com.android.tools.r8.KotlinTestBase;
import com.android.tools.r8.OutputMode;
import com.android.tools.r8.R8Command;
-import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
import com.android.tools.r8.graph.Code;
@@ -24,7 +24,6 @@
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.DescriptorUtils;
-import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -32,7 +31,6 @@
import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
import java.nio.file.Path;
-import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -46,7 +44,7 @@
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
-public abstract class AbstractR8KotlinTestBase extends TestBase {
+public abstract class AbstractR8KotlinTestBase extends KotlinTestBase {
// This is the name of the Jasmin-generated class which contains the "main" method which will
// invoke the tested method.
@@ -63,6 +61,10 @@
return buildParameters(BooleanUtils.values(), KotlinTargetVersion.values());
}
+ public AbstractR8KotlinTestBase() {
+ super(KotlinTargetVersion.JAVA_6);
+ }
+
protected void addExtraClasspath(Path path) {
extraClasspath.add(path);
}
@@ -230,8 +232,8 @@
// Build classpath for compilation (and java execution)
classpath.clear();
- classpath.add(getKotlinJarFile(folder));
- classpath.add(getJavaJarFile(folder));
+ classpath.add(getKotlinJarFile(folder, targetVersion));
+ classpath.add(getJavaJarFile(folder, targetVersion));
classpath.addAll(extraClasspath);
// Build with R8
@@ -296,16 +298,6 @@
}
}
- private Path getKotlinJarFile(String folder) {
- return Paths.get(ToolHelper.TESTS_BUILD_DIR, "kotlinR8TestResources",
- targetVersion.getFolderName(), folder + FileUtils.JAR_EXTENSION);
- }
-
- private Path getJavaJarFile(String folder) {
- return Paths.get(ToolHelper.TESTS_BUILD_DIR, "kotlinR8TestResources",
- targetVersion.getFolderName(), folder + ".java" + FileUtils.JAR_EXTENSION);
- }
-
@FunctionalInterface
interface AndroidAppInspector {
diff --git a/src/test/java/com/android/tools/r8/shaking/InvalidTypesTest.java b/src/test/java/com/android/tools/r8/shaking/InvalidTypesTest.java
index 4248497..6906b76 100644
--- a/src/test/java/com/android/tools/r8/shaking/InvalidTypesTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/InvalidTypesTest.java
@@ -14,6 +14,8 @@
import com.android.tools.r8.ProguardTestRunResult;
import com.android.tools.r8.R8TestRunResult;
import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.jasmin.JasminBuilder;
import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
@@ -279,8 +281,21 @@
} else if (compiler == Compiler.PROGUARD) {
return StringUtils.joinLines("Hello!", "Unexpected outcome of checkcast", "Goodbye!", "");
} else if (compiler == Compiler.DX || compiler == Compiler.D8) {
- return StringUtils.joinLines("Hello!", "Unexpected outcome of checkcast", "Goodbye!", "");
+ if (ToolHelper.getDexVm().getVersion() == Version.V4_0_4
+ || ToolHelper.getDexVm().getVersion() == Version.V4_4_4) {
+ return StringUtils.joinLines("Hello!", "Goodbye!", "");
+ } else if (ToolHelper.getDexVm().getVersion() == Version.V7_0_0) {
+ return StringUtils.joinLines(
+ "Hello!",
+ "Unexpected outcome of checkcast",
+ "Unexpected outcome of instanceof",
+ "Goodbye!",
+ "");
+ } else {
+ return StringUtils.joinLines("Hello!", "Unexpected outcome of checkcast", "Goodbye!", "");
+ }
} else {
+ assert compiler == Compiler.JAVAC;
return StringUtils.joinLines("Hello!", "Goodbye!", "");
}
} else {
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByAnnotatedMethodTest.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByAnnotatedMethodTest.java
new file mode 100644
index 0000000..baeacd2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByAnnotatedMethodTest.java
@@ -0,0 +1,39 @@
+// Copyright (c) 2019, 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.shaking.keptgraph;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.NeverInline;
+
+public class KeptByAnnotatedMethodTest {
+
+ static class Inner {
+
+ @Keep
+ void foo() {
+ bar();
+ }
+
+ @NeverInline
+ static void bar() {
+ System.out.println("called bar");
+ }
+
+ @NeverInline
+ static void baz() {
+ System.out.println("called baz");
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+ // Make inner class undecidable to avoid generating reflective rules.
+ Class<?> clazz = getInner(args.length);
+ Object instance = clazz.newInstance();
+ clazz.getDeclaredMethod("foo").invoke(instance);
+ }
+
+ private static Class<?> getInner(int i) {
+ return i == 0 ? Inner.class : null;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByAnnotatedMethodTestRunner.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByAnnotatedMethodTestRunner.java
new file mode 100644
index 0000000..19cdf04
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByAnnotatedMethodTestRunner.java
@@ -0,0 +1,95 @@
+// Copyright (c) 2019, 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.shaking.keptgraph;
+
+import static com.android.tools.r8.references.Reference.methodFromMethod;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.graphinspector.GraphInspector;
+import com.android.tools.r8.utils.graphinspector.GraphInspector.QueryNode;
+import java.util.Arrays;
+import java.util.Collection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class KeptByAnnotatedMethodTestRunner extends TestBase {
+
+ private static final Class<?> CLASS = KeptByAnnotatedMethodTest.class;
+ private static final Class<?> INNER = KeptByAnnotatedMethodTest.Inner.class;
+ private static final Collection<Class<?>> CLASSES = Arrays.asList(CLASS, INNER);
+
+ private final String EXPECTED = StringUtils.lines("called bar");
+
+ private final Backend backend;
+
+ @Parameters(name = "{0}")
+ public static Backend[] data() {
+ return Backend.values();
+ }
+
+ public KeptByAnnotatedMethodTestRunner(Backend backend) {
+ this.backend = backend;
+ }
+
+ @Test
+ public void testKeptMethod() throws Exception {
+ MethodReference mainMethod = methodFromMethod(CLASS.getDeclaredMethod("main", String[].class));
+ MethodReference fooMethod = methodFromMethod(INNER.getDeclaredMethod("foo"));
+ MethodReference barMethod = methodFromMethod(INNER.getDeclaredMethod("bar"));
+ MethodReference bazMethod = methodFromMethod(INNER.getDeclaredMethod("baz"));
+
+ if (backend == Backend.CF) {
+ testForJvm().addProgramClasses(CLASSES).run(CLASS).assertSuccessWithOutput(EXPECTED);
+ }
+
+ Origin ruleOrigin = Origin.unknown();
+
+ String keepAnnotatedMethodsRule = "-keepclassmembers class * { @com.android.tools.r8.Keep *; }";
+ String keepClassesOfAnnotatedMethodsRule =
+ "-keep,allowobfuscation class * { <init>(); @com.android.tools.r8.Keep *; }";
+ GraphInspector inspector =
+ testForR8(backend)
+ .enableGraphInspector()
+ .enableInliningAnnotations()
+ .addProgramClasses(CLASSES)
+ .addKeepMainRule(CLASS)
+ .addKeepRules(keepAnnotatedMethodsRule, keepClassesOfAnnotatedMethodsRule)
+ .run(CLASS)
+ .assertSuccessWithOutput(EXPECTED)
+ .graphInspector();
+
+ assertEquals(3, inspector.getRoots().size());
+ QueryNode keepMain = inspector.rule(ruleOrigin, 1, 1).assertRoot();
+ QueryNode keepAnnotatedMethods = inspector.rule(keepAnnotatedMethodsRule).assertRoot();
+ QueryNode keepClassesOfAnnotatedMethods =
+ inspector.rule(keepClassesOfAnnotatedMethodsRule).assertRoot();
+
+ inspector.method(mainMethod).assertNotRenamed().assertKeptBy(keepMain);
+
+ // Check that Inner is allowed and is actually renamed.
+ inspector.clazz(Reference.classFromClass(INNER)).assertRenamed();
+
+ // Check bar is called from foo.
+ inspector.method(barMethod).assertRenamed().assertInvokedFrom(fooMethod);
+
+ // Check foo *is not* called from main (it is reflectively accessed) and check that it is kept.
+ inspector
+ .method(fooMethod)
+ .assertNotRenamed()
+ .assertNotInvokedFrom(mainMethod)
+ // TODO(b/122297131): keepAnnotatedMethods should also be keeping foo alive!
+ .assertKeptBy(keepClassesOfAnnotatedMethods);
+
+ // Check baz is removed.
+ inspector.method(bazMethod).assertAbsent();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
index ffc64cf..924ec2d 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
@@ -4,6 +4,8 @@
package com.android.tools.r8.utils.codeinspector;
+import static org.junit.Assert.assertTrue;
+
import com.android.tools.r8.cf.code.CfArithmeticBinop;
import com.android.tools.r8.cf.code.CfCheckCast;
import com.android.tools.r8.cf.code.CfConstClass;
@@ -20,6 +22,7 @@
import com.android.tools.r8.cf.code.CfInvokeDynamic;
import com.android.tools.r8.cf.code.CfLabel;
import com.android.tools.r8.cf.code.CfLoad;
+import com.android.tools.r8.cf.code.CfMonitor;
import com.android.tools.r8.cf.code.CfNew;
import com.android.tools.r8.cf.code.CfNop;
import com.android.tools.r8.cf.code.CfPosition;
@@ -28,8 +31,10 @@
import com.android.tools.r8.cf.code.CfStackInstruction;
import com.android.tools.r8.cf.code.CfSwitch;
import com.android.tools.r8.cf.code.CfThrow;
+import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.ir.code.Monitor.Type;
import com.android.tools.r8.ir.code.ValueType;
import org.objectweb.asm.Opcodes;
@@ -254,6 +259,36 @@
}
@Override
+ public boolean isMonitorEnter() {
+ if (!(instruction instanceof CfMonitor)) {
+ return false;
+ }
+ CfMonitor monitor = (CfMonitor) instruction;
+ return monitor.getType() == Type.ENTER;
+ }
+
+ @Override
+ public boolean isMonitorExit() {
+ if (!(instruction instanceof CfMonitor)) {
+ return false;
+ }
+ CfMonitor monitor = (CfMonitor) instruction;
+ return monitor.getType() == Type.EXIT;
+ }
+
+ @Override
+ public int size() {
+ // TODO(b/122302789): CfInstruction#getSize()
+ throw new UnsupportedOperationException("CfInstruction doesn't have size yet.");
+ }
+
+ @Override
+ public InstructionOffsetSubject getOffset(MethodSubject methodSubject) {
+ // TODO(b/122302789): CfInstruction#getOffset()
+ throw new UnsupportedOperationException("CfInstruction doesn't have offset yet.");
+ }
+
+ @Override
public boolean equals(Object other) {
return other instanceof CfInstructionSubject
&& instruction.equals(((CfInstructionSubject) other).instruction);
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CfTryCatchIterator.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CfTryCatchIterator.java
new file mode 100644
index 0000000..965bffc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CfTryCatchIterator.java
@@ -0,0 +1,35 @@
+// Copyright (c) 2019, 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.utils.codeinspector;
+
+import com.android.tools.r8.cf.code.CfTryCatch;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.Code;
+import java.util.Iterator;
+
+class CfTryCatchIterator implements TryCatchIterator {
+ private final CodeInspector codeInspector;
+ private final CfCode cfCode;
+ private final Iterator<CfTryCatch> iterator;
+
+ CfTryCatchIterator(CodeInspector codeInspector, MethodSubject methodSubject) {
+ this.codeInspector = codeInspector;
+ assert methodSubject.isPresent();
+ Code code = methodSubject.getMethod().getCode();
+ assert code != null && code.isCfCode();
+ cfCode = code.asCfCode();
+ iterator = cfCode.getTryCatchRanges().iterator();
+ }
+
+ @Override
+ public boolean hasNext() {
+ return iterator.hasNext();
+ }
+
+ @Override
+ public TryCatchSubject next() {
+ return codeInspector.createTryCatchSubject(cfCode, iterator.next());
+ }
+
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CfTryCatchSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CfTryCatchSubject.java
new file mode 100644
index 0000000..2d7aec8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CfTryCatchSubject.java
@@ -0,0 +1,64 @@
+// Copyright (c) 2019, 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.utils.codeinspector;
+
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfTryCatch;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+
+class CfTryCatchSubject implements TryCatchSubject {
+ private final CfCode cfCode;
+ private final CfTryCatch tryCatch;
+
+ CfTryCatchSubject(CfCode cfCode, CfTryCatch tryCatch) {
+ this.cfCode = cfCode;
+ this.tryCatch = tryCatch;
+ }
+
+ @Override
+ public RangeSubject getRange() {
+ int index = 0;
+ int startIndex = -1;
+ int endIndex = -1;
+ for (CfInstruction instruction : cfCode.instructions) {
+ if (startIndex < 0 && instruction.equals(tryCatch.start)) {
+ startIndex = index;
+ }
+ if (endIndex < 0 && instruction.equals(tryCatch.end)) {
+ // To be inclusive, increase the index so that the range includes the current instruction.
+ assertNotEquals(-1, startIndex);
+ index++;
+ endIndex = index;
+ break;
+ }
+ index++;
+ }
+ assertNotEquals(-1, startIndex);
+ assertNotEquals(-1, endIndex);
+ assertTrue(startIndex < endIndex);
+ return new RangeSubject(startIndex, endIndex);
+ }
+
+ @Override
+ public boolean isCatching(String exceptionType) {
+ for (DexType guardType : tryCatch.guards) {
+ if (guardType.toString().equals(exceptionType)
+ || guardType.toDescriptorString().equals(exceptionType)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean hasCatchAll() {
+ return isCatching(DexItemFactory.catchAllType.toDescriptorString());
+ }
+
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
index 8c59c98..3f53530 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
@@ -7,15 +7,20 @@
import com.android.tools.r8.StringResource;
import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfTryCatch;
import com.android.tools.r8.code.Instruction;
import com.android.tools.r8.dex.ApplicationReader;
import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.Code;
import com.android.tools.r8.graph.DexAnnotation;
import com.android.tools.r8.graph.DexAnnotationElement;
import com.android.tools.r8.graph.DexAnnotationSet;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.graph.DexCode.Try;
+import com.android.tools.r8.graph.DexCode.TryHandler;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DexValue;
@@ -346,6 +351,26 @@
}
}
+ TryCatchSubject createTryCatchSubject(DexCode code, Try tryElement, TryHandler tryHandler) {
+ return new DexTryCatchSubject(code, tryElement, tryHandler);
+ }
+
+ TryCatchSubject createTryCatchSubject(CfCode code, CfTryCatch tryCatch) {
+ return new CfTryCatchSubject(code, tryCatch);
+ }
+
+ TryCatchIterator createTryCatchIterator(MethodSubject method) {
+ Code code = method.getMethod().getCode();
+ assert code != null;
+ if (code.isDexCode()) {
+ return new DexTryCatchIterator(this, method);
+ } else if (code.isCfCode()) {
+ return new CfTryCatchIterator(this, method);
+ } else {
+ throw new Unimplemented("TryCatchIterator is implemented for DexCode and CfCode only.");
+ }
+ }
+
// Build the generic signature using the current mapping if any.
class GenericSignatureGenerator implements GenericSignatureAction<String> {
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
index 9e3f563..6d463da 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
@@ -55,6 +55,8 @@
import com.android.tools.r8.code.IputObject;
import com.android.tools.r8.code.IputShort;
import com.android.tools.r8.code.IputWide;
+import com.android.tools.r8.code.MonitorEnter;
+import com.android.tools.r8.code.MonitorExit;
import com.android.tools.r8.code.MulDouble;
import com.android.tools.r8.code.MulDouble2Addr;
import com.android.tools.r8.code.MulFloat;
@@ -352,6 +354,26 @@
}
@Override
+ public boolean isMonitorEnter() {
+ return instruction instanceof MonitorEnter;
+ }
+
+ @Override
+ public boolean isMonitorExit() {
+ return instruction instanceof MonitorExit;
+ }
+
+ @Override
+ public int size() {
+ return instruction.getSize();
+ }
+
+ @Override
+ public InstructionOffsetSubject getOffset(MethodSubject methodSubject) {
+ return new InstructionOffsetSubject(instruction.getOffset());
+ }
+
+ @Override
public boolean equals(Object other) {
return other instanceof DexInstructionSubject
&& instruction.equals(((DexInstructionSubject) other).instruction);
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/DexTryCatchIterator.java b/src/test/java/com/android/tools/r8/utils/codeinspector/DexTryCatchIterator.java
new file mode 100644
index 0000000..dff1cbe
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/DexTryCatchIterator.java
@@ -0,0 +1,38 @@
+// Copyright (c) 2019, 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.utils.codeinspector;
+
+import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DexCode;
+import java.util.NoSuchElementException;
+
+class DexTryCatchIterator implements TryCatchIterator {
+ private final CodeInspector codeInspector;
+ private final DexCode code;
+ private int index;
+
+ DexTryCatchIterator(CodeInspector codeInspector, MethodSubject methodSubject) {
+ this.codeInspector = codeInspector;
+ assert methodSubject.isPresent();
+ Code code = methodSubject.getMethod().getCode();
+ assert code != null && code.isDexCode();
+ this.code = code.asDexCode();
+ this.index = 0;
+ }
+
+ @Override
+ public boolean hasNext() {
+ return index < code.tries.length;
+ }
+
+ @Override
+ public TryCatchSubject next() {
+ if (index == code.tries.length) {
+ throw new NoSuchElementException();
+ }
+ int current = index++;
+ return codeInspector.createTryCatchSubject(
+ code, code.tries[current], code.handlers[code.tries[current].handlerIndex]);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/DexTryCatchSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/DexTryCatchSubject.java
new file mode 100644
index 0000000..ed46590
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/DexTryCatchSubject.java
@@ -0,0 +1,47 @@
+// Copyright (c) 2019, 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.utils.codeinspector;
+
+import static com.android.tools.r8.graph.DexCode.TryHandler.NO_HANDLER;
+
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.graph.DexCode.Try;
+import com.android.tools.r8.graph.DexCode.TryHandler;
+import com.android.tools.r8.graph.DexCode.TryHandler.TypeAddrPair;
+
+class DexTryCatchSubject implements TryCatchSubject {
+ private final DexCode dexCode;
+ private final Try tryElement;
+ private final TryHandler tryHandler;
+
+ DexTryCatchSubject(DexCode dexCode, Try tryElement, TryHandler tryHandler) {
+ this.dexCode = dexCode;
+ this.tryElement = tryElement;
+ this.tryHandler = tryHandler;
+ }
+
+ @Override
+ public RangeSubject getRange() {
+ return new RangeSubject(
+ tryElement.startAddress,
+ tryElement.startAddress + tryElement.instructionCount - 1);
+ }
+
+ @Override
+ public boolean isCatching(String exceptionType) {
+ for (TypeAddrPair pair : tryHandler.pairs) {
+ if (pair.type.toString().equals(exceptionType)
+ || pair.type.toDescriptorString().equals(exceptionType)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean hasCatchAll() {
+ return tryHandler.catchAllAddr != NO_HANDLER;
+ }
+
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FilteredInstructionIterator.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FilteredInstructionIterator.java
index 870953e..598f0be 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FilteredInstructionIterator.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FilteredInstructionIterator.java
@@ -18,7 +18,6 @@
CodeInspector codeInspector, MethodSubject method, Predicate<InstructionSubject> predicate) {
this.iterator = codeInspector.createInstructionIterator(method);
this.predicate = predicate;
- hasNext();
}
@Override
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FilteredTryCatchIterator.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FilteredTryCatchIterator.java
new file mode 100644
index 0000000..14fcb21
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FilteredTryCatchIterator.java
@@ -0,0 +1,50 @@
+// Copyright (c) 2019, 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.utils.codeinspector;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+import java.util.function.Predicate;
+
+class FilteredTryCatchIterator<T extends TryCatchSubject> implements Iterator<T> {
+
+ private final TryCatchIterator iterator;
+ private final Predicate<TryCatchSubject> predicate;
+ private TryCatchSubject pendingNext = null;
+
+ FilteredTryCatchIterator(
+ CodeInspector codeInspector,
+ MethodSubject methodSubject,
+ Predicate<TryCatchSubject> predicate) {
+ this.iterator = codeInspector.createTryCatchIterator(methodSubject);
+ this.predicate = predicate;
+ }
+
+ @Override
+ public boolean hasNext() {
+ if (pendingNext == null) {
+ while (iterator.hasNext()) {
+ pendingNext = iterator.next();
+ if (predicate.test(pendingNext)) {
+ break;
+ }
+ pendingNext = null;
+ }
+ }
+ return pendingNext != null;
+ }
+
+ @Override
+ public T next() {
+ hasNext();
+ if (pendingNext == null) {
+ throw new NoSuchElementException();
+ }
+ // We cannot tell if the provided predicate will only match instruction subjects of type T.
+ @SuppressWarnings("unchecked")
+ T result = (T) pendingNext;
+ pendingNext = null;
+ return result;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
index 6ce0496..2b4825e 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
@@ -4,6 +4,9 @@
package com.android.tools.r8.utils.codeinspector;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
import com.android.tools.r8.cf.code.CfInstruction;
import com.android.tools.r8.cf.code.CfPosition;
import com.android.tools.r8.code.Instruction;
@@ -165,6 +168,17 @@
}
@Override
+ public Iterator<TryCatchSubject> iterateTryCatches() {
+ return codeInspector.createTryCatchIterator(this);
+ }
+
+ @Override
+ public <T extends TryCatchSubject> Iterator<T> iterateTryCatches(
+ Predicate<TryCatchSubject> filter) {
+ return new FilteredTryCatchIterator<>(codeInspector, this, filter);
+ }
+
+ @Override
public boolean hasLocalVariableTable() {
Code code = getMethod().getCode();
if (code.isDexCode()) {
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionOffsetSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionOffsetSubject.java
new file mode 100644
index 0000000..232884b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionOffsetSubject.java
@@ -0,0 +1,15 @@
+// Copyright (c) 2019, 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.utils.codeinspector;
+
+public class InstructionOffsetSubject {
+ // For Dex backend, this is bytecode offset.
+ // For CF backend, this is the index in the list of instruction.
+ final int offset;
+
+ InstructionOffsetSubject(int offset) {
+ this.offset = offset;
+ }
+
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
index a8a3225..6abeacf 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
@@ -79,4 +79,12 @@
boolean isSparseSwitch();
boolean isMultiplication();
+
+ boolean isMonitorEnter();
+
+ boolean isMonitorExit();
+
+ int size();
+
+ InstructionOffsetSubject getOffset(MethodSubject methodSubject);
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
index adcbf31..9cecb1d 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
@@ -35,6 +35,15 @@
return null;
}
+ public Iterator<TryCatchSubject> iterateTryCatches() {
+ return null;
+ }
+
+ public <T extends TryCatchSubject> Iterator<T> iterateTryCatches(
+ Predicate<TryCatchSubject> filter) {
+ return null;
+ }
+
public boolean hasLineNumberTable() {
return getLineNumberTable() != null;
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/RangeSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/RangeSubject.java
new file mode 100644
index 0000000..12e3a4e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/RangeSubject.java
@@ -0,0 +1,21 @@
+// Copyright (c) 2019, 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.utils.codeinspector;
+
+public class RangeSubject {
+ // For Dex backend, these are bytecode offset.
+ // For CF backend, these are indices in the list of instructions.
+ final int start;
+ final int end;
+
+ RangeSubject(int start, int end) {
+ this.start = start;
+ this.end = end;
+ }
+
+ // Returns true if the given instruction is within the current range.
+ public boolean includes(InstructionOffsetSubject offsetSubject) {
+ return this.start <= offsetSubject.offset && offsetSubject.offset <= this.end;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/TryCatchIterator.java b/src/test/java/com/android/tools/r8/utils/codeinspector/TryCatchIterator.java
new file mode 100644
index 0000000..c37839d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/TryCatchIterator.java
@@ -0,0 +1,8 @@
+// Copyright (c) 2019, 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.utils.codeinspector;
+
+import java.util.Iterator;
+
+interface TryCatchIterator extends Iterator<TryCatchSubject> {}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/TryCatchSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/TryCatchSubject.java
new file mode 100644
index 0000000..838f7a8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/TryCatchSubject.java
@@ -0,0 +1,10 @@
+// Copyright (c) 2019, 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.utils.codeinspector;
+
+public interface TryCatchSubject {
+ RangeSubject getRange();
+ boolean isCatching(String exceptionType);
+ boolean hasCatchAll();
+}
diff --git a/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java b/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java
index b8ce79a..107978b 100644
--- a/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java
@@ -108,14 +108,21 @@
public QueryNode assertInvokedFrom(MethodReference method) {
assertTrue(
- errorMessage("invokation from " + method.toString(), "none"), isInvokedFrom(method));
+ errorMessage("invocation from " + method.toString(), "none"), isInvokedFrom(method));
+ return this;
+ }
+
+ public QueryNode assertNotInvokedFrom(MethodReference method) {
+ assertTrue(
+ errorMessage("no invocation from " + method.toString(), "invoke"),
+ !isInvokedFrom(method));
return this;
}
public QueryNode assertKeptBy(QueryNode node) {
assertTrue("Invalid call to assertKeptBy with: " + node.getNodeDescription(),
node.isPresent());
- assertTrue(errorMessage("kept by " + getNodeDescription(), "none"), isKeptBy(node));
+ assertTrue(errorMessage("kept by " + node.getNodeDescription(), "none"), isKeptBy(node));
return this;
}
}
@@ -287,6 +294,19 @@
return Collections.unmodifiableSet(roots);
}
+ public QueryNode rule(String ruleContent) {
+ KeepRuleGraphNode found = null;
+ for (KeepRuleGraphNode rule : rules) {
+ if (rule.getContent().equals(ruleContent)) {
+ if (found != null) {
+ fail("Found two matching rules matching " + ruleContent + ": " + found + " and " + rule);
+ }
+ found = rule;
+ }
+ }
+ return getQueryNode(found, ruleContent);
+ }
+
public QueryNode rule(Origin origin, int line, int column) {
String ruleReferenceString = getReferenceStringForRule(origin, line, column);
KeepRuleGraphNode found = null;
@@ -317,6 +337,10 @@
return "rule@" + origin + ":" + new TextPosition(0, line, column);
}
+ public QueryNode clazz(ClassReference clazz) {
+ return getQueryNode(classes.get(clazz), clazz.toString());
+ }
+
public QueryNode method(MethodReference method) {
return getQueryNode(methods.get(method), method.toString());
}