Merge "Revert "Change location of kotlin r8 test resources for relocating""
diff --git a/src/main/java/com/android/tools/r8/PrintUses.java b/src/main/java/com/android/tools/r8/PrintUses.java
index 035847d..b12e26f 100644
--- a/src/main/java/com/android/tools/r8/PrintUses.java
+++ b/src/main/java/com/android/tools/r8/PrintUses.java
@@ -115,7 +115,12 @@
@Override
public boolean registerInvokeSuper(DexMethod method) {
- addMethod(method);
+ DexEncodedMethod superTarget = appInfo.lookupSuperTarget(method, method.holder);
+ if (superTarget != null) {
+ addMethod(superTarget.method);
+ } else {
+ addMethod(method);
+ }
return false;
}
@@ -201,7 +206,7 @@
private void registerMethod(DexEncodedMethod method) {
DexEncodedMethod superTarget = appInfo.lookupSuperTarget(method.method, method.method.holder);
if (superTarget != null) {
- registerInvokeSuper(superTarget.method);
+ addMethod(superTarget.method);
}
for (DexType type : method.method.proto.parameters.values) {
registerTypeReference(type);
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index c1664a6..3cf4664 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -418,7 +418,8 @@
if (options.enableArgumentRemoval) {
if (options.enableUnusedArgumentRemoval) {
timing.begin("UnusedArgumentRemoval");
- appView.setGraphLense(new UnusedArgumentsCollector(appViewWithLiveness).run());
+ appView.setGraphLense(
+ new UnusedArgumentsCollector(appViewWithLiveness).run(executorService));
application = application.asDirect().rewrittenWithLense(appView.graphLense());
timing.end();
appViewWithLiveness.setAppInfo(
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/optimize/UninstantiatedTypeOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
index 3b858b0..74e9c6a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
@@ -257,7 +257,8 @@
private RewrittenPrototypeDescription getPrototypeChanges(
DexEncodedMethod encodedMethod, Strategy strategy) {
- if (ArgumentRemovalUtils.isPinned(encodedMethod, appView)) {
+ if (ArgumentRemovalUtils.isPinned(encodedMethod, appView)
+ || appView.appInfo().keepConstantArguments.contains(encodedMethod.method)) {
return RewrittenPrototypeDescription.none();
}
return new RewrittenPrototypeDescription(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
index e06cdbef..47b9289 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
@@ -20,11 +20,13 @@
import com.android.tools.r8.graph.GraphLense.RewrittenPrototypeDescription.RemovedArgumentsInfo;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.android.tools.r8.utils.MethodSignatureEquivalence;
+import com.android.tools.r8.utils.ThreadUtils;
import com.google.common.base.Equivalence.Wrapper;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Streams;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.HashSet;
@@ -32,6 +34,8 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
public class UnusedArgumentsCollector {
@@ -80,9 +84,12 @@
this.appView = appView;
}
- public GraphLense run() {
- // TODO(65810338): Do this is parallel.
- appView.appInfo().classes().forEach(this::processClass);
+ public GraphLense run(ExecutorService executorService) throws ExecutionException {
+ ThreadUtils.awaitFutures(
+ Streams.stream(appView.appInfo().classes())
+ .map(this::runnableForClass)
+ .map(executorService::submit)
+ .iterator());
if (!methodMapping.isEmpty()) {
return new UnusedArgumentsGraphLense(
@@ -104,14 +111,22 @@
private final MethodSignatureEquivalence equivalence = MethodSignatureEquivalence.get();
private final Set<Wrapper<DexMethod>> usedSignatures = new HashSet<>();
- private DexProto protoWithRemovedArguments(DexMethod method, RemovedArgumentsInfo unused) {
- DexType[] parameters =
- new DexType[method.proto.parameters.size() - unused.numberOfRemovedArguments()];
- if (parameters.length > 0) {
+ private DexProto protoWithRemovedArguments(
+ DexEncodedMethod encodedMethod, RemovedArgumentsInfo unused) {
+ DexMethod method = encodedMethod.method;
+
+ int firstArgumentIndex = encodedMethod.isStatic() ? 0 : 1;
+ int numberOfParameters = method.proto.parameters.size() - unused.numberOfRemovedArguments();
+ if (!encodedMethod.isStatic() && unused.isArgumentRemoved(0)) {
+ numberOfParameters++;
+ }
+
+ DexType[] parameters = new DexType[numberOfParameters];
+ if (numberOfParameters > 0) {
int newIndex = 0;
- for (int j = 0; j < method.proto.parameters.size(); j++) {
- if (!unused.isArgumentRemoved(j)) {
- parameters[newIndex++] = method.proto.parameters.values[j];
+ for (int oldIndex = 0; oldIndex < method.proto.parameters.size(); oldIndex++) {
+ if (!unused.isArgumentRemoved(oldIndex + firstArgumentIndex)) {
+ parameters[newIndex++] = method.proto.parameters.values[oldIndex];
}
}
assert newIndex == parameters.length;
@@ -128,19 +143,26 @@
}
DexEncodedMethod removeArguments(DexEncodedMethod method, RemovedArgumentsInfo unused) {
+ if (unused == null) {
+ return null;
+ }
+
boolean removed = usedSignatures.remove(equivalence.wrap(method.method));
assert removed;
- DexProto newProto = protoWithRemovedArguments(method.method, unused);
+ DexProto newProto = protoWithRemovedArguments(method, unused);
DexMethod newSignature;
int count = 0;
DexString newName = null;
do {
- newName =
- newName == null
- ? method.method.name
- : appView
- .dexItemFactory()
- .createString(method.method.name.toSourceString() + count);
+ if (newName == null) {
+ newName = method.method.name;
+ } else if (method.method.name != appView.dexItemFactory().initMethodName) {
+ newName =
+ appView.dexItemFactory().createString(method.method.name.toSourceString() + count);
+ } else {
+ // Constructors must be named `<init>`.
+ return null;
+ }
newSignature =
appView.dexItemFactory().createMethod(method.method.holder, newProto, newName);
count++;
@@ -150,6 +172,10 @@
}
}
+ private Runnable runnableForClass(DexProgramClass clazz) {
+ return () -> this.processClass(clazz);
+ }
+
private void processClass(DexProgramClass clazz) {
UsedSignatures signatures = new UsedSignatures();
for (DexEncodedMethod method : clazz.methods()) {
@@ -158,17 +184,20 @@
for (int i = 0; i < clazz.directMethods().length; i++) {
DexEncodedMethod method = clazz.directMethods()[i];
RemovedArgumentsInfo unused = collectUnusedArguments(method);
- if (unused != null) {
- DexEncodedMethod newMethod = signatures.removeArguments(method, unused);
+ DexEncodedMethod newMethod = signatures.removeArguments(method, unused);
+ if (newMethod != null) {
clazz.directMethods()[i] = newMethod;
- methodMapping.put(method.method, newMethod.method);
- removedArguments.put(newMethod.method, unused);
+ synchronized (this) {
+ methodMapping.put(method.method, newMethod.method);
+ removedArguments.put(newMethod.method, unused);
+ }
}
}
}
private RemovedArgumentsInfo collectUnusedArguments(DexEncodedMethod method) {
- if (ArgumentRemovalUtils.isPinned(method, appView)) {
+ if (ArgumentRemovalUtils.isPinned(method, appView)
+ || appView.appInfo().keepUnusedArguments.contains(method.method)) {
return null;
}
// Only process JAR code.
@@ -178,21 +207,25 @@
assert method.getCode().getOwner() == method;
int argumentCount =
method.method.proto.parameters.size() + (method.accessFlags.isStatic() ? 0 : 1);
- // TODO(65810338): Implement for private and virtual as well.
- if (!method.accessFlags.isStatic()) {
- return null;
- }
- CollectUsedArguments collector = new CollectUsedArguments();
- method.getCode().registerArgumentReferences(collector);
- BitSet used = collector.getUsedArguments();
- if (used.cardinality() < argumentCount) {
- List<RemovedArgumentInfo> unused = new ArrayList<>();
- for (int i = 0; i < argumentCount; i++) {
- if (!used.get(i)) {
- unused.add(RemovedArgumentInfo.builder().setArgumentIndex(i).build());
- }
+ // TODO(65810338): Implement for virtual methods as well.
+ if (method.accessFlags.isPrivate() || method.accessFlags.isStatic()) {
+ CollectUsedArguments collector = new CollectUsedArguments();
+ if (!method.accessFlags.isStatic()) {
+ // TODO(65810338): The receiver cannot be removed without transforming the method to being
+ // static.
+ collector.register(0);
}
- return new RemovedArgumentsInfo(unused);
+ method.getCode().registerArgumentReferences(collector);
+ BitSet used = collector.getUsedArguments();
+ if (used.cardinality() < argumentCount) {
+ List<RemovedArgumentInfo> unused = new ArrayList<>();
+ for (int i = 0; i < argumentCount; i++) {
+ if (!used.get(i)) {
+ unused.add(RemovedArgumentInfo.builder().setArgumentIndex(i).build());
+ }
+ }
+ return new RemovedArgumentsInfo(unused);
+ }
}
return null;
}
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/ConstantArgumentRule.java b/src/main/java/com/android/tools/r8/shaking/ConstantArgumentRule.java
new file mode 100644
index 0000000..6d6f424
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/ConstantArgumentRule.java
@@ -0,0 +1,81 @@
+// 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;
+
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+import java.util.List;
+
+public class ConstantArgumentRule extends ProguardConfigurationRule {
+
+ public static class Builder
+ extends ProguardConfigurationRule.Builder<ConstantArgumentRule, Builder> {
+
+ private Builder() {
+ super();
+ }
+
+ @Override
+ public Builder self() {
+ return this;
+ }
+
+ @Override
+ public ConstantArgumentRule build() {
+ return new ConstantArgumentRule(
+ origin,
+ getPosition(),
+ source,
+ classAnnotation,
+ classAccessFlags,
+ negatedClassAccessFlags,
+ classTypeNegated,
+ classType,
+ classNames,
+ inheritanceAnnotation,
+ inheritanceClassName,
+ inheritanceIsExtends,
+ memberRules);
+ }
+ }
+
+ private ConstantArgumentRule(
+ Origin origin,
+ Position position,
+ String source,
+ ProguardTypeMatcher classAnnotation,
+ ProguardAccessFlags classAccessFlags,
+ ProguardAccessFlags negatedClassAccessFlags,
+ boolean classTypeNegated,
+ ProguardClassType classType,
+ ProguardClassNameList classNames,
+ ProguardTypeMatcher inheritanceAnnotation,
+ ProguardTypeMatcher inheritanceClassName,
+ boolean inheritanceIsExtends,
+ List<ProguardMemberRule> memberRules) {
+ super(
+ origin,
+ position,
+ source,
+ classAnnotation,
+ classAccessFlags,
+ negatedClassAccessFlags,
+ classTypeNegated,
+ classType,
+ classNames,
+ inheritanceAnnotation,
+ inheritanceClassName,
+ inheritanceIsExtends,
+ memberRules);
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ @Override
+ String typeString() {
+ return "keepconstantarguments";
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 95f077d..279d381 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -1941,6 +1941,10 @@
* All methods that *must* never be inlined due to a configuration directive (testing only).
*/
public final Set<DexMethod> neverInline;
+ /** All methods that may not have any parameters with a constant value removed. */
+ public final Set<DexMethod> keepConstantArguments;
+ /** All methods that may not have any unused arguments removed. */
+ public final Set<DexMethod> keepUnusedArguments;
/**
* All types that *must* never be inlined due to a configuration directive (testing only).
*/
@@ -2015,6 +2019,8 @@
this.alwaysInline = enqueuer.rootSet.alwaysInline;
this.forceInline = enqueuer.rootSet.forceInline;
this.neverInline = enqueuer.rootSet.neverInline;
+ this.keepConstantArguments = enqueuer.rootSet.keepConstantArguments;
+ this.keepUnusedArguments = enqueuer.rootSet.keepUnusedArguments;
this.neverClassInline = enqueuer.rootSet.neverClassInline;
this.neverMerge = enqueuer.rootSet.neverMerge;
this.identifierNameStrings = joinIdentifierNameStrings(
@@ -2062,6 +2068,8 @@
this.alwaysInline = previous.alwaysInline;
this.forceInline = previous.forceInline;
this.neverInline = previous.neverInline;
+ this.keepConstantArguments = previous.keepConstantArguments;
+ this.keepUnusedArguments = previous.keepUnusedArguments;
this.neverClassInline = previous.neverClassInline;
this.neverMerge = previous.neverMerge;
this.identifierNameStrings = previous.identifierNameStrings;
@@ -2123,6 +2131,10 @@
this.alwaysInline = previous.alwaysInline;
this.forceInline = lense.rewriteMethodsWithRenamedSignature(previous.forceInline);
this.neverInline = lense.rewriteMethodsWithRenamedSignature(previous.neverInline);
+ this.keepConstantArguments =
+ lense.rewriteMethodsWithRenamedSignature(previous.keepConstantArguments);
+ this.keepUnusedArguments =
+ lense.rewriteMethodsWithRenamedSignature(previous.keepUnusedArguments);
assert lense.assertDefinitionsNotModified(
previous.neverMerge.stream()
.map(this::definitionFor)
@@ -2178,6 +2190,8 @@
this.alwaysInline = previous.alwaysInline;
this.forceInline = previous.forceInline;
this.neverInline = previous.neverInline;
+ this.keepConstantArguments = previous.keepConstantArguments;
+ this.keepUnusedArguments = previous.keepUnusedArguments;
this.neverClassInline = previous.neverClassInline;
this.neverMerge = previous.neverMerge;
this.identifierNameStrings = previous.identifierNameStrings;
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index 8d2c1b9..47d3743 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -242,6 +242,12 @@
} else if (acceptString("keepdirectories")) {
configurationBuilder.enableKeepDirectories();
parsePathFilter(configurationBuilder::addKeepDirectories);
+ } else if (allowTestOptions && acceptString("keepconstantarguments")) {
+ ConstantArgumentRule rule = parseConstantArgumentRule(optionStart);
+ configurationBuilder.addRule(rule);
+ } else if (allowTestOptions && acceptString("keepunusedarguments")) {
+ UnusedArgumentRule rule = parseUnusedArgumentRule(optionStart);
+ configurationBuilder.addRule(rule);
} else if (acceptString("keep")) {
ProguardKeepRule rule = parseKeepRule(optionStart);
configurationBuilder.addRule(rule);
@@ -683,6 +689,28 @@
"Expecting '-keep' option after '-if' option.", origin, getPosition(optionStart)));
}
+ private ConstantArgumentRule parseConstantArgumentRule(Position start)
+ throws ProguardRuleParserException {
+ ConstantArgumentRule.Builder keepRuleBuilder =
+ ConstantArgumentRule.builder().setOrigin(origin).setStart(start);
+ parseClassSpec(keepRuleBuilder, false);
+ Position end = getPosition();
+ keepRuleBuilder.setSource(getSourceSnippet(contents, start, end));
+ keepRuleBuilder.setEnd(end);
+ return keepRuleBuilder.build();
+ }
+
+ private UnusedArgumentRule parseUnusedArgumentRule(Position start)
+ throws ProguardRuleParserException {
+ UnusedArgumentRule.Builder keepRuleBuilder =
+ UnusedArgumentRule.builder().setOrigin(origin).setStart(start);
+ parseClassSpec(keepRuleBuilder, false);
+ Position end = getPosition();
+ keepRuleBuilder.setSource(getSourceSnippet(contents, start, end));
+ keepRuleBuilder.setEnd(end);
+ return keepRuleBuilder.build();
+ }
+
void verifyAndLinkBackReferences(Iterable<ProguardWildcard> wildcards) {
List<Pattern> patterns = new ArrayList<>();
boolean backReferenceStarted = false;
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/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index 69d2ee1..2f7f54a 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -72,6 +72,8 @@
private final Set<DexMethod> alwaysInline = Sets.newIdentityHashSet();
private final Set<DexMethod> forceInline = Sets.newIdentityHashSet();
private final Set<DexMethod> neverInline = Sets.newIdentityHashSet();
+ private final Set<DexMethod> keepParametersWithConstantValue = Sets.newIdentityHashSet();
+ private final Set<DexMethod> keepUnusedArguments = Sets.newIdentityHashSet();
private final Set<DexType> neverClassInline = Sets.newIdentityHashSet();
private final Set<DexType> neverMerge = Sets.newIdentityHashSet();
private final Map<DexDefinition, Map<DexDefinition, ProguardKeepRule>> dependentNoShrinking =
@@ -185,7 +187,9 @@
if (allRulesSatisfied(memberKeepRules, clazz)) {
markClass(clazz, rule);
}
- } else if (rule instanceof InlineRule) {
+ } else if (rule instanceof InlineRule
+ || rule instanceof ConstantArgumentRule
+ || rule instanceof UnusedArgumentRule) {
markMatchingMethods(clazz, memberKeepRules, rule, null);
} else if (rule instanceof ClassInlineRule) {
if (allRulesSatisfied(memberKeepRules, clazz)) {
@@ -261,6 +265,8 @@
alwaysInline,
forceInline,
neverInline,
+ keepParametersWithConstantValue,
+ keepUnusedArguments,
neverClassInline,
neverMerge,
noSideEffects,
@@ -951,6 +957,14 @@
} else if (item.isDexEncodedMethod()) {
identifierNameStrings.add(item.asDexEncodedMethod().method);
}
+ } else if (context instanceof ConstantArgumentRule) {
+ if (item.isDexEncodedMethod()) {
+ keepParametersWithConstantValue.add(item.asDexEncodedMethod().method);
+ }
+ } else if (context instanceof UnusedArgumentRule) {
+ if (item.isDexEncodedMethod()) {
+ keepUnusedArguments.add(item.asDexEncodedMethod().method);
+ }
}
}
@@ -965,6 +979,8 @@
public final Set<DexMethod> alwaysInline;
public final Set<DexMethod> forceInline;
public final Set<DexMethod> neverInline;
+ public final Set<DexMethod> keepConstantArguments;
+ public final Set<DexMethod> keepUnusedArguments;
public final Set<DexType> neverClassInline;
public final Set<DexType> neverMerge;
public final Map<DexDefinition, ProguardMemberRule> noSideEffects;
@@ -983,6 +999,8 @@
Set<DexMethod> alwaysInline,
Set<DexMethod> forceInline,
Set<DexMethod> neverInline,
+ Set<DexMethod> keepConstantArguments,
+ Set<DexMethod> keepUnusedArguments,
Set<DexType> neverClassInline,
Set<DexType> neverMerge,
Map<DexDefinition, ProguardMemberRule> noSideEffects,
@@ -999,6 +1017,8 @@
this.alwaysInline = Collections.unmodifiableSet(alwaysInline);
this.forceInline = Collections.unmodifiableSet(forceInline);
this.neverInline = neverInline;
+ this.keepConstantArguments = keepConstantArguments;
+ this.keepUnusedArguments = keepUnusedArguments;
this.neverClassInline = neverClassInline;
this.neverMerge = Collections.unmodifiableSet(neverMerge);
this.noSideEffects = Collections.unmodifiableMap(noSideEffects);
diff --git a/src/main/java/com/android/tools/r8/shaking/UnusedArgumentRule.java b/src/main/java/com/android/tools/r8/shaking/UnusedArgumentRule.java
new file mode 100644
index 0000000..1098bcd
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/UnusedArgumentRule.java
@@ -0,0 +1,81 @@
+// 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;
+
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+import java.util.List;
+
+public class UnusedArgumentRule extends ProguardConfigurationRule {
+
+ public static class Builder
+ extends ProguardConfigurationRule.Builder<UnusedArgumentRule, Builder> {
+
+ private Builder() {
+ super();
+ }
+
+ @Override
+ public Builder self() {
+ return this;
+ }
+
+ @Override
+ public UnusedArgumentRule build() {
+ return new UnusedArgumentRule(
+ origin,
+ getPosition(),
+ source,
+ classAnnotation,
+ classAccessFlags,
+ negatedClassAccessFlags,
+ classTypeNegated,
+ classType,
+ classNames,
+ inheritanceAnnotation,
+ inheritanceClassName,
+ inheritanceIsExtends,
+ memberRules);
+ }
+ }
+
+ private UnusedArgumentRule(
+ Origin origin,
+ Position position,
+ String source,
+ ProguardTypeMatcher classAnnotation,
+ ProguardAccessFlags classAccessFlags,
+ ProguardAccessFlags negatedClassAccessFlags,
+ boolean classTypeNegated,
+ ProguardClassType classType,
+ ProguardClassNameList classNames,
+ ProguardTypeMatcher inheritanceAnnotation,
+ ProguardTypeMatcher inheritanceClassName,
+ boolean inheritanceIsExtends,
+ List<ProguardMemberRule> memberRules) {
+ super(
+ origin,
+ position,
+ source,
+ classAnnotation,
+ classAccessFlags,
+ negatedClassAccessFlags,
+ classTypeNegated,
+ classType,
+ classNames,
+ inheritanceAnnotation,
+ inheritanceClassName,
+ inheritanceIsExtends,
+ memberRules);
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ @Override
+ String typeString() {
+ return "keepunusedarguments";
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
index 40143af..91bb786 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -146,12 +146,6 @@
ClassNameMapper.Builder classNameMapperBuilder = ClassNameMapper.builder();
// Collect which files contain which classes that need to have their line numbers optimized.
for (DexProgramClass clazz : application.classes()) {
-
- // TODO(tamaskenez) fix b/69356670 and remove the conditional skipping.
- if (!clazz.getSynthesizedFrom().isEmpty()) {
- continue;
- }
-
IdentityHashMap<DexString, List<DexEncodedMethod>> methodsByRenamedName =
groupMethodsByRenamedName(namingLens, clazz);
diff --git a/src/main/java/com/android/tools/r8/utils/ThreadUtils.java b/src/main/java/com/android/tools/r8/utils/ThreadUtils.java
index 1586cd4..bde7016 100644
--- a/src/main/java/com/android/tools/r8/utils/ThreadUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ThreadUtils.java
@@ -15,10 +15,14 @@
public static void awaitFutures(Iterable<? extends Future<?>> futures)
throws ExecutionException {
- Iterator<? extends Future<?>> it = futures.iterator();
+ awaitFutures(futures.iterator());
+ }
+
+ public static void awaitFutures(Iterator<? extends Future<?>> futureIterator)
+ throws ExecutionException {
try {
- while (it.hasNext()) {
- it.next().get();
+ while (futureIterator.hasNext()) {
+ futureIterator.next().get();
}
} catch (InterruptedException e) {
throw new RuntimeException("Interrupted while waiting for future.", e);
@@ -26,9 +30,9 @@
// In case we get interrupted or one of the threads throws an exception, still wait for all
// further work to make sure synchronization guarantees are met. Calling cancel unfortunately
// does not guarantee that the task at hand actually terminates before cancel returns.
- while (it.hasNext()) {
+ while (futureIterator.hasNext()) {
try {
- it.next().get();
+ futureIterator.next().get();
} catch (Throwable t) {
// Ignore any new Exception.
}
diff --git a/src/test/java/com/android/tools/r8/KeepConstantArguments.java b/src/test/java/com/android/tools/r8/KeepConstantArguments.java
new file mode 100644
index 0000000..87edb4f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/KeepConstantArguments.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;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+@Target({ElementType.METHOD})
+public @interface KeepConstantArguments {}
diff --git a/src/test/java/com/android/tools/r8/KeepUnusedArguments.java b/src/test/java/com/android/tools/r8/KeepUnusedArguments.java
new file mode 100644
index 0000000..9a5e3e9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/KeepUnusedArguments.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;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+@Target({ElementType.METHOD})
+public @interface KeepUnusedArguments {}
diff --git a/src/test/java/com/android/tools/r8/NeverInline.java b/src/test/java/com/android/tools/r8/NeverInline.java
index cf92f18f..082d446 100644
--- a/src/test/java/com/android/tools/r8/NeverInline.java
+++ b/src/test/java/com/android/tools/r8/NeverInline.java
@@ -3,5 +3,8 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8;
-public @interface NeverInline {
-}
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+@Target({ElementType.METHOD})
+public @interface NeverInline {}
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index 475f764..1c39971 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -39,7 +39,10 @@
private boolean enableInliningAnnotations = false;
private boolean enableClassInliningAnnotations = false;
private boolean enableMergeAnnotations = false;
+ private boolean enableConstantArgumentAnnotations = false;
+ private boolean enableUnusedArgumentAnnotations = false;
private CollectingGraphConsumer graphConsumer = null;
+ private List<String> keepRules = new ArrayList<>();
@Override
R8TestBuilder self() {
@@ -50,9 +53,16 @@
R8TestCompileResult internalCompile(
Builder builder, Consumer<InternalOptions> optionsConsumer, Supplier<AndroidApp> app)
throws CompilationFailedException {
- if (enableInliningAnnotations || enableClassInliningAnnotations || enableMergeAnnotations) {
+ if (enableInliningAnnotations
+ || enableClassInliningAnnotations
+ || enableMergeAnnotations
+ || enableConstantArgumentAnnotations
+ || enableUnusedArgumentAnnotations) {
ToolHelper.allowTestProguardOptions(builder);
}
+ if (!keepRules.isEmpty()) {
+ builder.addProguardConfiguration(keepRules, Origin.unknown());
+ }
StringBuilder proguardMapBuilder = new StringBuilder();
builder.setDisableTreeShaking(!enableTreeShaking);
builder.setDisableMinification(!enableMinification);
@@ -75,7 +85,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 +116,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 +126,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 +134,25 @@
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();
+ }
+
+ public R8TestBuilder enableConstantArgumentAnnotations() {
+ if (!enableConstantArgumentAnnotations) {
+ enableConstantArgumentAnnotations = true;
+ addInternalKeepRules(
+ "-keepconstantarguments class * { @com.android.tools.r8.KeepConstantArguments *; }");
+ }
+ return self();
+ }
+
+ public R8TestBuilder enableUnusedArgumentAnnotations() {
+ if (!enableUnusedArgumentAnnotations) {
+ enableUnusedArgumentAnnotations = true;
+ addInternalKeepRules(
+ "-keepunusedarguments class * { @com.android.tools.r8.KeepUnusedArguments *; }");
}
return self();
}
@@ -150,4 +179,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/TestCompileResult.java b/src/test/java/com/android/tools/r8/TestCompileResult.java
index c750ded..5a17455 100644
--- a/src/test/java/com/android/tools/r8/TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/TestCompileResult.java
@@ -24,6 +24,7 @@
import org.hamcrest.Matcher;
public abstract class TestCompileResult<RR extends TestRunResult> {
+
final TestState state;
public final AndroidApp app;
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/classinliner/ClassInliningOracleTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliningOracleTest.java
index 6982656..cb02879 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliningOracleTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliningOracleTest.java
@@ -7,6 +7,7 @@
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.junit.Assert.assertThat;
+import com.android.tools.r8.KeepUnusedArguments;
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NeverMerge;
@@ -26,6 +27,7 @@
.enableInliningAnnotations()
.enableClassInliningAnnotations()
.enableMergeAnnotations()
+ .enableUnusedArgumentAnnotations()
.compile()
.inspector();
assertThat(inspector.clazz(Builder.class), isPresent());
@@ -55,12 +57,10 @@
@NeverClassInline
static class Helper extends HelperBase {
+ @KeepUnusedArguments
@NeverInline
public void help(Builder builder) {
- // TODO(b/120959040): To avoid unused argument removal; should be replaced by a testing rule).
- if (builder != null) {
- super.hello();
- }
+ super.hello();
}
}
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/ir/optimize/unusedarguments/UnusedArgumentsObjectTest.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsObjectTest.java
index 10bf5df..2f7e437 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsObjectTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsObjectTest.java
@@ -6,6 +6,7 @@
import static org.junit.Assert.assertEquals;
+import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.google.common.collect.ImmutableList;
@@ -40,6 +41,7 @@
}
}
+ @NeverClassInline
static class TestClass {
@NeverInline
@@ -57,10 +59,29 @@
return a;
}
+ @NeverInline
+ private Object b(Object a) {
+ return a;
+ }
+
+ @NeverInline
+ private Object b(Object a, Object b) {
+ return a;
+ }
+
+ @NeverInline
+ private Object b(Object a, Object b, Object c) {
+ return a;
+ }
+
public static void main(String[] args) {
+ TestClass instance = new TestClass();
System.out.print(a(new TestObject("1")));
System.out.print(a(new TestObject("2"), new TestObject("3")));
System.out.print(a(new TestObject("4"), new TestObject("5"), new TestObject("6")));
+ System.out.print(instance.b(new TestObject("1")));
+ System.out.print(instance.b(new TestObject("2"), new TestObject("3")));
+ System.out.print(instance.b(new TestObject("4"), new TestObject("5"), new TestObject("6")));
}
}
@@ -76,18 +97,18 @@
@Override
public String getExpectedResult() {
- return "124";
+ return "124124";
}
@Override
public void inspectTestClass(ClassSubject clazz) {
- assertEquals(4, clazz.allMethods().size());
+ assertEquals(8, clazz.allMethods().size());
clazz.forAllMethods(
- method -> {
- Assert.assertTrue(
- method.getFinalName().equals("main")
- || (method.getFinalSignature().parameters.length == 1
- && method.getFinalSignature().parameters[0].equals("java.lang.Object")));
- });
+ method ->
+ Assert.assertTrue(
+ method.getFinalName().equals("main")
+ || method.getFinalName().equals("<init>")
+ || (method.getFinalSignature().parameters.length == 1
+ && method.getFinalSignature().parameters[0].equals("java.lang.Object"))));
}
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsTestBase.java b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsTestBase.java
index d20de91..0a5d8e9 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsTestBase.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/unusedarguments/UnusedArgumentsTestBase.java
@@ -55,8 +55,9 @@
.addProgramClasses(getTestClass())
.addProgramClasses(getAdditionalClasses())
.addKeepMainRule(getTestClass())
- .addKeepRules(minification ? "" : "-dontobfuscate")
+ .minification(minification)
.enableInliningAnnotations()
+ .enableClassInliningAnnotations()
.compile()
.run(getTestClass())
.inspect(this::inspect)
diff --git a/src/test/java/com/android/tools/r8/naming/CovariantReturnTypeTest.java b/src/test/java/com/android/tools/r8/naming/CovariantReturnTypeTest.java
index 44bd3db..314acdc 100644
--- a/src/test/java/com/android/tools/r8/naming/CovariantReturnTypeTest.java
+++ b/src/test/java/com/android/tools/r8/naming/CovariantReturnTypeTest.java
@@ -9,17 +9,14 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
-import com.android.tools.r8.StringConsumer.FileConsumer;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.jasmin.JasminBuilder;
import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
-import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
-import java.io.File;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.HashSet;
@@ -40,18 +37,6 @@
JasminBuilder appBuilder = new JasminBuilder();
ClassBuilder classBuilder = appBuilder.addClass("package.TestClass");
- // TODO(b/120959040): Use a testing rule to disable relevant optimizations instead.
- // Add a main method that instantiates A and B to make sure that the return type of the methods
- // below are not changed to void by the uninstantiated type optimization.
- classBuilder.addMainMethod(
- ".limit stack 1",
- ".limit locals 1",
- "new package/A",
- "invokespecial package/A/<init>()V",
- "new package/B",
- "invokespecial package/B/<init>()V",
- "return");
-
classBuilder.addVirtualMethod("method1", "Ljava/lang/Object;", returnNullByteCode);
classBuilder.addVirtualMethod("method1", "Lpackage/A;", returnNullByteCode);
classBuilder.addVirtualMethod("method1", "Lpackage/B;", returnNullByteCode);
@@ -70,17 +55,18 @@
classBuilder = appBuilder.addClass("package.B");
classBuilder.addDefaultConstructor();
- Path proguardMapPath = File.createTempFile("mapping", ".txt", temp.getRoot()).toPath();
- AndroidApp output =
- compileWithR8(
- appBuilder.build(),
- keepMainProguardConfiguration("package.TestClass"),
- options -> {
- options.enableTreeShaking = false;
- options.proguardMapConsumer = new FileConsumer(proguardMapPath);
- });
+ Path inputJar = writeToJar(appBuilder);
- CodeInspector inspector = new CodeInspector(output, proguardMapPath);
+ CodeInspector inspector =
+ testForR8(Backend.DEX)
+ .addProgramFiles(inputJar)
+ .addKeepMainRule("package.TestClass")
+ .addKeepRules("-keepconstantarguments class * { *; }")
+ .enableConstantArgumentAnnotations()
+ .treeShaking(false)
+ .compile()
+ .inspector();
+
ClassSubject clazz = inspector.clazz("package.TestClass");
assertThat(clazz, isPresent());
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/DesugarLambdaRetraceTest.java b/src/test/java/com/android/tools/r8/naming/retrace/DesugarLambdaRetraceTest.java
new file mode 100644
index 0000000..fa0a393
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/retrace/DesugarLambdaRetraceTest.java
@@ -0,0 +1,100 @@
+// 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.naming.retrace;
+
+import static com.android.tools.r8.naming.retrace.StackTrace.isSameExceptForFileName;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine;
+import com.google.common.collect.ImmutableList;
+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 DesugarLambdaRetraceTest extends RetraceTestBase {
+
+ @Parameters(name = "Backend: {0}, mode: {1}")
+ public static Collection<Object[]> data() {
+ return buildParameters(Backend.values(), CompilationMode.values());
+ }
+
+ public DesugarLambdaRetraceTest(Backend backend, CompilationMode mode) {
+ super(backend, mode);
+ }
+
+ @Override
+ public Collection<Class<?>> getClasses() {
+ return ImmutableList.of(getMainClass(), ConsumerDesugarLambdaRetraceTest.class);
+ }
+
+ @Override
+ public Class<?> getMainClass() {
+ return MainDesugarLambdaRetraceTest.class;
+ }
+
+ private int expectedActualStackTraceHeight() {
+ return mode == CompilationMode.RELEASE ? 2 : (backend == Backend.CF ? 4 : 5);
+ }
+
+ private boolean isSynthesizedLambdaFrame(StackTraceLine line) {
+ return line.className.contains("-$$Lambda$");
+ }
+
+ @Test
+ public void testSourceFileAndLineNumberTable() throws Exception {
+ runTest(
+ "-keepattributes SourceFile,LineNumberTable\n-printmapping",
+ (StackTrace actualStackTrace, StackTrace retracedStackTrace) -> {
+ // Even when SourceFile is present retrace replaces the file name in the stack trace.
+ if (backend == Backend.CF) {
+ assertThat(retracedStackTrace, isSameExceptForFileName(expectedStackTrace));
+ } else {
+ // With the frame from the lambda class filtered out the stack trace is teh same.
+ assertThat(
+ retracedStackTrace.filter(line -> !isSynthesizedLambdaFrame(line)),
+ isSameExceptForFileName(expectedStackTrace));
+ // Check the frame from the lambda class.
+ StackTrace lambdaFrames = retracedStackTrace.filter(this::isSynthesizedLambdaFrame);
+ assertEquals(1, lambdaFrames.size());
+ assertEquals(mode == CompilationMode.RELEASE ? 0 : 2, lambdaFrames.get(0).lineNumber);
+ // Proguard retrace will take the class name until the first $ to construct the file
+ // name, so for "-$$Lambda$...", the file name becomes "-.java".
+ assertEquals("-.java", lambdaFrames.get(0).fileName);
+ }
+ assertEquals(expectedActualStackTraceHeight(), actualStackTrace.size());
+ });
+ }
+}
+
+// Custom consumer functional interface, as java.util.function.Consumer is not present on Android.
+@FunctionalInterface
+interface ConsumerDesugarLambdaRetraceTest<T> {
+ void accept(T value);
+}
+
+class MainDesugarLambdaRetraceTest {
+
+ public static void method2(long j) {
+ System.out.println("In method2");
+ if (j == 1) throw null;
+ }
+
+ public static void method1(String s, ConsumerDesugarLambdaRetraceTest<String> consumer) {
+ System.out.println("In method1");
+ for (int i = 0; i < 10; i++) {
+ consumer.accept(s);
+ }
+ }
+
+ public static void main(String[] args) {
+ System.out.println("In main");
+ method1("1", s -> method2(Long.parseLong(s)));
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/RetraceTestBase.java b/src/test/java/com/android/tools/r8/naming/retrace/RetraceTestBase.java
index 0d0c21b..6243e26 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/RetraceTestBase.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/RetraceTestBase.java
@@ -7,6 +7,8 @@
import com.android.tools.r8.CompilationMode;
import com.android.tools.r8.R8TestRunResult;
import com.android.tools.r8.TestBase;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
import java.util.function.BiConsumer;
import org.junit.Before;
@@ -21,6 +23,10 @@
public StackTrace expectedStackTrace;
+ public Collection<Class<?>> getClasses() {
+ return ImmutableList.of(getMainClass());
+ }
+
public abstract Class<?> getMainClass();
@Before
@@ -42,7 +48,7 @@
.setMode(mode)
.enableProguardTestOptions()
.enableInliningAnnotations()
- .addProgramClasses(getMainClass())
+ .addProgramClasses(getClasses())
.addKeepMainRule(getMainClass())
.addKeepRules(keepRule)
.run(getMainClass())
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java b/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
index 3270e48..13115f4 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
@@ -18,6 +18,7 @@
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
+import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
@@ -183,6 +184,10 @@
return StackTrace.extractFromJvm(ToolHelper.runRetrace(mapFile, stackTraceFile));
}
+ public StackTrace filter(Predicate<StackTraceLine> filter) {
+ return new StackTrace(stackTraceLines.stream().filter(filter).collect(Collectors.toList()));
+ }
+
@Override
public int hashCode() {
return stackTraceLines.hashCode();
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());
}