Merge "Add tool to trace minimum memory usage back in time"
diff --git a/build.gradle b/build.gradle
index e469e03..977ebd7 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1902,6 +1902,7 @@
task copyMavenDeps(type: Copy) {
from configurations.compile into "$buildDir/deps"
+ from configurations.compileClasspath into "$buildDir/deps"
from configurations.testCompile into "$buildDir/deps"
}
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 3cf4664..228c0e5 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -445,9 +445,6 @@
appViewWithLiveness.setAppInfo(new SwitchMapCollector(appViewWithLiveness, options).run());
appViewWithLiveness.setAppInfo(
new EnumOrdinalMapCollector(appViewWithLiveness, options).run());
-
- // TODO(b/79143143): re-enable once fixed.
- // graphLense = new BridgeMethodAnalysis(graphLense, appInfo.withLiveness()).run();
}
timing.begin("Create IR");
diff --git a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
index e4f1c8c..012c4a4 100644
--- a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
@@ -7,6 +7,7 @@
package com.android.tools.r8.graph;
import com.android.tools.r8.ProgramResourceProvider;
+import com.android.tools.r8.graph.LazyLoadedDexApplication.AllClasses;
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.utils.ProgramClassCollection;
import com.android.tools.r8.utils.Timing;
@@ -21,9 +22,11 @@
public class DirectMappedDexApplication extends DexApplication {
+ private final AllClasses allClasses;
private final ImmutableMap<DexType, DexLibraryClass> libraryClasses;
private DirectMappedDexApplication(ClassNameMapper proguardMap,
+ AllClasses allClasses,
ProgramClassCollection programClasses,
ImmutableList<ProgramResourceProvider> programResourceProviders,
ImmutableMap<DexType, DexLibraryClass> libraryClasses,
@@ -32,6 +35,7 @@
Timing timing) {
super(proguardMap, programClasses, programResourceProviders, mainDexList, deadCode,
dexItemFactory, highestSortingString, timing);
+ this.allClasses = allClasses;
this.libraryClasses = libraryClasses;
}
@@ -92,17 +96,21 @@
public static class Builder extends DexApplication.Builder<Builder> {
+ private final AllClasses allClasses;
private final List<DexLibraryClass> libraryClasses = new ArrayList<>();
Builder(LazyLoadedDexApplication application) {
super(application);
// As a side-effect, this will force-load all classes.
- Map<DexType, DexClass> allClasses = application.getFullClassMap();
+ this.allClasses = application.loadAllClasses();
+ Map<DexType, DexClass> allClasses = this.allClasses.getClasses();
+ // TODO(120884788): This filter will only add library classes which are not program classes.
Iterables.filter(allClasses.values(), DexLibraryClass.class).forEach(libraryClasses::add);
}
private Builder(DirectMappedDexApplication application) {
super(application);
+ this.allClasses = application.allClasses;
this.libraryClasses.addAll(application.libraryClasses.values());
}
@@ -116,6 +124,7 @@
// Rebuild the map. This will fail if keys are not unique.
return new DirectMappedDexApplication(
proguardMap,
+ allClasses,
ProgramClassCollection.create(
programClasses, ProgramClassCollection::resolveClassConflictImpl),
ImmutableList.copyOf(programResourceProviders),
diff --git a/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java b/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
index b648e41..6dea825 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
@@ -55,31 +55,67 @@
return clazz;
}
- private Map<DexType, DexClass> forceLoadAllClasses() {
- Map<DexType, DexClass> loaded = new IdentityHashMap<>();
+ static class AllClasses {
+ private Map<DexType, DexClass> libraryClasses;
+ private Map<DexType, DexClass> classpathClasses;
+ private Map<DexType, DexClass> programClasses;
+ private Map<DexType, DexClass> classes;
- // Program classes are supposed to be loaded, but force-loading them is no-op.
- programClasses.forceLoad(type -> true);
- programClasses.getAllClasses().forEach(clazz -> loaded.put(clazz.type, clazz));
+ AllClasses(
+ LibraryClassCollection libraryClasses,
+ ClasspathClassCollection classpathClasses,
+ ProgramClassCollection programClasses) {
+ load(libraryClasses, classpathClasses, programClasses);
- if (classpathClasses != null) {
- classpathClasses.forceLoad(type -> !loaded.containsKey(type));
- classpathClasses.getAllClasses().forEach(clazz -> loaded.putIfAbsent(clazz.type, clazz));
+ // Collect loaded classes in the precedence order program classes, class path classes and
+ // library classes.
+ // TODO(b/120884788): Change this.
+ classes = new IdentityHashMap<>();
+ classes.putAll(this.programClasses);
+ if (classpathClasses != null) {
+ classpathClasses.getAllClasses().forEach(clazz -> classes.putIfAbsent(clazz.type, clazz));
+ }
+ if (libraryClasses != null) {
+ libraryClasses.getAllClasses().forEach(clazz -> classes.putIfAbsent(clazz.type, clazz));
+ }
}
- if (libraryClasses != null) {
- libraryClasses.forceLoad(type -> !loaded.containsKey(type));
- libraryClasses.getAllClasses().forEach(clazz -> loaded.putIfAbsent(clazz.type, clazz));
+ public Map<DexType, DexClass> getLibraryClasses() {
+ return libraryClasses;
}
- return loaded;
+ public Map<DexType, DexClass> getClasspathClasses() {
+ return classpathClasses;
+ }
+
+ public Map<DexType, DexClass> getClasses() {
+ return classes;
+ }
+
+ private void load(
+ LibraryClassCollection libraryClasses,
+ ClasspathClassCollection classpathClasses,
+ ProgramClassCollection programClasses) {
+ if (libraryClasses != null) {
+ libraryClasses.forceLoad(type -> true);
+ this.libraryClasses = libraryClasses.getAllClassesInMap();
+ }
+ if (classpathClasses != null) {
+ classpathClasses.forceLoad(type -> true);
+ this.classpathClasses = classpathClasses.getAllClassesInMap();
+ }
+ assert programClasses != null;
+ // Program classes are supposed to be loaded, but force-loading them is no-op.
+ programClasses.forceLoad(type -> true);
+ this.programClasses = programClasses.getAllClassesInMap();
+ }
}
/**
* Force load all classes and return type -> class map containing all the classes.
*/
- public Map<DexType, DexClass> getFullClassMap() {
- return forceLoadAllClasses();
+ public AllClasses loadAllClasses() {
+ return new AllClasses(libraryClasses, classpathClasses, programClasses);
}
public static class Builder extends DexApplication.Builder<Builder> {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ReferenceTypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ReferenceTypeLatticeElement.java
index e7282e1..0db0ca4 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/ReferenceTypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ReferenceTypeLatticeElement.java
@@ -43,13 +43,13 @@
}
@Override
- public boolean isNull() {
+ public boolean isNullType() {
return type == DexItemFactory.nullValueType;
}
@Override
public TypeLatticeElement asNullable() {
- assert isNull();
+ assert isNullType();
return this;
}
@@ -88,7 +88,7 @@
@Override
public int hashCode() {
- assert isNull();
+ assert isNullType();
return System.identityHashCode(this);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeLatticeElement.java
index b29cd31..ad8edd3 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeLatticeElement.java
@@ -29,7 +29,6 @@
public static final ReferenceTypeLatticeElement NULL =
ReferenceTypeLatticeElement.getNullTypeLatticeElement();
-
// TODO(b/72693244): Switch to NullLatticeElement.
private final boolean isNullable;
@@ -42,7 +41,7 @@
}
public NullLatticeElement nullElement() {
- if (isNull()) {
+ if (isNullType()) {
return NullLatticeElement.definitelyNull();
}
if (!isNullable()) {
@@ -91,10 +90,10 @@
if (isTop() || other.isTop()) {
return TOP;
}
- if (isNull()) {
+ if (isNullType()) {
return other.asNullable();
}
- if (other.isNull()) {
+ if (other.isNullType()) {
return asNullable();
}
if (isPrimitive()) {
@@ -270,7 +269,7 @@
public boolean isPreciseType() {
return isArrayType()
|| isClassType()
- || isNull()
+ || isNullType()
|| isInt()
|| isFloat()
|| isLong()
@@ -286,21 +285,13 @@
}
/**
- * Should use {@link #isConstantNull()} or {@link #isDefinitelyNull()} instead.
- */
- @Deprecated
- public boolean isNull() {
- return false;
- }
-
- /**
* Determines if this type only includes null values that are defined by a const-number
* instruction in the same enclosing method.
*
* These null values can be assigned to any type.
*/
- public boolean isConstantNull() {
- return isNull();
+ public boolean isNullType() {
+ return false;
}
/**
diff --git a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
index c68baf9..596fdb8 100644
--- a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
+++ b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
@@ -148,7 +148,7 @@
assert inType.nullElement().lessThanOrEqual(outType.nullElement());
// Since we cannot remove the cast the in-value must be different from null.
- assert !inType.isNull();
+ assert !inType.isNullType();
// TODO(b/72693244): Consider checking equivalence. This requires that the types are always
// as precise as possible, though, meaning that almost all changes to the IR must be followed
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
index 7a6a9a4..3976889 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
@@ -302,7 +302,7 @@
assert super.verifyTypes(appInfo, graphLense);
assert !isZero()
|| outValue().getTypeLattice().isPrimitive()
- || outValue().getTypeLattice().isConstantNull();
+ || outValue().getTypeLattice().isNullType();
return true;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
index cde197c..48b6c9b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
@@ -11,8 +11,10 @@
import java.util.ListIterator;
import java.util.function.Predicate;
-public interface InstructionListIterator extends ListIterator<Instruction>,
- NextUntilIterator<Instruction> {
+public interface InstructionListIterator
+ extends ListIterator<Instruction>,
+ NextUntilIterator<Instruction>,
+ PreviousUntilIterator<Instruction> {
/**
* Peek the previous instruction.
diff --git a/src/main/java/com/android/tools/r8/ir/code/PreviousUntilIterator.java b/src/main/java/com/android/tools/r8/ir/code/PreviousUntilIterator.java
new file mode 100644
index 0000000..4d0f790
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/code/PreviousUntilIterator.java
@@ -0,0 +1,26 @@
+// 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.code;
+
+import java.util.ListIterator;
+import java.util.function.Predicate;
+
+public interface PreviousUntilIterator<T> extends ListIterator<T> {
+
+ /**
+ * Continue to call {@link #previous} while {@code predicate} tests {@code false}.
+ *
+ * @returns the item that matched the predicate or {@code null} if all items fail the predicate
+ * test
+ */
+ default T previousUntil(Predicate<T> predicate) {
+ while (hasPrevious()) {
+ T item = previous();
+ if (predicate.test(item)) {
+ return item;
+ }
+ }
+ return null;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Throw.java b/src/main/java/com/android/tools/r8/ir/code/Throw.java
index c9b12f2..23bb76c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Throw.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Throw.java
@@ -82,7 +82,7 @@
return true;
}
TypeLatticeElement exceptionType = exception().getTypeLattice();
- if (exceptionType.isConstantNull()) {
+ if (exceptionType.isNullType()) {
// throw null
return true;
}
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 72e2887..8c16d04 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
@@ -5,7 +5,7 @@
import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.Flavor.ExcludeDexResources;
import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.Flavor.IncludeAllResources;
-import static com.android.tools.r8.ir.optimize.CodeRewriter.checksNullReceiverBeforeSideEffect;
+import static com.android.tools.r8.ir.optimize.CodeRewriter.checksNullBeforeSideEffect;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppInfo;
@@ -35,7 +35,6 @@
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionListIterator;
-import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.InvokeStatic;
import com.android.tools.r8.ir.code.NumericType;
import com.android.tools.r8.ir.code.Value;
@@ -880,10 +879,6 @@
printC1VisualizerHeader(method);
String previous = printMethod(code, "Initial IR (SSA)", null);
- if (method.getCode() != null && method.getCode().isJarCode()) {
- computeKotlinNonNullParamHints(feedback, method, code);
- }
-
if (options.canHaveArtStringNewInitBug()) {
CodeRewriter.ensureDirectStringNewToInit(code);
}
@@ -1110,11 +1105,10 @@
codeRewriter.identifyReturnsArgument(method, code, feedback);
if (options.enableInlining && inliner != null) {
- codeRewriter.identifyInvokeSemanticsForInlining(method, code, feedback);
+ codeRewriter.identifyInvokeSemanticsForInlining(method, code, graphLense(), feedback);
}
- // If hints from Kotlin metadata or use of Kotlin Intrinsics were not available, track usage of
- // parameters and compute their nullability and possibility of NPE.
+ // Track usage of parameters and compute their nullability and possibility of NPE.
if (method.getOptimizationInfo().getNonNullParamOrThrow() == null) {
computeNonNullParamHints(feedback, method, code);
}
@@ -1159,33 +1153,25 @@
finalizeIR(method, code, feedback);
}
- private void computeKotlinNonNullParamHints(
- OptimizationFeedback feedback, DexEncodedMethod method, IRCode code) {
+ private void computeNonNullParamHints(
+ OptimizationFeedback feedback, DexEncodedMethod method, IRCode code) {
List<Value> arguments = code.collectArguments(true);
BitSet paramsCheckedForNull = new BitSet();
- DexMethod checkParameterIsNotNull =
- code.options.itemFactory.kotlin.intrinsics.checkParameterIsNotNull;
for (int index = 0; index < arguments.size(); index++) {
Value argument = arguments.get(index);
- for (Instruction user : argument.uniqueUsers()) {
- // To enforce parameter non-null requirement Kotlin uses intrinsic:
- // kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull(param, message)
- //
- // with the following we simply look if the parameter is ever passed
- // to the mentioned intrinsic as the first argument. We do it only for
- // code coming from Java classfile, since after the method is rewritten
- // by R8 this call gets inlined.
- if (!user.isInvokeStatic()) {
- continue;
- }
- InvokeMethod invoke = user.asInvokeMethod();
- DexMethod invokedMethod =
- 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);
- }
+ // This handles cases where the parameter is checked via Kotlin Intrinsics:
+ //
+ // kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull(param, message)
+ //
+ // or its inlined version:
+ //
+ // if (param != null) return;
+ // invoke-static throwParameterIsNullException(msg)
+ //
+ // or some other variants, e.g., throw null or NPE after the direct null check.
+ if (argument.isUsed()
+ && checksNullBeforeSideEffect(code, appInfo, graphLense(), argument)) {
+ paramsCheckedForNull.set(index);
}
}
if (paramsCheckedForNull.length() > 0) {
@@ -1213,28 +1199,6 @@
}
}
- // 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);
- BitSet paramsCheckedForNull = new BitSet();
- for (int index = 0; index < arguments.size(); index++) {
- Value argument = arguments.get(index);
- if (argument.isUsed()
- && checksNullReceiverBeforeSideEffect(code, appInfo.dexItemFactory, argument)) {
- paramsCheckedForNull.set(index);
- }
- // The above one only catches something like:
- // if (param != null) return;
- // invoke-static throwParameterIsNullException(msg)
- // This is good enough to handle checkParameterIsNotNull(param, msg), which is generated by
- // kotlinc for arguments that are not nullable.
- }
- if (paramsCheckedForNull.length() > 0) {
- feedback.setNonNullParamOrThrow(method, paramsCheckedForNull);
- }
- }
-
private void finalizeIR(DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
code.traceBlocks();
if (options.isGeneratingClassFiles()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 2244653..40a7692 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -38,6 +38,7 @@
import com.android.tools.r8.graph.DexValue.DexValueNull;
import com.android.tools.r8.graph.DexValue.DexValueShort;
import com.android.tools.r8.graph.DexValue.DexValueString;
+import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.graph.ParameterUsagesInfo;
import com.android.tools.r8.graph.ParameterUsagesInfo.ParameterUsage;
import com.android.tools.r8.graph.ParameterUsagesInfo.ParameterUsageBuilder;
@@ -1003,7 +1004,7 @@
}
public void identifyInvokeSemanticsForInlining(
- DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
+ DexEncodedMethod method, IRCode code, GraphLense graphLense, OptimizationFeedback feedback) {
if (method.isStatic()) {
// Identifies if the method preserves class initialization after inlining.
feedback.markTriggerClassInitBeforeAnySideEffect(method,
@@ -1013,7 +1014,7 @@
final Value receiver = code.getThis();
feedback.markCheckNullReceiverBeforeAnySideEffect(method,
receiver.isUsed()
- && checksNullReceiverBeforeSideEffect(code, appInfo.dexItemFactory, receiver));
+ && checksNullBeforeSideEffect(code, appInfo, graphLense, receiver));
}
}
@@ -1342,55 +1343,66 @@
}
/**
- * Returns true if the given code unconditionally throws if receiver is null before any other
- * side effect instruction.
+ * Returns true if the given code unconditionally throws if value is null before any other side
+ * effect instruction.
*
* Note: we do not track phis so we may return false negative. This is a conservative approach.
*/
- public static boolean checksNullReceiverBeforeSideEffect(
- IRCode code, DexItemFactory factory, Value receiver) {
- Wrapper<DexMethod> throwParamIsNullException =
- MethodSignatureEquivalence.get()
- .wrap(factory.kotlin.intrinsics.throwParameterIsNullException);
+ public static boolean checksNullBeforeSideEffect(
+ IRCode code, AppInfo appInfo, GraphLense graphLense, Value value) {
return alwaysTriggerExpectedEffectBeforeAnythingElse(
code,
(instr, it) -> {
BasicBlock currentBlock = instr.getBlock();
- // If the code explicitly checks the nullability of receiver, we should visit the next
- // block that corresponds to the null receiver where NPE semantic could be preserved.
- if (!currentBlock.hasCatchHandlers() && isNullCheck(instr, receiver)) {
+ // If the code explicitly checks the nullability of the value, we should visit the next
+ // block that corresponds to the null value where NPE semantic could be preserved.
+ if (!currentBlock.hasCatchHandlers() && isNullCheck(instr, value)) {
return InstructionEffect.CONDITIONAL_EFFECT;
}
- // Kotlin specific way of throwing NPE: throwParameterIsNullException.
- // Similarly, combined with the above CONDITIONAL_EFFECT, the code checks on NPE on
- // receiver.
- if (instr.isInvokeStatic()) {
- DexMethod method = instr.asInvokeStatic().getInvokedMethod();
- if (MethodSignatureEquivalence.get().wrap(method).equals(throwParamIsNullException)) {
+ if (isKotlinNullCheck(appInfo, graphLense, instr, value)) {
+ DexMethod invokedMethod = instr.asInvokeStatic().getInvokedMethod();
+ // Kotlin specific way of throwing NPE: throwParameterIsNullException.
+ // Similarly, combined with the above CONDITIONAL_EFFECT, the code checks on NPE on
+ // the value.
+ if (invokedMethod.name
+ == appInfo.dexItemFactory.kotlin.intrinsics.throwParameterIsNullException.name) {
// We found a NPE (or similar exception) throwing code.
- // Combined with the above CONDITIONAL_EFFECT, the code checks NPE on receiver.
+ // Combined with the above CONDITIONAL_EFFECT, the code checks NPE on the value.
for (BasicBlock predecessor : currentBlock.getPredecessors()) {
Instruction last =
predecessor.listIterator(predecessor.getInstructions().size()).previous();
- if (isNullCheck(last, receiver)) {
+ if (isNullCheck(last, value)) {
return InstructionEffect.DESIRED_EFFECT;
}
}
+ // Hitting here means that this call might be used for other parameters. If we don't
+ // bail out, it will be regarded as side effects for the current value.
+ return InstructionEffect.NO_EFFECT;
+ } else {
+ // Kotlin specific way of checking parameter nullness: checkParameterIsNotNull.
+ assert invokedMethod.name
+ == appInfo.dexItemFactory.kotlin.intrinsics.checkParameterIsNotNull.name;
+ return InstructionEffect.DESIRED_EFFECT;
}
}
- if (isInstantiationOfNullPointerException(instr, it, factory)) {
+ if (isInstantiationOfNullPointerException(instr, it, appInfo.dexItemFactory)) {
it.next(); // Skip call to NullPointerException.<init>.
return InstructionEffect.NO_EFFECT;
- } else if (instr.throwsNpeIfValueIsNull(receiver, factory)) {
+ } else if (instr.throwsNpeIfValueIsNull(value, appInfo.dexItemFactory)) {
// In order to preserve NPE semantic, the exception must not be caught by any handler.
// Therefore, we must ignore this instruction if it is covered by a catch handler.
// Note: this is a conservative approach where we consider that any catch handler could
// catch the exception, even if it cannot catch a NullPointerException.
if (!currentBlock.hasCatchHandlers()) {
- // We found a NPE check on receiver.
+ // We found a NPE check on the value.
return InstructionEffect.DESIRED_EFFECT;
}
} else if (instructionHasSideEffects(instr)) {
+ // If the current instruction is const-string, this could load the parameter name.
+ // Just make sure it is indeed not throwing.
+ if (instr.isConstString() && !instr.instructionInstanceCanThrow()) {
+ return InstructionEffect.NO_EFFECT;
+ }
// We found a side effect before a NPE check.
return InstructionEffect.OTHER_EFFECT;
}
@@ -1398,9 +1410,36 @@
});
}
- private static boolean isNullCheck(Instruction instr, Value receiver) {
+ // Note that this method may have false positives, since the application could in principle
+ // declare a method called checkParameterIsNotNull(parameter, message) or
+ // throwParameterIsNullException(parameterName) in a package that starts with "kotlin".
+ private static boolean isKotlinNullCheck(
+ AppInfo appInfo, GraphLense graphLense, Instruction instr, Value value) {
+ if (!instr.isInvokeStatic()) {
+ return false;
+ }
+ // We need to strip the holder, since Kotlin adds different versions of null-check machinery,
+ // e.g., kotlin.collections.ArraysKt___ArraysKt... or kotlin.jvm.internal.ArrayIteratorKt...
+ MethodSignatureEquivalence wrapper = MethodSignatureEquivalence.get();
+ Wrapper<DexMethod> checkParameterIsNotNull =
+ wrapper.wrap(appInfo.dexItemFactory.kotlin.intrinsics.checkParameterIsNotNull);
+ Wrapper<DexMethod> throwParamIsNullException =
+ wrapper.wrap(appInfo.dexItemFactory.kotlin.intrinsics.throwParameterIsNullException);
+ DexMethod invokedMethod =
+ graphLense.getOriginalMethodSignature(instr.asInvokeStatic().getInvokedMethod());
+ Wrapper<DexMethod> methodWrap = wrapper.wrap(invokedMethod);
+ if (methodWrap.equals(throwParamIsNullException)
+ || (methodWrap.equals(checkParameterIsNotNull) && instr.inValues().get(0).equals(value))) {
+ if (invokedMethod.getHolder().getPackageDescriptor().startsWith("kotlin")) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static boolean isNullCheck(Instruction instr, Value value) {
return instr.isIf() && instr.asIf().isZeroTest()
- && instr.inValues().get(0).equals(receiver)
+ && instr.inValues().get(0).equals(value)
&& (instr.asIf().getType() == Type.EQ || instr.asIf().getType() == Type.NE);
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
index b82c743..e08e9c4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
@@ -250,20 +250,23 @@
// in class initializer and is never null.
//
DexClass holderDefinition = appInfo.definitionFor(field.getHolder());
- if (holderDefinition != null &&
- holderDefinition.accessFlags.isFinal() &&
- !appInfo.canTriggerStaticInitializer(field.getHolder(), true)) {
-
+ if (holderDefinition != null
+ && holderDefinition.accessFlags.isFinal()
+ && !appInfo.canTriggerStaticInitializer(field.getHolder(), true)) {
Value outValue = staticGet.dest();
DexEncodedMethod classInitializer = holderDefinition.getClassInitializer();
if (classInitializer != null && !isProcessedConcurrently.test(classInitializer)) {
TrivialInitializer info =
classInitializer.getOptimizationInfo().getTrivialInitializerInfo();
- if (info != null &&
- ((TrivialClassInitializer) info).field == field &&
- !appInfo.isPinned(field) &&
- outValue.canBeNull()) {
+ if (info != null
+ && ((TrivialClassInitializer) info).field == field
+ && !appInfo.isPinned(field)
+ && outValue.canBeNull()) {
outValue.markNeverNull();
+ TypeLatticeElement typeLattice = outValue.getTypeLattice();
+ assert typeLattice.isNullable() && typeLattice.isReference();
+ outValue.narrowing(appInfo, typeLattice.asNonNullable());
+ affectedValues.addAll(outValue.affectedValues());
}
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java b/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
index 2094584..b417c48 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
@@ -343,7 +343,7 @@
// redundant
!knownToBeNonNullValue.isNeverNull()
// v <- non-null NULL ?!
- && !typeLattice.isConstantNull()
+ && !typeLattice.isNullType()
// v <- non-null known-to-be-non-null // redundant
&& typeLattice.isNullable()
// e.g., v <- non-null INT ?!
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 47b9289..9d2bb32 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
@@ -36,6 +36,7 @@
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
+import java.util.stream.Collectors;
public class UnusedArgumentsCollector {
@@ -89,7 +90,9 @@
Streams.stream(appView.appInfo().classes())
.map(this::runnableForClass)
.map(executorService::submit)
- .iterator());
+ // Materialize list such that all runnables are submitted to the executor service
+ // before calling awaitFutures().
+ .collect(Collectors.toList()));
if (!methodMapping.isEmpty()) {
return new UnusedArgumentsGraphLense(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/peepholes/PeepholeHelper.java b/src/main/java/com/android/tools/r8/ir/optimize/peepholes/PeepholeHelper.java
index c1e911f..d656455 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/peepholes/PeepholeHelper.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/peepholes/PeepholeHelper.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.StackValues;
import com.android.tools.r8.ir.code.Value;
+import java.util.List;
import java.util.function.Predicate;
public class PeepholeHelper {
@@ -19,18 +20,6 @@
&& (t.outValue() == null || !t.outValue().hasLocalInfo());
}
- public static void swapNextTwoInstructions(InstructionListIterator it) {
- assert it.hasNext();
- Instruction moveForward = it.next();
- Instruction moveBack = it.next();
- it.set(moveForward);
- // Two calls to previous is needed because the iterator moves between elements.
- it.previous();
- it.previous();
- it.set(moveBack);
- it.next();
- }
-
public static void resetNext(InstructionListIterator it, int count) {
for (int i = 0; i < count; i++) {
it.previous();
@@ -62,4 +51,23 @@
}
return count;
}
+
+ public static void moveInstructionsUpToCurrentPosition(
+ InstructionListIterator it, List<Instruction> instructions) {
+ assert !instructions.isEmpty();
+ for (Instruction instruction : instructions) {
+ for (Value inValue : instruction.inValues()) {
+ inValue.addUser(instruction);
+ }
+ it.add(instruction);
+ }
+ Instruction current = it.nextUntil(i -> i == instructions.get(0));
+ for (int i = 0; i < instructions.size(); i++) {
+ assert current == instructions.get(i);
+ it.removeOrReplaceByDebugLocalRead();
+ current = it.next();
+ }
+ it.previousUntil(i -> i == instructions.get(instructions.size() - 1));
+ it.next();
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/peepholes/StoreLoadToDupStorePeephole.java b/src/main/java/com/android/tools/r8/ir/optimize/peepholes/StoreLoadToDupStorePeephole.java
index a0f1c8c..58c50d8 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/peepholes/StoreLoadToDupStorePeephole.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/peepholes/StoreLoadToDupStorePeephole.java
@@ -11,7 +11,6 @@
import com.android.tools.r8.ir.code.StackValue;
import com.android.tools.r8.ir.code.StackValues;
import com.android.tools.r8.ir.code.Store;
-import com.android.tools.r8.ir.code.Value;
import java.util.List;
/**
@@ -45,63 +44,40 @@
if (match == null) {
return false;
}
- Store oldStore = storeExp.get(match).asStore();
+ Store store = storeExp.get(match).asStore();
Load load = loadExp.get(match).asLoad();
- if (load.src() != oldStore.outValue() || oldStore.outValue().numberOfAllUsers() <= 1) {
+ if (load.src() != store.outValue() || store.outValue().numberOfAllUsers() <= 1) {
return false;
}
List<Instruction> dups = dupsExp.get(match);
- Instruction lastDup = dups.isEmpty() ? null : dups.get(dups.size() - 1);
- StackValue oldStoreSrc = (StackValue) oldStore.src();
- Value loadOut = load.swapOutValue(null);
- if (lastDup == null) {
- // Replace
- // Store v0 <- oldStoreSrc
- // Load loadOut <- v0
- // with
- // Dup [loadOut, newStoreSrc] <- oldStoreSrc
- // Store v0 <- newStoreSrc
-
- StackValue newStoreSrc = oldStoreSrc.duplicate(oldStoreSrc.getHeight() + 1);
- Dup dup = new Dup((StackValue) loadOut, newStoreSrc, oldStoreSrc);
- dup.setPosition(oldStore.getPosition());
- oldStore.replaceValue(0, newStoreSrc);
- it.add(dup);
- PeepholeHelper.resetPrevious(it, 2);
- it.removeOrReplaceByDebugLocalRead();
+ StackValue oldStoreSrc = (StackValue) store.src();
+ StackValue dupIn;
+ StackValue lastOut;
+ // Store and load may be in two different basic blocks (see b/122445224) so move all
+ // instructions to the block with the store.
+ if (dups.isEmpty()) {
+ lastOut = (StackValue) load.outValue();
+ dupIn = oldStoreSrc;
} else {
- // Replace
- // Store storeOut <- oldStoreSrc
- // Load loadOut <- storeOut
- // Dup [sa, sb] <- loadOut
- // (Dup/Dup2)*
- // Dup/Dup2 [..., topAfterDups] <- [...] # lastDup
- // with
- // Dup [sa, sb] <- oldStoreSrc
- // (Dup/Dup2)*
- // Dup/Dup2 [..., newTopAfterDups] <- [...] # lastDup
- // Dup [topAfterDups, newStoreSrc] <- newTopAfterDups
- // Store storeOut <- newStoreSrc
- Value storeOut = oldStore.swapOutValue(null);
- it.removeOrReplaceByDebugLocalRead(); // Remove Store.
- it.next();
- it.removeOrReplaceByDebugLocalRead(); // Remove Load.
- assert dups.get(0).isDup() && dups.get(0).inValues().get(0) == loadOut;
+ assert dups.get(0).isDup() && dups.get(0).inValues().get(0) == load.outValue();
dups.get(0).replaceValue(0, oldStoreSrc);
- StackValues lastDupOut = (StackValues) lastDup.outValue();
- StackValue topAfterDups = lastDupOut.getStackValues()[lastDupOut.getStackValues().length - 1];
- StackValue newTopAfterDups = topAfterDups.duplicate(topAfterDups.getHeight());
- lastDupOut.getStackValues()[lastDupOut.getStackValues().length - 1] = newTopAfterDups;
- newTopAfterDups.definition = lastDup;
- StackValue newStoreSrc = oldStoreSrc.duplicate(topAfterDups.getHeight() + 1);
- Dup newDup = new Dup(topAfterDups, newStoreSrc, newTopAfterDups);
- newDup.setPosition(lastDup.getPosition());
- PeepholeHelper.resetPrevious(it, dups.size());
- it.add(newDup);
- Store newStore = new Store(storeOut, newStoreSrc);
- newStore.setPosition(lastDup.getPosition());
- it.add(newStore);
+ PeepholeHelper.moveInstructionsUpToCurrentPosition(it, dups);
+ StackValue[] lastDupOutValues =
+ ((StackValues) dups.get(dups.size() - 1).outValue()).getStackValues();
+ lastOut = lastDupOutValues[lastDupOutValues.length - 1];
+ dupIn = lastOut;
}
+ StackValue newLastOut = lastOut.duplicate(lastOut.getHeight());
+ StackValue newStoreSrc = lastOut.duplicate(lastOut.getHeight() + 1);
+ lastOut.replaceUsers(newLastOut);
+ assert !load.outValue().isUsed();
+ Dup dup = new Dup(newLastOut, newStoreSrc, dupIn);
+ dup.setPosition(store.getPosition());
+ it.add(dup);
+ store.replaceValue(0, newStoreSrc);
+ it.nextUntil(i -> i == load);
+ it.removeOrReplaceByDebugLocalRead();
+ PeepholeHelper.resetNext(it, dups.size() + 1);
return true;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
index bd4f2b1..cdd85e4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
@@ -305,7 +305,7 @@
continue;
}
TypeLatticeElement inType = in.getTypeLattice();
- if (inType.isConstantNull()) {
+ if (inType.isNullType()) {
Value nullStringValue =
code.createValue(TypeLatticeElement.stringClassType(appInfo), invoke.getLocalInfo());
ConstString nullString = new ConstString(
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
index 049b2e7..1ac8555 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -423,14 +423,10 @@
// Compute the final change in locals and insert it before nextInstruction.
boolean localsChanged = !ending.isEmpty() || !starting.isEmpty();
if (localsChanged) {
- boolean skipChange =
- nextInstruction == nextInstruction.getBlock().exit() && nextInstruction.isGoto();
- if (!skipChange) {
- DebugLocalsChange change = createLocalsChange(ending, starting);
- if (change != null) {
- // Insert the DebugLocalsChange instruction before nextInstruction.
- instructionIterator.add(change);
- }
+ DebugLocalsChange change = createLocalsChange(ending, starting);
+ if (change != null) {
+ // Insert the DebugLocalsChange instruction before nextInstruction.
+ instructionIterator.add(change);
}
// Create new maps for the next DebugLocalsChange instruction.
ending = new Int2ReferenceOpenHashMap<>();
diff --git a/src/main/java/com/android/tools/r8/optimize/BridgeMethodAnalysis.java b/src/main/java/com/android/tools/r8/optimize/BridgeMethodAnalysis.java
deleted file mode 100644
index 86065d9..0000000
--- a/src/main/java/com/android/tools/r8/optimize/BridgeMethodAnalysis.java
+++ /dev/null
@@ -1,160 +0,0 @@
-// Copyright (c) 2017, 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.optimize;
-
-import com.android.tools.r8.errors.Unimplemented;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GraphLense;
-import com.android.tools.r8.ir.code.Invoke.Type;
-import com.android.tools.r8.logging.Log;
-import com.android.tools.r8.optimize.InvokeSingleTargetExtractor.InvokeKind;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
-import java.util.IdentityHashMap;
-import java.util.Map;
-
-public class BridgeMethodAnalysis {
-
- private final GraphLense lense;
- private final AppInfoWithLiveness appInfo;
- private final Map<DexMethod, DexMethod> bridgeTargetToBridgeMap = new IdentityHashMap<>();
-
- public BridgeMethodAnalysis(GraphLense lense, AppInfoWithLiveness appInfo) {
- this.lense = lense;
- this.appInfo = appInfo;
- }
-
- public GraphLense run() {
- for (DexClass clazz : appInfo.classes()) {
- clazz.forEachMethod(this::identifyBridgeMethod);
- }
- return new BridgeLense(lense, bridgeTargetToBridgeMap);
- }
-
- private void identifyBridgeMethod(DexEncodedMethod method) {
- // The tree pruner can mark bridge methods abstract if they are not reachable but cannot
- // be removed.
- if (method.accessFlags.isBridge() && !method.accessFlags.isAbstract()) {
- InvokeSingleTargetExtractor targetExtractor =
- new InvokeSingleTargetExtractor(appInfo.dexItemFactory);
- method.getCode().registerCodeReferences(targetExtractor);
- DexMethod target = targetExtractor.getTarget();
- InvokeKind kind = targetExtractor.getKind();
- if (target != null && target.getArity() == method.method.getArity()) {
- assert !method.accessFlags.isPrivate() && !method.accessFlags.isConstructor();
- if (kind == InvokeKind.STATIC) {
- assert method.accessFlags.isStatic();
- DexMethod actualTarget = lense.lookupMethod(target, method, Type.STATIC).getMethod();
- DexEncodedMethod targetMethod = appInfo.lookupStaticTarget(actualTarget);
- if (targetMethod != null) {
- addForwarding(method, targetMethod);
- }
- } else if (kind == InvokeKind.VIRTUAL) {
- // TODO(herhut): Add support for bridges with multiple targets.
- DexMethod actualTarget = lense.lookupMethod(target, method, Type.VIRTUAL).getMethod();
- DexEncodedMethod targetMethod = appInfo.lookupSingleVirtualTarget(actualTarget);
- if (targetMethod != null) {
- addForwarding(method, targetMethod);
- }
- }
- }
- }
- }
-
- private void addForwarding(DexEncodedMethod method, DexEncodedMethod target) {
- // This is a single target bridge we can inline.
- if (Log.ENABLED) {
- Log.info(getClass(), "Adding bridge forwarding %s -> %s.", method.method,
- target.method);
- }
- // If we manage to rewrite all invocations, the bridge will be the only invocation of the target
- // of the bridge and the target will get inlined. This should happen in most cases. For the few
- // other cases, we might have inserted some extra checkcast instructions for the return type.
- bridgeTargetToBridgeMap.put(target.method, method.method);
- }
-
-
- private static class BridgeLense extends GraphLense {
-
- private final GraphLense previousLense;
- private final Map<DexMethod, DexMethod> bridgeTargetToBridgeMap;
-
- private BridgeLense(GraphLense previousLense,
- Map<DexMethod, DexMethod> bridgeTargetToBridgeMap) {
- this.previousLense = previousLense;
- this.bridgeTargetToBridgeMap = bridgeTargetToBridgeMap;
- }
-
- @Override
- public DexField getOriginalFieldSignature(DexField field) {
- return previousLense.getOriginalFieldSignature(field);
- }
-
- @Override
- public DexMethod getOriginalMethodSignature(DexMethod method) {
- // TODO(b/79143143): implement this when re-enable bridge analysis.
- throw new Unimplemented("BridgeLense.getOriginalMethodSignature() not implemented");
- }
-
- @Override
- public DexField getRenamedFieldSignature(DexField originalField) {
- return previousLense.getRenamedFieldSignature(originalField);
- }
-
- @Override
- public DexMethod getRenamedMethodSignature(DexMethod originalMethod) {
- // TODO(b/79143143): implement this when re-enabling bridge analysis.
- throw new Unimplemented("BridgeLense.getRenamedMethodSignature() not implemented");
- }
-
- @Override
- public DexType lookupType(DexType type) {
- return previousLense.lookupType(type);
- }
-
- @Override
- public GraphLenseLookupResult lookupMethod(
- DexMethod method, DexEncodedMethod context, Type type) {
- GraphLenseLookupResult previous = previousLense.lookupMethod(method, context, type);
- DexMethod bridge = bridgeTargetToBridgeMap.get(previous.getMethod());
- // Do not forward calls from a bridge method to itself while the bridge method is still
- // a bridge.
- if (bridge == null || (context.accessFlags.isBridge() && bridge == context.method)) {
- return previous;
- }
- return new GraphLenseLookupResult(bridge, type);
- }
-
- @Override
- public RewrittenPrototypeDescription lookupPrototypeChanges(DexMethod method) {
- return previousLense.lookupPrototypeChanges(method);
- }
-
- @Override
- public DexField lookupField(DexField field) {
- return previousLense.lookupField(field);
- }
-
- @Override
- public boolean isContextFreeForMethods() {
- return false;
- }
-
- @Override
- public String toString() {
- StringBuilder builder = new StringBuilder();
- builder.append("------ BridgeMap ------").append(System.lineSeparator());
- for (Map.Entry<DexMethod, DexMethod> entry : bridgeTargetToBridgeMap.entrySet()) {
- builder.append(entry.getKey().toSourceString()).append(" -> ");
- builder.append(entry.getValue().toSourceString()).append(System.lineSeparator());
- }
- builder.append("-----------------------").append(System.lineSeparator());
- builder.append(previousLense.toString());
- return builder.toString();
- }
- }
-}
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 279d381..f10d000 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -297,11 +297,11 @@
this.options = options;
}
- private void enqueueRootItems(Map<DexDefinition, ProguardKeepRule> items) {
+ private void enqueueRootItems(Map<DexDefinition, Set<ProguardKeepRule>> items) {
items.entrySet().forEach(this::enqueueRootItem);
}
- private void enqueueRootItem(Entry<DexDefinition, ProguardKeepRule> root) {
+ private void enqueueRootItem(Entry<DexDefinition, Set<ProguardKeepRule>> root) {
enqueueRootItem(root.getKey(), root.getValue());
}
@@ -309,7 +309,26 @@
enqueueRootItem(item, KeepReason.dueToKeepRule(rule));
}
+ private void enqueueRootItem(DexDefinition item, Set<ProguardKeepRule> rules) {
+ assert !rules.isEmpty();
+ if (keptGraphConsumer != null) {
+ GraphNode node = getGraphNode(item);
+ for (ProguardKeepRule rule : rules) {
+ registerEdge(node, KeepReason.dueToKeepRule(rule));
+ }
+ }
+ internalEnqueueRootItem(item, KeepReason.dueToKeepRule(rules.iterator().next()));
+ }
+
private void enqueueRootItem(DexDefinition item, KeepReason reason) {
+ if (keptGraphConsumer != null) {
+ registerEdge(getGraphNode(item), reason);
+ }
+ internalEnqueueRootItem(item, reason);
+ }
+
+ private void internalEnqueueRootItem(DexDefinition item, KeepReason reason) {
+ // TODO(b/120959039): do we need to propagate the reason to the action now?
if (item.isDexClass()) {
DexClass clazz = item.asDexClass();
workList.add(Action.markInstantiated(clazz, reason));
@@ -349,11 +368,11 @@
}
private void enqueueHolderIfDependentNonStaticMember(
- DexClass holder, Map<DexDefinition, ProguardKeepRule> dependentItems) {
+ DexClass holder, Map<DexDefinition, Set<ProguardKeepRule>> dependentItems) {
// Check if any dependent members are not static, and in that case enqueue the class as well.
// Having a dependent rule like -keepclassmembers with non static items indicates that class
// instances will be present even if tracing do not find any instantiation. See b/115867670.
- for (Entry<DexDefinition, ProguardKeepRule> entry : dependentItems.entrySet()) {
+ for (Entry<DexDefinition, Set<ProguardKeepRule>> entry : dependentItems.entrySet()) {
DexDefinition dependentItem = entry.getKey();
if (dependentItem.isDexClass()) {
continue;
@@ -791,7 +810,7 @@
annotations.forEach(this::handleAnnotationOfLiveType);
}
- Map<DexDefinition, ProguardKeepRule> dependentItems = rootSet.getDependentItems(holder);
+ Map<DexDefinition, Set<ProguardKeepRule>> dependentItems = rootSet.getDependentItems(holder);
enqueueHolderIfDependentNonStaticMember(holder, dependentItems);
// Add all dependent members to the workqueue.
enqueueRootItems(dependentItems);
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 2f7f54a..e86966f 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -61,7 +61,7 @@
private final AppView<? extends AppInfo> appView;
private final DirectMappedDexApplication application;
private final Collection<ProguardConfigurationRule> rules;
- private final Map<DexDefinition, ProguardKeepRule> noShrinking = new IdentityHashMap<>();
+ private final Map<DexDefinition, Set<ProguardKeepRule>> noShrinking = new IdentityHashMap<>();
private final Set<DexDefinition> noOptimization = Sets.newIdentityHashSet();
private final Set<DexDefinition> noObfuscation = Sets.newIdentityHashSet();
private final LinkedHashMap<DexDefinition, DexDefinition> reasonAsked = new LinkedHashMap<>();
@@ -76,7 +76,7 @@
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 =
+ private final Map<DexDefinition, Map<DexDefinition, Set<ProguardKeepRule>>> dependentNoShrinking =
new IdentityHashMap<>();
private final Map<DexDefinition, ProguardMemberRule> noSideEffects = new IdentityHashMap<>();
private final Map<DexDefinition, ProguardMemberRule> assumedValues = new IdentityHashMap<>();
@@ -854,8 +854,10 @@
return;
}
// Keep the type if the item is also kept.
- dependentNoShrinking.computeIfAbsent(item, x -> new IdentityHashMap<>())
- .put(definition, context);
+ dependentNoShrinking
+ .computeIfAbsent(item, x -> new IdentityHashMap<>())
+ .computeIfAbsent(definition, k -> new HashSet<>())
+ .add(context);
// Unconditionally add to no-obfuscation, as that is only checked for surviving items.
noObfuscation.add(definition);
}
@@ -890,10 +892,12 @@
ProguardKeepRuleModifiers modifiers = keepRule.getModifiers();
if (!modifiers.allowsShrinking) {
if (precondition != null) {
- dependentNoShrinking.computeIfAbsent(precondition, x -> new IdentityHashMap<>())
- .put(item, keepRule);
+ dependentNoShrinking
+ .computeIfAbsent(precondition, x -> new IdentityHashMap<>())
+ .computeIfAbsent(item, i -> new HashSet<>())
+ .add(keepRule);
} else {
- noShrinking.put(item, keepRule);
+ noShrinking.computeIfAbsent(item, i -> new HashSet<>()).add(keepRule);
}
}
if (!modifiers.allowsOptimization) {
@@ -970,7 +974,7 @@
public static class RootSet {
- public final Map<DexDefinition, ProguardKeepRule> noShrinking;
+ public final Map<DexDefinition, Set<ProguardKeepRule>> noShrinking;
public final Set<DexDefinition> noOptimization;
public final Set<DexDefinition> noObfuscation;
public final ImmutableList<DexDefinition> reasonAsked;
@@ -985,12 +989,13 @@
public final Set<DexType> neverMerge;
public final Map<DexDefinition, ProguardMemberRule> noSideEffects;
public final Map<DexDefinition, ProguardMemberRule> assumedValues;
- private final Map<DexDefinition, Map<DexDefinition, ProguardKeepRule>> dependentNoShrinking;
+ private final Map<DexDefinition, Map<DexDefinition, Set<ProguardKeepRule>>>
+ dependentNoShrinking;
public final Set<DexReference> identifierNameStrings;
public final Set<ProguardIfRule> ifRules;
private RootSet(
- Map<DexDefinition, ProguardKeepRule> noShrinking,
+ Map<DexDefinition, Set<ProguardKeepRule>> noShrinking,
Set<DexDefinition> noOptimization,
Set<DexDefinition> noObfuscation,
ImmutableList<DexDefinition> reasonAsked,
@@ -1005,7 +1010,7 @@
Set<DexType> neverMerge,
Map<DexDefinition, ProguardMemberRule> noSideEffects,
Map<DexDefinition, ProguardMemberRule> assumedValues,
- Map<DexDefinition, Map<DexDefinition, ProguardKeepRule>> dependentNoShrinking,
+ Map<DexDefinition, Map<DexDefinition, Set<ProguardKeepRule>>> dependentNoShrinking,
Set<DexReference> identifierNameStrings,
Set<ProguardIfRule> ifRules) {
this.noShrinking = Collections.unmodifiableMap(noShrinking);
@@ -1030,7 +1035,7 @@
// Add dependent items that depend on -if rules.
void addDependentItems(
- Map<DexDefinition, Map<DexDefinition, ProguardKeepRule>> dependentItems) {
+ Map<DexDefinition, Map<DexDefinition, Set<ProguardKeepRule>>> dependentItems) {
dependentItems.forEach(
(def, dependence) ->
dependentNoShrinking
@@ -1038,7 +1043,7 @@
.putAll(dependence));
}
- Map<DexDefinition, ProguardKeepRule> getDependentItems(DexDefinition item) {
+ Map<DexDefinition, Set<ProguardKeepRule>> getDependentItems(DexDefinition item) {
return Collections
.unmodifiableMap(dependentNoShrinking.getOrDefault(item, Collections.emptyMap()));
}
@@ -1224,18 +1229,18 @@
static class ConsequentRootSet {
final Set<DexMethod> neverInline;
final Set<DexType> neverClassInline;
- final Map<DexDefinition, ProguardKeepRule> noShrinking;
+ final Map<DexDefinition, Set<ProguardKeepRule>> noShrinking;
final Set<DexDefinition> noOptimization;
final Set<DexDefinition> noObfuscation;
- final Map<DexDefinition, Map<DexDefinition, ProguardKeepRule>> dependentNoShrinking;
+ final Map<DexDefinition, Map<DexDefinition, Set<ProguardKeepRule>>> dependentNoShrinking;
private ConsequentRootSet(
Set<DexMethod> neverInline,
Set<DexType> neverClassInline,
- Map<DexDefinition, ProguardKeepRule> noShrinking,
+ Map<DexDefinition, Set<ProguardKeepRule>> noShrinking,
Set<DexDefinition> noOptimization,
Set<DexDefinition> noObfuscation,
- Map<DexDefinition, Map<DexDefinition, ProguardKeepRule>> dependentNoShrinking) {
+ Map<DexDefinition, Map<DexDefinition, Set<ProguardKeepRule>>> dependentNoShrinking) {
this.neverInline = Collections.unmodifiableSet(neverInline);
this.neverClassInline = Collections.unmodifiableSet(neverClassInline);
this.noShrinking = Collections.unmodifiableMap(noShrinking);
diff --git a/src/main/java/com/android/tools/r8/utils/ClassMap.java b/src/main/java/com/android/tools/r8/utils/ClassMap.java
index 0ebfb9d..33c1bf3 100644
--- a/src/main/java/com/android/tools/r8/utils/ClassMap.java
+++ b/src/main/java/com/android/tools/r8/utils/ClassMap.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.graph.ClassKind;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexType;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.Iterator;
@@ -134,6 +135,18 @@
return loadedClasses;
}
+ public Map<DexType, DexClass> getAllClassesInMap() {
+ if (classProvider.get() != null) {
+ throw new Unreachable("Getting all classes from not fully loaded collection.");
+ }
+ ImmutableMap.Builder<DexType, DexClass> builder = ImmutableMap.builder();
+ // This is fully loaded, so the class map will no longer change.
+ for (Map.Entry<DexType, Supplier<T>> entry : classes.entrySet()) {
+ builder.put(entry.getKey(), entry.getValue().get());
+ }
+ return builder.build();
+ }
+
public Iterable<DexType> getAllTypes() {
return classes.keySet();
}
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 91bb786..ec12f82 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -10,14 +10,12 @@
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.graph.DexDebugEvent;
-import com.android.tools.r8.graph.DexDebugEvent.AdvanceLine;
import com.android.tools.r8.graph.DexDebugEvent.AdvancePC;
import com.android.tools.r8.graph.DexDebugEvent.Default;
import com.android.tools.r8.graph.DexDebugEvent.EndLocal;
import com.android.tools.r8.graph.DexDebugEvent.RestartLocal;
import com.android.tools.r8.graph.DexDebugEvent.SetEpilogueBegin;
import com.android.tools.r8.graph.DexDebugEvent.SetFile;
-import com.android.tools.r8.graph.DexDebugEvent.SetInlineFrame;
import com.android.tools.r8.graph.DexDebugEvent.SetPrologueEnd;
import com.android.tools.r8.graph.DexDebugEvent.StartLocal;
import com.android.tools.r8.graph.DexDebugEventBuilder;
@@ -401,56 +399,45 @@
new PositionEventEmitter(application.dexItemFactory, method.method, processedEvents);
// Debug event visitor to map line numbers.
- // TODO(117268618): Cleanup the duplicate pc tracking.
DexDebugEventVisitor visitor =
- new DexDebugEventVisitor() {
- DexDebugPositionState state =
- new DexDebugPositionState(debugInfo.startLine, method.method);
- int currentPc = 0;
+ new DexDebugPositionState(debugInfo.startLine, method.method) {
+ // Keep track of what PC has been emitted.
+ private int emittedPc = 0;
+
+ // Force the current PC to emitted.
private void flushPc() {
- if (currentPc != state.getCurrentPc()) {
- positionEventEmitter.emitAdvancePc(state.getCurrentPc());
- currentPc = state.getCurrentPc();
+ if (emittedPc != getCurrentPc()) {
+ positionEventEmitter.emitAdvancePc(getCurrentPc());
+ emittedPc = getCurrentPc();
}
}
- @Override
- public void visit(AdvancePC advancePC) {
- state.visit(advancePC);
- }
-
- @Override
- public void visit(AdvanceLine advanceLine) {
- state.visit(advanceLine);
- }
-
- @Override
- public void visit(SetInlineFrame setInlineFrame) {
- state.visit(setInlineFrame);
- }
-
+ // A default event denotes a line table entry and must always be emitted. Remap its line.
@Override
public void visit(Default defaultEvent) {
- state.visit(defaultEvent);
- int currentLine = state.getCurrentLine();
- assert currentLine >= 0;
+ super.visit(defaultEvent);
+ assert getCurrentLine() >= 0;
Position position =
positionRemapper.createRemappedPosition(
- state.getCurrentLine(),
- state.getCurrentFile(),
- state.getCurrentMethod(),
- state.getCurrentCallerPosition());
+ getCurrentLine(),
+ getCurrentFile(),
+ getCurrentMethod(),
+ getCurrentCallerPosition());
mappedPositions.add(
new MappedPosition(
- state.getCurrentMethod(),
- currentLine,
- state.getCurrentCallerPosition(),
+ getCurrentMethod(),
+ getCurrentLine(),
+ getCurrentCallerPosition(),
position.line));
- positionEventEmitter.emitPositionEvents(state.getCurrentPc(), position);
- currentPc = state.getCurrentPc();
+ positionEventEmitter.emitPositionEvents(getCurrentPc(), position);
+ emittedPc = getCurrentPc();
}
+ // Non-materializing events use super, ie, AdvancePC, AdvanceLine and SetInlineFrame.
+
+ // Materializing events are just amended to the stream.
+
@Override
public void visit(SetFile setFile) {
processedEvents.add(setFile);
@@ -466,6 +453,8 @@
processedEvents.add(setEpilogueBegin);
}
+ // Local changes must force flush the PC ensuing they pertain to the correct point.
+
@Override
public void visit(StartLocal startLocal) {
flushPc();
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 bde7016..fb4f409 100644
--- a/src/main/java/com/android/tools/r8/utils/ThreadUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ThreadUtils.java
@@ -15,11 +15,7 @@
public static void awaitFutures(Iterable<? extends Future<?>> futures)
throws ExecutionException {
- awaitFutures(futures.iterator());
- }
-
- public static void awaitFutures(Iterator<? extends Future<?>> futureIterator)
- throws ExecutionException {
+ Iterator<? extends Future<?>> futureIterator = futures.iterator();
try {
while (futureIterator.hasNext()) {
futureIterator.next().get();
diff --git a/src/test/java/com/android/tools/r8/D8TestCompileResult.java b/src/test/java/com/android/tools/r8/D8TestCompileResult.java
index 79fb293..1e40a0b 100644
--- a/src/test/java/com/android/tools/r8/D8TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/D8TestCompileResult.java
@@ -7,12 +7,17 @@
import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.utils.AndroidApp;
-public class D8TestCompileResult extends TestCompileResult<D8TestRunResult> {
+public class D8TestCompileResult extends TestCompileResult<D8TestCompileResult, D8TestRunResult> {
D8TestCompileResult(TestState state, AndroidApp app) {
super(state, app);
}
@Override
+ public D8TestCompileResult self() {
+ return this;
+ }
+
+ @Override
public Backend getBackend() {
return Backend.DEX;
}
@@ -23,7 +28,7 @@
}
@Override
- public D8TestRunResult createRunResult(AndroidApp app, ProcessResult result) {
+ public D8TestRunResult createRunResult(ProcessResult result) {
return new D8TestRunResult(app, result);
}
}
diff --git a/src/test/java/com/android/tools/r8/DXTestCompileResult.java b/src/test/java/com/android/tools/r8/DXTestCompileResult.java
index da6a17a..e339440 100644
--- a/src/test/java/com/android/tools/r8/DXTestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/DXTestCompileResult.java
@@ -7,13 +7,18 @@
import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.utils.AndroidApp;
-public class DXTestCompileResult extends TestCompileResult<DXTestRunResult> {
+public class DXTestCompileResult extends TestCompileResult<DXTestCompileResult, DXTestRunResult> {
DXTestCompileResult(TestState state, AndroidApp app) {
super(state, app);
}
@Override
+ public DXTestCompileResult self() {
+ return this;
+ }
+
+ @Override
public Backend getBackend() {
return Backend.DEX;
}
@@ -24,7 +29,7 @@
}
@Override
- public DXTestRunResult createRunResult(AndroidApp app, ProcessResult result) {
+ public DXTestRunResult createRunResult(ProcessResult result) {
return new DXTestRunResult(app, result);
}
}
diff --git a/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java b/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java
new file mode 100644
index 0000000..3b5ddd8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java
@@ -0,0 +1,184 @@
+// 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 static com.android.tools.r8.ToolHelper.getJavaExecutable;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.R8Command.Builder;
+import com.android.tools.r8.TestBase.Backend;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.base.Charsets;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+// The type arguments R8Command, Builder is not relevant for running external R8.
+public class ExternalR8TestBuilder
+ extends TestShrinkerBuilder<
+ R8Command,
+ Builder,
+ ExternalR8TestCompileResult,
+ ExternalR8TestRunResult,
+ ExternalR8TestBuilder> {
+
+ // The r8.jar to run.
+ private Path r8jar = ToolHelper.R8_JAR;
+
+ // Ordered list of program jar entries.
+ private final List<Path> programJars = new ArrayList<>();
+
+ // Ordered list of library jar entries.
+ private final List<Path> libJars = new ArrayList<>();
+
+ // Proguard configuration file lines.
+ private final List<String> config = new ArrayList<>();
+
+ // Additional Proguard configuration files.
+ private List<Path> proguardConfigFiles = new ArrayList<>();
+
+ private ExternalR8TestBuilder(TestState state, Builder builder, Backend backend) {
+ super(state, builder, backend);
+ }
+
+ public static ExternalR8TestBuilder create(TestState state, Backend backend) {
+ return new ExternalR8TestBuilder(state, R8Command.builder(), backend);
+ }
+
+ @Override
+ ExternalR8TestBuilder self() {
+ return this;
+ }
+
+ @Override
+ ExternalR8TestCompileResult internalCompile(
+ Builder builder, Consumer<InternalOptions> optionsConsumer, Supplier<AndroidApp> app)
+ throws CompilationFailedException {
+ try {
+ Path outputFolder = getState().getNewTempFolder();
+ Path outputJar = outputFolder.resolve("output.jar");
+ Path proguardMapFile = outputFolder.resolve("output.jar.map");
+
+ List<String> command = new ArrayList<>();
+ Collections.addAll(
+ command,
+ getJavaExecutable(),
+ "-cp",
+ r8jar.toAbsolutePath().toString(),
+ R8.class.getTypeName(),
+ "--output",
+ outputJar.toAbsolutePath().toString(),
+ "--pg-map-output",
+ proguardMapFile.toString(),
+ backend == Backend.CF ? "--classfile" : "--dex",
+ builder.getMode() == CompilationMode.DEBUG ? "--debug" : "--release");
+ if (!config.isEmpty()) {
+ Path proguardConfigFile = outputFolder.resolve("proguard-config.txt");
+ FileUtils.writeTextFile(proguardConfigFile, config);
+ command.add("--pg-conf");
+ command.add(proguardConfigFile.toAbsolutePath().toString());
+ }
+ for (Path proguardConfigFile : proguardConfigFiles) {
+ command.add("--pg-conf");
+ command.add(proguardConfigFile.toAbsolutePath().toString());
+ }
+ if (libJars.isEmpty()) {
+ command.add("--lib");
+ command.add(TestBase.runtimeJar(backend).toAbsolutePath().toString());
+ } else {
+ for (Path libJar : libJars) {
+ command.add("--lib");
+ command.add(libJar.toAbsolutePath().toString());
+ }
+ }
+ command.addAll(programJars.stream().map(Path::toString).collect(Collectors.toList()));
+
+ ProcessBuilder processBuilder = new ProcessBuilder(command);
+ ProcessResult processResult = ToolHelper.runProcess(processBuilder);
+ assertEquals(processResult.stderr, 0, processResult.exitCode);
+ String proguardMap =
+ proguardMapFile.toFile().exists()
+ ? FileUtils.readTextFile(proguardMapFile, Charsets.UTF_8)
+ : "";
+ return new ExternalR8TestCompileResult(getState(), outputJar, processResult, proguardMap);
+ } catch (IOException e) {
+ throw new CompilationFailedException(e);
+ }
+ }
+
+ @Override
+ public ExternalR8TestBuilder addProgramClasses(Collection<Class<?>> classes) {
+ // Adding a collection of classes will build a jar of exactly those classes so that no other
+ // classes are made available via a too broad classpath directory.
+ try {
+ Path programJar = getState().getNewTempFolder().resolve("input.jar");
+ ClassFileConsumer inputConsumer = new ClassFileConsumer.ArchiveConsumer(programJar);
+ for (Class<?> clazz : classes) {
+ String descriptor = DescriptorUtils.javaTypeToDescriptor(clazz.getName());
+ inputConsumer.accept(ByteDataView.of(ToolHelper.getClassAsBytes(clazz)), descriptor, null);
+ }
+ inputConsumer.finished(null);
+ programJars.add(programJar);
+ return self();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public ExternalR8TestBuilder addProgramFiles(Collection<Path> files) {
+ for (Path file : files) {
+ if (FileUtils.isJarFile(file)) {
+ programJars.add(file);
+ } else {
+ throw new Unimplemented("No support for adding paths directly");
+ }
+ }
+ return self();
+ }
+
+ @Override
+ public ExternalR8TestBuilder addProgramClassFileData(Collection<byte[]> classes) {
+ throw new Unimplemented("No support for adding classfile data directly");
+ }
+
+ @Override
+ public ExternalR8TestBuilder addLibraryFiles(Collection<Path> files) {
+ libJars.addAll(files);
+ return self();
+ }
+
+ @Override
+ public ExternalR8TestBuilder addKeepRuleFiles(List<Path> proguardConfigFiles) {
+ this.proguardConfigFiles.addAll(proguardConfigFiles);
+ return self();
+ }
+
+ @Override
+ public ExternalR8TestBuilder addKeepRules(Collection<String> rules) {
+ config.addAll(rules);
+ return self();
+ }
+
+ public ExternalR8TestBuilder useR8WithRelocatedDeps() {
+ return useProvidedR8(ToolHelper.R8_WITH_RELOCATED_DEPS_JAR);
+ }
+
+ public ExternalR8TestBuilder useProvidedR8(Path r8jar) {
+ this.r8jar = r8jar;
+ return self();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ExternalR8TestCompileResult.java b/src/test/java/com/android/tools/r8/ExternalR8TestCompileResult.java
new file mode 100644
index 0000000..f2a8839
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ExternalR8TestCompileResult.java
@@ -0,0 +1,67 @@
+// 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 com.android.tools.r8.TestBase.Backend;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.concurrent.ExecutionException;
+
+public class ExternalR8TestCompileResult
+ extends TestCompileResult<ExternalR8TestCompileResult, ExternalR8TestRunResult> {
+
+ private final Path outputJar;
+ private final ProcessResult processResult;
+ private final String proguardMap;
+
+ protected ExternalR8TestCompileResult(
+ TestState state, Path outputJar, ProcessResult processResult, String proguardMap) {
+ super(state, AndroidApp.builder().addProgramFiles(outputJar).build());
+ assert processResult.exitCode == 0;
+ this.outputJar = outputJar;
+ this.processResult = processResult;
+ this.proguardMap = proguardMap;
+ }
+
+ public Path outputJar() {
+ return outputJar;
+ }
+
+ public String stdout() {
+ return processResult.stdout;
+ }
+
+ public String stderr() {
+ return processResult.stdout;
+ }
+
+ @Override
+ public ExternalR8TestCompileResult self() {
+ return this;
+ }
+
+ @Override
+ public Backend getBackend() {
+ return null;
+ }
+
+ @Override
+ public TestDiagnosticMessages getDiagnosticMessages() {
+ throw new UnsupportedOperationException("No diagnostics messages from external R8");
+ }
+
+ @Override
+ public CodeInspector inspector() throws IOException, ExecutionException {
+ return new CodeInspector(app, proguardMap);
+ }
+
+ @Override
+ protected ExternalR8TestRunResult createRunResult(ProcessResult result) {
+ return new ExternalR8TestRunResult(app, outputJar, proguardMap, result);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ExternalR8TestRunResult.java b/src/test/java/com/android/tools/r8/ExternalR8TestRunResult.java
new file mode 100644
index 0000000..f301b78
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ExternalR8TestRunResult.java
@@ -0,0 +1,44 @@
+// 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 static org.junit.Assert.assertNotNull;
+
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.concurrent.ExecutionException;
+
+public class ExternalR8TestRunResult extends TestRunResult<ExternalR8TestRunResult> {
+
+ private final Path outputJar;
+ private final String proguardMap;
+
+ public ExternalR8TestRunResult(
+ AndroidApp app, Path outputJar, String proguardMap, ProcessResult result) {
+ super(app, result);
+ this.outputJar = outputJar;
+ this.proguardMap = proguardMap;
+ }
+
+ public Path outputJar() {
+ return outputJar;
+ }
+
+ @Override
+ protected ExternalR8TestRunResult self() {
+ return this;
+ }
+
+ @Override
+ public CodeInspector inspector() throws IOException, ExecutionException {
+ // See comment in base class.
+ assertSuccess();
+ assertNotNull(app);
+ return new CodeInspector(app, proguardMap);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ForceInline.java b/src/test/java/com/android/tools/r8/ForceInline.java
index 4a74233..dfeace7 100644
--- a/src/test/java/com/android/tools/r8/ForceInline.java
+++ b/src/test/java/com/android/tools/r8/ForceInline.java
@@ -3,5 +3,8 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8;
-public @interface ForceInline {
-}
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+@Target({ElementType.METHOD})
+public @interface ForceInline {}
diff --git a/src/test/java/com/android/tools/r8/ProguardTestBuilder.java b/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
index 2e4b1bd..ea482fa 100644
--- a/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
@@ -98,9 +98,13 @@
// Ordered list of injar entries.
private List<Path> injars = new ArrayList<>();
+
// Proguard configuration file lines.
private List<String> config = new ArrayList<>();
+ // Additional Proguard configuration files.
+ private List<Path> proguardConfigFiles = new ArrayList<>();
+
private ProguardTestBuilder(TestState state, Builder builder, Backend backend) {
super(state, builder, backend);
}
@@ -138,6 +142,10 @@
command.add(ToolHelper.getJava8RuntimeJar().toString());
command.add("-include");
command.add(configFile.toString());
+ for (Path proguardConfigFile : proguardConfigFiles) {
+ command.add("-include");
+ command.add(proguardConfigFile.toAbsolutePath().toString());
+ }
command.add("-outjar");
command.add(outJar.toString());
command.add("-printmapping");
@@ -153,11 +161,9 @@
if (result.exitCode != 0) {
throw new CompilationFailedException(result.toString());
}
- AndroidApp.Builder aaabuilder = AndroidApp.builder();
- aaabuilder.addProgramFiles(outJar);
String proguardMap =
Files.exists(mapFile) ? FileUtils.readTextFile(mapFile, Charsets.UTF_8) : "";
- return new ProguardTestCompileResult(getState(), aaabuilder.build(), proguardMap);
+ return new ProguardTestCompileResult(getState(), outJar, proguardMap);
} catch (IOException e) {
throw new CompilationFailedException(e);
}
@@ -203,14 +209,8 @@
}
@Override
- public ProguardTestBuilder addKeepRuleFiles(List<Path> files) {
- try {
- for (Path file : files) {
- config.addAll(FileUtils.readAllLines(file));
- }
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
+ public ProguardTestBuilder addKeepRuleFiles(List<Path> proguardConfigFiles) {
+ this.proguardConfigFiles.addAll(proguardConfigFiles);
return self();
}
diff --git a/src/test/java/com/android/tools/r8/ProguardTestCompileResult.java b/src/test/java/com/android/tools/r8/ProguardTestCompileResult.java
index 4ea8953..6f47adb 100644
--- a/src/test/java/com/android/tools/r8/ProguardTestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/ProguardTestCompileResult.java
@@ -8,17 +8,30 @@
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import java.io.IOException;
+import java.nio.file.Path;
import java.util.concurrent.ExecutionException;
-public class ProguardTestCompileResult extends TestCompileResult<ProguardTestRunResult> {
+public class ProguardTestCompileResult
+ extends TestCompileResult<ProguardTestCompileResult, ProguardTestRunResult> {
+ private final Path outputJar;
private final String proguardMap;
- ProguardTestCompileResult(TestState state, AndroidApp app, String proguardMap) {
- super(state, app);
+ ProguardTestCompileResult(TestState state, Path outputJar, String proguardMap) {
+ super(state, AndroidApp.builder().addProgramFiles(outputJar).build());
+ this.outputJar = outputJar;
this.proguardMap = proguardMap;
}
+ public Path outputJar() {
+ return outputJar;
+ }
+
+ @Override
+ public ProguardTestCompileResult self() {
+ return this;
+ }
+
@Override
public Backend getBackend() {
return Backend.CF;
@@ -26,7 +39,7 @@
@Override
public TestDiagnosticMessages getDiagnosticMessages() {
- throw new UnsupportedOperationException("No diagnostics messages from dx");
+ throw new UnsupportedOperationException("No diagnostics messages from Proguard");
}
@Override
@@ -35,7 +48,7 @@
}
@Override
- public ProguardTestRunResult createRunResult(AndroidApp app, ProcessResult result) {
+ public ProguardTestRunResult createRunResult(ProcessResult result) {
return new ProguardTestRunResult(app, result, proguardMap);
}
}
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java b/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
index 195e01c..7d6af00 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.ToolHelper.DexVm;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
import com.android.tools.r8.utils.TestDescriptionWatcher;
import com.google.common.collect.ImmutableList;
@@ -80,8 +81,8 @@
private final CompilationMode mode;
private final String pkg;
private final String mainClass;
- private final Frontend frontend;
- private final Output output;
+ protected final Frontend frontend;
+ protected final Output output;
public R8RunExamplesCommon(
String pkg,
@@ -174,16 +175,7 @@
.setDisableMinification(true)
.addProguardConfiguration(ImmutableList.of("-keepattributes *"), Origin.unknown())
.build();
- ToolHelper.runR8(
- command,
- options -> {
- options.lineNumberOptimization = LineNumberOptimization.OFF;
- options.enableCfFrontend = frontend == Frontend.CF;
- if (output == Output.CF) {
- // Class inliner is not supported with CF backend yet.
- options.enableClassInlining = false;
- }
- });
+ ToolHelper.runR8(command, this::configure);
break;
}
default:
@@ -191,6 +183,15 @@
}
}
+ protected void configure(InternalOptions options) {
+ options.lineNumberOptimization = LineNumberOptimization.OFF;
+ options.enableCfFrontend = frontend == Frontend.CF;
+ if (output == Output.CF) {
+ // Class inliner is not supported with CF backend yet.
+ options.enableClassInlining = false;
+ }
+ }
+
private boolean shouldCompileFail() {
if (output == Output.CF && getFailingCompileCf().contains(mainClass)) {
return true;
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesKotlinTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesKotlinTest.java
index 4451910..c2c0869 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesKotlinTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesKotlinTest.java
@@ -4,6 +4,7 @@
package com.android.tools.r8;
import com.android.tools.r8.R8RunArtTestsTest.CompilerUnderTest;
+import com.android.tools.r8.utils.InternalOptions;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -17,6 +18,15 @@
@RunWith(Parameterized.class)
public class R8RunExamplesKotlinTest extends R8RunExamplesCommon {
+ @Override
+ protected void configure(InternalOptions options) {
+ options.enableCfFrontend = frontend == Frontend.CF;
+ if (output == Output.CF) {
+ // Class inliner is not supported with CF backend yet.
+ options.enableClassInlining = false;
+ }
+ }
+
@Parameters(name = "{0}_{1}_{2}_{3}_{5}_{6}")
public static Collection<String[]> data() {
String[] tests = {
diff --git a/src/test/java/com/android/tools/r8/R8TestCompileResult.java b/src/test/java/com/android/tools/r8/R8TestCompileResult.java
index ef5bfb1..8a4c44d 100644
--- a/src/test/java/com/android/tools/r8/R8TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/R8TestCompileResult.java
@@ -12,7 +12,7 @@
import java.io.IOException;
import java.util.concurrent.ExecutionException;
-public class R8TestCompileResult extends TestCompileResult<R8TestRunResult> {
+public class R8TestCompileResult extends TestCompileResult<R8TestCompileResult, R8TestRunResult> {
private final Backend backend;
private final String proguardMap;
@@ -31,6 +31,11 @@
}
@Override
+ public R8TestCompileResult self() {
+ return this;
+ }
+
+ @Override
public Backend getBackend() {
return backend;
}
@@ -51,7 +56,7 @@
}
@Override
- public R8TestRunResult createRunResult(AndroidApp app, ProcessResult result) {
+ public R8TestRunResult createRunResult(ProcessResult result) {
return new R8TestRunResult(app, result, proguardMap, this::graphInspector);
}
}
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 0783413..e0baab1 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -83,6 +83,10 @@
return R8TestBuilder.create(new TestState(temp), backend, R8Mode.Compat);
}
+ public static ExternalR8TestBuilder testForExternalR8(TemporaryFolder temp, Backend backend) {
+ return ExternalR8TestBuilder.create(new TestState(temp), backend);
+ }
+
public static D8TestBuilder testForD8(TemporaryFolder temp) {
return D8TestBuilder.create(new TestState(temp));
}
@@ -107,6 +111,10 @@
return testForR8Compat(temp, backend);
}
+ public ExternalR8TestBuilder testForExternalR8(Backend backend) {
+ return testForExternalR8(temp, backend);
+ }
+
public D8TestBuilder testForD8() {
return testForD8(temp);
}
diff --git a/src/test/java/com/android/tools/r8/TestCompileResult.java b/src/test/java/com/android/tools/r8/TestCompileResult.java
index 4a31320..5d5229b 100644
--- a/src/test/java/com/android/tools/r8/TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/TestCompileResult.java
@@ -24,7 +24,8 @@
import java.util.concurrent.ExecutionException;
import org.hamcrest.Matcher;
-public abstract class TestCompileResult<RR extends TestRunResult> {
+public abstract class TestCompileResult<
+ CR extends TestCompileResult<CR, RR>, RR extends TestRunResult> {
final TestState state;
public final AndroidApp app;
@@ -34,11 +35,13 @@
this.app = app;
}
+ public abstract CR self();
+
public abstract Backend getBackend();
public abstract TestDiagnosticMessages getDiagnosticMessages();
- protected abstract RR createRunResult(AndroidApp add, ProcessResult result);
+ protected abstract RR createRunResult(ProcessResult result);
public RR run(Class<?> mainClass) throws IOException {
return run(mainClass.getTypeName());
@@ -55,47 +58,46 @@
}
}
- public TestCompileResult writeToZip(Path file) throws IOException {
+ public CR writeToZip(Path file) throws IOException {
app.writeToZip(file, getBackend() == DEX ? OutputMode.DexIndexed : OutputMode.ClassFile);
- return this;
+ return self();
}
public CodeInspector inspector() throws IOException, ExecutionException {
return new CodeInspector(app);
}
- public TestCompileResult<RR> inspect(Consumer<CodeInspector> consumer)
- throws IOException, ExecutionException {
+ public CR inspect(Consumer<CodeInspector> consumer) throws IOException, ExecutionException {
consumer.accept(inspector());
- return this;
+ return self();
}
- public TestCompileResult<RR> assertNoMessages() {
+ public CR assertNoMessages() {
assertEquals(0, getDiagnosticMessages().getInfos().size());
assertEquals(0, getDiagnosticMessages().getWarnings().size());
assertEquals(0, getDiagnosticMessages().getErrors().size());
- return this;
+ return self();
}
- public TestCompileResult<RR> assertOnlyInfos() {
+ public CR assertOnlyInfos() {
assertNotEquals(0, getDiagnosticMessages().getInfos().size());
assertEquals(0, getDiagnosticMessages().getWarnings().size());
assertEquals(0, getDiagnosticMessages().getErrors().size());
- return this;
+ return self();
}
- public TestCompileResult<RR> assertOnlyWarnings() {
+ public CR assertOnlyWarnings() {
assertEquals(0, getDiagnosticMessages().getInfos().size());
assertNotEquals(0, getDiagnosticMessages().getWarnings().size());
assertEquals(0, getDiagnosticMessages().getErrors().size());
- return this;
+ return self();
}
- public TestCompileResult<RR> assertWarningMessageThatMatches(Matcher<String> matcher) {
+ public CR assertWarningMessageThatMatches(Matcher<String> matcher) {
assertNotEquals(0, getDiagnosticMessages().getWarnings().size());
for (int i = 0; i < getDiagnosticMessages().getWarnings().size(); i++) {
if (matcher.matches(getDiagnosticMessages().getWarnings().get(i).getDiagnosticMessage())) {
- return this;
+ return self();
}
}
StringBuilder builder = new StringBuilder("No warning matches " + matcher.toString());
@@ -111,10 +113,10 @@
}
}
fail(builder.toString());
- return this;
+ return self();
}
- public TestCompileResult<RR> assertNoWarningMessageThatMatches(Matcher<String> matcher) {
+ public CR assertNoWarningMessageThatMatches(Matcher<String> matcher) {
assertNotEquals(0, getDiagnosticMessages().getWarnings().size());
for (int i = 0; i < getDiagnosticMessages().getWarnings().size(); i++) {
String message = getDiagnosticMessages().getWarnings().get(i).getDiagnosticMessage();
@@ -122,15 +124,15 @@
fail("The warning: \"" + message + "\" + matches " + matcher + ".");
}
}
- return this;
+ return self();
}
- public TestCompileResult<RR> disassemble(PrintStream ps) throws IOException, ExecutionException {
+ public CR disassemble(PrintStream ps) throws IOException, ExecutionException {
ToolHelper.disassemble(app, ps);
- return this;
+ return self();
}
- public TestCompileResult<RR> disassemble() throws IOException, ExecutionException {
+ public CR disassemble() throws IOException, ExecutionException {
return disassemble(System.out);
}
@@ -162,14 +164,14 @@
Path out = state.getNewTempFolder().resolve("out.zip");
app.writeToZip(out, OutputMode.ClassFile);
ProcessResult result = ToolHelper.runJava(out, mainClass);
- return createRunResult(app, result);
+ return createRunResult(result);
}
private RR runArt(String mainClass) throws IOException {
Path out = state.getNewTempFolder().resolve("out.zip");
app.writeToZip(out, OutputMode.DexIndexed);
ProcessResult result = ToolHelper.runArtRaw(out.toString(), mainClass);
- return createRunResult(app, result);
+ return createRunResult(result);
}
public Dex2OatTestRunResult runDex2Oat() throws IOException {
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index e9ebd4e..8276a06 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -21,7 +21,7 @@
public abstract class TestCompilerBuilder<
C extends BaseCompilerCommand,
B extends BaseCompilerCommand.Builder<C, B>,
- CR extends TestCompileResult<RR>,
+ CR extends TestCompileResult<CR, RR>,
RR extends TestRunResult,
T extends TestCompilerBuilder<C, B, CR, RR, T>>
extends TestBuilder<RR, T> {
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index 81f0407..9d8e03c 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -16,7 +16,7 @@
public abstract class TestShrinkerBuilder<
C extends BaseCompilerCommand,
B extends BaseCompilerCommand.Builder<C, B>,
- CR extends TestCompileResult<RR>,
+ CR extends TestCompileResult<CR, RR>,
RR extends TestRunResult,
T extends TestCompilerBuilder<C, B, CR, RR, T>>
extends TestCompilerBuilder<C, B, CR, RR, T> {
diff --git a/src/test/java/com/android/tools/r8/cf/BootstrapCurrentEqualityTest.java b/src/test/java/com/android/tools/r8/cf/BootstrapCurrentEqualityTest.java
index a0cd03d..aeeb0ec 100644
--- a/src/test/java/com/android/tools/r8/cf/BootstrapCurrentEqualityTest.java
+++ b/src/test/java/com/android/tools/r8/cf/BootstrapCurrentEqualityTest.java
@@ -10,14 +10,13 @@
import com.android.tools.r8.ArchiveClassFileProvider;
import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.ExternalR8TestCompileResult;
import com.android.tools.r8.OutputMode;
import com.android.tools.r8.R8;
import com.android.tools.r8.R8Command;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.utils.FileUtils;
-import com.google.common.base.Charsets;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
@@ -34,12 +33,11 @@
import org.junit.rules.TemporaryFolder;
/**
- * This test relies on a freshly built builds/libs/r8lib_with_deps.jar. If this test fails
- * remove build directory and rebuild r8lib_with_deps by calling test.py or gradle r8libWithdeps.
+ * This test relies on a freshly built build/libs/r8lib_with_deps.jar. If this test fails remove
+ * build directory and rebuild r8lib_with_deps by calling test.py or gradle r8libWithdeps.
*/
public class BootstrapCurrentEqualityTest extends TestBase {
- private static final String R8_NAME = "com.android.tools.r8.R8";
private static final Path MAIN_KEEP = Paths.get("src/main/keep.txt");
private static final String HELLO_NAME = "hello.Hello";
@@ -47,24 +45,6 @@
"-keep class " + HELLO_NAME + " {", " public static void main(...);", "}",
};
- private static class R8Result {
-
- final ProcessResult processResult;
- final Path outputJar;
- final String pgMap;
-
- R8Result(ProcessResult processResult, Path outputJar, String pgMap) {
- this.processResult = processResult;
- this.outputJar = outputJar;
- this.pgMap = pgMap;
- }
-
- @Override
- public String toString() {
- return processResult.toString() + "\n\n" + pgMap;
- }
- }
-
private static Path r8R8Debug;
private static Path r8R8Release;
@@ -74,22 +54,22 @@
@BeforeClass
public static void beforeAll() throws Exception {
- r8R8Debug = compileR8("--debug");
- r8R8Release = compileR8("--release");
+ r8R8Debug = compileR8(CompilationMode.DEBUG);
+ r8R8Release = compileR8(CompilationMode.RELEASE);
}
- private static Path compileR8(String mode) throws Exception {
+ private static Path compileR8(CompilationMode mode) throws Exception {
// Run R8 on r8.jar.
Path jar;
if (testExternal) {
- R8Result output =
- runExternalR8(
- ToolHelper.R8_WITH_RELOCATED_DEPS_JAR,
- ToolHelper.R8_WITH_RELOCATED_DEPS_JAR,
- testFolder.newFolder().toPath(),
- MAIN_KEEP,
- mode);
- jar = output.outputJar;
+ jar =
+ testForExternalR8(newTempFolder(), Backend.CF)
+ .useR8WithRelocatedDeps()
+ .addProgramFiles(ToolHelper.R8_WITH_RELOCATED_DEPS_JAR)
+ .addKeepRuleFiles(MAIN_KEEP)
+ .setMode(mode)
+ .compile()
+ .outputJar();
} else {
jar = testFolder.newFolder().toPath().resolve("out.zip");
R8.run(
@@ -124,66 +104,48 @@
private void compareR8(Path program, ProcessResult runResult, String[] keep, String... args)
throws Exception {
- R8Result runR8Debug =
- runExternalR8(
- ToolHelper.R8_WITH_RELOCATED_DEPS_JAR,
- program,
- temp.newFolder().toPath(),
- keep,
- "--debug");
- assertEquals(runResult.toString(), ToolHelper.runJava(runR8Debug.outputJar, args).toString());
- R8Result runR8Release =
- runExternalR8(
- ToolHelper.R8_WITH_RELOCATED_DEPS_JAR,
- program,
- temp.newFolder().toPath(),
- keep,
- "--release");
- assertEquals(runResult.toString(), ToolHelper.runJava(runR8Release.outputJar, args).toString());
- RunR8AndCheck(r8R8Debug, program, runR8Debug, keep, "--debug");
- RunR8AndCheck(r8R8Debug, program, runR8Release, keep, "--release");
- RunR8AndCheck(r8R8Release, program, runR8Debug, keep, "--debug");
- RunR8AndCheck(r8R8Release, program, runR8Release, keep, "--release");
+ ExternalR8TestCompileResult runR8Debug =
+ testForExternalR8(newTempFolder(), Backend.CF)
+ .useR8WithRelocatedDeps()
+ .addProgramFiles(program)
+ .addKeepRules(keep)
+ .setMode(CompilationMode.DEBUG)
+ .compile();
+ assertEquals(runResult.toString(), ToolHelper.runJava(runR8Debug.outputJar(), args).toString());
+ ExternalR8TestCompileResult runR8Release =
+ testForExternalR8(newTempFolder(), Backend.CF)
+ .useR8WithRelocatedDeps()
+ .addProgramFiles(program)
+ .addKeepRules(keep)
+ .setMode(CompilationMode.RELEASE)
+ .compile();
+ assertEquals(
+ runResult.toString(), ToolHelper.runJava(runR8Release.outputJar(), args).toString());
+ RunR8AndCheck(r8R8Debug, program, runR8Debug, keep, CompilationMode.DEBUG);
+ RunR8AndCheck(r8R8Debug, program, runR8Release, keep, CompilationMode.RELEASE);
+ RunR8AndCheck(r8R8Release, program, runR8Debug, keep, CompilationMode.DEBUG);
+ RunR8AndCheck(r8R8Release, program, runR8Release, keep, CompilationMode.RELEASE);
}
- private void RunR8AndCheck(Path r8, Path program, R8Result result, String[] keep, String mode)
+ private void RunR8AndCheck(
+ Path r8,
+ Path program,
+ ExternalR8TestCompileResult result,
+ String[] keep,
+ CompilationMode mode)
throws Exception {
- R8Result runR8R8 = runExternalR8(r8, program, temp.newFolder().toPath(), keep, mode);
+ ExternalR8TestCompileResult runR8R8 =
+ testForExternalR8(newTempFolder(), Backend.CF)
+ .useProvidedR8(r8)
+ .addProgramFiles(program)
+ .addKeepRules(keep)
+ .setMode(mode)
+ .compile();
// Check that the process outputs (exit code, stdout, stderr) are the same.
- assertEquals(result.toString(), runR8R8.toString());
+ assertEquals(result.stdout(), runR8R8.stdout());
+ assertEquals(result.stderr(), runR8R8.stderr());
// Check that the output jars are the same.
- assertProgramsEqual(result.outputJar, runR8R8.outputJar);
- }
-
- private static R8Result runExternalR8(
- Path r8Jar, Path inputJar, Path output, String[] keepRules, String mode) throws Exception {
- Path pgConfigFile = output.resolve("keep.rules");
- FileUtils.writeTextFile(pgConfigFile, keepRules);
- return runExternalR8(r8Jar, inputJar, output, pgConfigFile, mode);
- }
-
- private static R8Result runExternalR8(
- Path r8Jar, Path inputJar, Path output, Path keepRules, String mode) throws Exception {
- Path outputJar = output.resolve("output.jar");
- Path pgMapFile = output.resolve("output.jar.map");
- ProcessResult processResult =
- ToolHelper.runJava(
- r8Jar,
- R8_NAME,
- "--lib",
- ToolHelper.JAVA_8_RUNTIME,
- "--classfile",
- inputJar.toString(),
- "--output",
- outputJar.toString(),
- "--pg-conf",
- keepRules.toString(),
- mode,
- "--pg-map-output",
- pgMapFile.toString());
- assertEquals(processResult.stderr, 0, processResult.exitCode);
- String pgMap = FileUtils.readTextFile(pgMapFile, Charsets.UTF_8);
- return new R8Result(processResult, outputJar, pgMap);
+ assertProgramsEqual(result.outputJar(), runR8R8.outputJar());
}
private static void assertProgramsEqual(Path expectedJar, Path actualJar) throws Exception {
@@ -234,4 +196,10 @@
throws Exception {
return toByteArray(inputJar.getProgramResource(descriptor).getByteStream());
}
+
+ private static TemporaryFolder newTempFolder() throws IOException {
+ TemporaryFolder tempFolder = new TemporaryFolder(testFolder.newFolder());
+ tempFolder.create();
+ return tempFolder;
+ }
}
diff --git a/src/test/java/com/android/tools/r8/cf/CloserTest.java b/src/test/java/com/android/tools/r8/cf/CloserTest.java
new file mode 100644
index 0000000..874eb8a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/CloserTest.java
@@ -0,0 +1,37 @@
+// 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.cf;
+
+import com.android.tools.r8.NeverInline;
+import java.io.IOException;
+import java.io.OutputStream;
+
+public class CloserTest {
+
+ @NeverInline
+ public static CloserTest create() {
+ return new CloserTest();
+ }
+
+ OutputStream register() {
+ return System.out;
+ }
+
+ public void doSomething(String message) throws IOException {
+ System.out.println(message);
+ }
+
+ public static void main(String... args) throws IOException {
+ CloserTest closer = CloserTest.create();
+ try {
+ OutputStream out = closer.register();
+ out.write(args[0].getBytes());
+ } catch (Throwable e) {
+ closer.doSomething(e.getMessage());
+ } finally {
+ closer.doSomething("FINISHED");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/CloserTestRunner.java b/src/test/java/com/android/tools/r8/cf/CloserTestRunner.java
new file mode 100644
index 0000000..cac41cc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/CloserTestRunner.java
@@ -0,0 +1,54 @@
+// 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.cf;
+
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.TestBase;
+import org.junit.Test;
+
+/**
+ * This tests that we produce valid code when having normal-flow with exceptional edges in blocks.
+ * We might perform optimizations that add stack-operations (dup, swap, etc.) before and after
+ * instructions that lie on the boundary of the exception table that is generated for a basic block:
+ *
+ * <pre>
+ * Code:
+ * 0: invokestatic #16 // Method create:()Lcom/android/tools/r8/cf/CloserTest;
+ * 3: dup
+ * 4: dup
+ * 5: astore_1
+ * ...
+ * Exception table:
+ * from to target type
+ * 3 9 24 Class java/lang/Throwable
+ * ...
+ * StackMap table:
+ * StackMapTable: number_of_entries = 4
+ * frame_type = 255
+ * offset_delta=21
+ * locals=[top, class com/android/tools/r8/cf/CloserTest]
+ * stack=[class java/lang/Throwable]
+ * </pre>
+ *
+ * If we produce something like this, the JVM verifier will throw a VerifyError on @bci: 3 since we
+ * have not stored CloserTest in locals[1] (that happens in @bci 5), as described in the
+ * StackMapTable. This is because the exception handler starts at @bci 3 and not later.
+ */
+public class CloserTestRunner extends TestBase {
+
+ @Test
+ public void test() throws Exception {
+ testForR8(Backend.CF)
+ .addProgramClasses(CloserTest.class)
+ .addKeepMainRule(CloserTest.class)
+ .setMode(CompilationMode.RELEASE)
+ .minification(false)
+ .noTreeShaking()
+ .enableInliningAnnotations()
+ .compile()
+ .run(CloserTest.class)
+ .assertSuccess();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/DebugInfoTestRunner.java b/src/test/java/com/android/tools/r8/cf/DebugInfoTestRunner.java
index f8a654a..001c520 100644
--- a/src/test/java/com/android/tools/r8/cf/DebugInfoTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/DebugInfoTestRunner.java
@@ -3,62 +3,56 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.cf;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import com.android.tools.r8.ClassFileConsumer;
-import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.CompilationMode;
-import com.android.tools.r8.ProgramConsumer;
-import com.android.tools.r8.R8Command;
-import com.android.tools.r8.R8Command.Builder;
+import com.android.tools.r8.R8TestBuilder;
import com.android.tools.r8.TestBase;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.utils.ThrowingConsumer;
import java.nio.file.Path;
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 DebugInfoTestRunner extends TestBase {
- static final Class CLASS = DebugInfoTest.class;
+ private static final Class<?> CLASS = DebugInfoTest.class;
+ private static final String EXPECTED = "";
+
+ @Parameters(name = "{0}")
+ public static Backend[] data() {
+ return Backend.values();
+ }
+
+ private final Backend backend;
+
+ public DebugInfoTestRunner(Backend backend) {
+ this.backend = backend;
+ }
@Test
public void test() throws Exception {
- ProcessResult runInput =
- ToolHelper.runJava(ToolHelper.getClassPathForTests(), CLASS.getCanonicalName());
- assertEquals(0, runInput.exitCode);
- Path out1 = temp.getRoot().toPath().resolve("out1.zip");
- build(
- builder -> builder.addClassProgramData(ToolHelper.getClassAsBytes(CLASS), Origin.unknown()),
- new ClassFileConsumer.ArchiveConsumer(out1));
- ProcessResult run1 = ToolHelper.runJava(out1, CLASS.getCanonicalName());
- assertEquals(runInput.toString(), run1.toString());
- Path out2 = temp.getRoot().toPath().resolve("out2.zip");
- boolean invalidDebugInfo = false;
- try {
- build(builder -> builder.addProgramFiles(out1), new ClassFileConsumer.ArchiveConsumer(out2));
- } catch (CompilationFailedException e) {
- invalidDebugInfo = e.getCause().getMessage().contains("Invalid debug info");
+ if (backend == Backend.CF) {
+ testForJvm().addProgramClasses(CLASS).run(CLASS).assertSuccessWithOutput(EXPECTED);
}
- // TODO(b/77522100): Change to assertFalse when fixed.
- assertTrue(invalidDebugInfo);
- if (!invalidDebugInfo) {
- ProcessResult run2 = ToolHelper.runJava(out2, CLASS.getCanonicalName());
- assertEquals(runInput.toString(), run2.toString());
+
+ // Compile the input with R8 and run.
+ Path out = temp.getRoot().toPath().resolve("out.zip");
+ builder()
+ .addProgramClasses(CLASS)
+ .compile()
+ .writeToZip(out)
+ .run(CLASS)
+ .assertSuccessWithOutput(EXPECTED);
+
+ if (backend == Backend.CF) {
+ // If first compilation was to CF, then compile and run it again.
+ builder().addProgramFiles(out).run(CLASS).assertSuccessWithOutput(EXPECTED);
}
}
- private void build(ThrowingConsumer<Builder, Exception> input, ProgramConsumer consumer)
- throws Exception {
- Builder builder =
- R8Command.builder()
- .setMode(CompilationMode.DEBUG)
- .setDisableTreeShaking(true)
- .setDisableMinification(true)
- .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
- .setProgramConsumer(consumer);
- input.accept(builder);
- ToolHelper.runR8(builder.build(), o -> o.invalidDebugInfoFatal = true);
+ private R8TestBuilder builder() {
+ return testForR8(backend)
+ .debug()
+ .noTreeShaking()
+ .noMinification()
+ .addOptionsModification(o -> o.invalidDebugInfoFatal = true);
}
}
diff --git a/src/test/java/com/android/tools/r8/classlookup/LibraryClassExtendsProgramClassTest.java b/src/test/java/com/android/tools/r8/classlookup/LibraryClassExtendsProgramClassTest.java
index a33a5ad..0b5a60b 100644
--- a/src/test/java/com/android/tools/r8/classlookup/LibraryClassExtendsProgramClassTest.java
+++ b/src/test/java/com/android/tools/r8/classlookup/LibraryClassExtendsProgramClassTest.java
@@ -9,9 +9,8 @@
import static org.junit.Assert.fail;
import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.R8TestCompileResult;
import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestCompileResult;
import com.android.tools.r8.jasmin.JasminBuilder;
import com.android.tools.r8.utils.AndroidApiLevel;
import java.util.List;
@@ -45,7 +44,7 @@
@Test
public void testCompatibilityModeWarning() throws Exception {
- TestCompileResult<R8TestRunResult> result = testForR8Compat(Backend.DEX)
+ R8TestCompileResult result = testForR8Compat(Backend.DEX)
.setMinApi(AndroidApiLevel.O)
.addProgramClassFileData(junitClasses)
.addKeepAllClassesRule()
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/NonNullParamTest.java b/src/test/java/com/android/tools/r8/ir/optimize/NonNullParamTest.java
index 10b9a63..518ea8d 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/NonNullParamTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/NonNullParamTest.java
@@ -92,7 +92,7 @@
MethodSubject selfCheck = mainSubject.method("void", "selfCheck", ImmutableList.of());
assertThat(selfCheck, isPresent());
- assertEquals(0, countCallToParamNullCheck(selfCheck));
+ assertEquals(1, countCallToParamNullCheck(selfCheck));
assertEquals(1, countPrintCall(selfCheck));
assertEquals(0, countThrow(selfCheck));
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..19c931e 100644
--- a/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
+++ b/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
@@ -146,16 +146,16 @@
protected MethodSubject checkMethodIsKept(ClassSubject classSubject,
MethodSignature methodSignature) {
checkMethodPresenceInInput(classSubject.getOriginalName(), methodSignature, true);
- return checkMethodisKeptOrRemoved(classSubject, methodSignature, true);
+ return checkMethodIsKeptOrRemoved(classSubject, methodSignature, true);
}
protected void checkMethodIsRemoved(ClassSubject classSubject,
MethodSignature methodSignature) {
checkMethodPresenceInInput(classSubject.getOriginalName(), methodSignature, true);
- checkMethodisKeptOrRemoved(classSubject, methodSignature, false);
+ checkMethodIsKeptOrRemoved(classSubject, methodSignature, false);
}
- protected MethodSubject checkMethodisKeptOrRemoved(ClassSubject classSubject,
+ protected MethodSubject checkMethodIsKeptOrRemoved(ClassSubject classSubject,
MethodSignature methodSignature, boolean isPresent) {
checkMethodPresenceInInput(classSubject.getOriginalName(), methodSignature, true);
return checkMethodPresenceInOutput(classSubject, methodSignature, isPresent);
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
index 1d3f25e..1a690e2 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
@@ -16,6 +16,7 @@
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+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.InstructionSubject;
@@ -212,7 +213,13 @@
protected void runTest(String folder, String mainClass,
boolean enabled, AndroidAppInspector inspector) throws Exception {
runTest(
- folder, mainClass, null,
+ folder, mainClass,
+ // TODO(jsjeon): Introduce @NeverInline to kotlinR8TestResources
+ StringUtils.lines(
+ "-neverinline class * { void test*State*(...); }",
+ "-neverinline class * { void testBigExtraMethod(...); }",
+ "-neverinline class * { void testBigExtraMethodReturningLambda(...); }"
+ ),
options -> {
options.enableInlining = true;
options.enableClassInlining = enabled;
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java
index 58eae89..9224405 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java
@@ -49,8 +49,8 @@
// Getters should be removed after inlining, which is possible only if access is relaxed.
final boolean areGetterPresent = !allowAccessModification;
- checkMethodisKeptOrRemoved(dataClass, NAME_GETTER_METHOD, areGetterPresent);
- checkMethodisKeptOrRemoved(dataClass, AGE_GETTER_METHOD, areGetterPresent);
+ checkMethodIsKeptOrRemoved(dataClass, NAME_GETTER_METHOD, areGetterPresent);
+ checkMethodIsKeptOrRemoved(dataClass, AGE_GETTER_METHOD, areGetterPresent);
// No use of componentN functions.
checkMethodIsRemoved(dataClass, COMPONENT1_METHOD);
@@ -85,8 +85,8 @@
// ComponentN functions should be removed after inlining, which is possible only if access
// is relaxed.
final boolean areComponentMethodsPresent = !allowAccessModification;
- checkMethodisKeptOrRemoved(dataClass, COMPONENT1_METHOD, areComponentMethodsPresent);
- checkMethodisKeptOrRemoved(dataClass, COMPONENT2_METHOD, areComponentMethodsPresent);
+ checkMethodIsKeptOrRemoved(dataClass, COMPONENT1_METHOD, areComponentMethodsPresent);
+ checkMethodIsKeptOrRemoved(dataClass, COMPONENT2_METHOD, areComponentMethodsPresent);
// No use of getter.
checkMethodIsRemoved(dataClass, NAME_GETTER_METHOD);
@@ -118,7 +118,7 @@
ClassSubject dataClass = checkClassIsKept(codeInspector, TEST_DATA_CLASS.getClassName());
boolean component2IsPresent = !allowAccessModification;
- checkMethodisKeptOrRemoved(dataClass, COMPONENT2_METHOD, component2IsPresent);
+ checkMethodIsKeptOrRemoved(dataClass, COMPONENT2_METHOD, component2IsPresent);
// Function component1 is not used.
checkMethodIsRemoved(dataClass, COMPONENT1_METHOD);
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
index c004828..ac75230 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
@@ -735,10 +735,8 @@
MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName);
MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName);
- // Getter and setter cannot be inlined when we don't know if null check semantic is
- // preserved.
- checkMethodIsKept(objectClass, getter);
- checkMethodIsKept(objectClass, setter);
+ checkMethodIsRemoved(objectClass, getter);
+ checkMethodIsRemoved(objectClass, setter);
assertTrue(fieldSubject.getField().accessFlags.isPublic());
});
}
@@ -758,10 +756,8 @@
MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName);
MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName);
- // Getter and setter cannot be inlined when we don't know if null check semantic is
- // preserved.
- checkMethodIsKept(objectClass, getter);
- checkMethodIsKept(objectClass, setter);
+ checkMethodIsRemoved(objectClass, getter);
+ checkMethodIsRemoved(objectClass, setter);
assertTrue(fieldSubject.getField().accessFlags.isPublic());
});
}
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
index 1868940..4c43c7c 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/DesugarLambdaRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/DesugarLambdaRetraceTest.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.naming.retrace;
import static com.android.tools.r8.naming.retrace.StackTrace.isSameExceptForFileName;
+import static com.android.tools.r8.naming.retrace.StackTrace.isSameExceptForFileNameAndLineNumber;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
@@ -12,6 +13,7 @@
import com.android.tools.r8.naming.retrace.StackTrace.StackTraceLine;
import com.google.common.collect.ImmutableList;
import java.util.Collection;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -40,6 +42,8 @@
}
private int expectedActualStackTraceHeight() {
+ // In debug mode the expected stack trace height differs since there is no lambda desugaring
+ // for CF.
return mode == CompilationMode.RELEASE ? 2 : (backend == Backend.CF ? 4 : 5);
}
@@ -47,31 +51,117 @@
return line.className.contains("-$$Lambda$");
}
+ private void checkLambdaFrame(StackTrace retracedStackTrace) {
+ StackTrace lambdaFrames = retracedStackTrace.filter(this::isSynthesizedLambdaFrame);
+ assertEquals(1, lambdaFrames.size());
+ if (lambdaFrames.get(0).hasLineNumber()) {
+ 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);
+ }
+
+ private void checkIsSameExceptForFileName(
+ StackTrace actualStackTrace, StackTrace retracedStackTrace) {
+ // Even when SourceFile is present retrace replaces the file name in the stack trace.
+ if (backend == Backend.CF) {
+ // TODO(122440196): Additional code to locate issue.
+ if (!isSameExceptForFileName(expectedStackTrace).matches(retracedStackTrace)) {
+ System.out.println("Expected original:");
+ System.out.println(expectedStackTrace.getOriginalStderr());
+ System.out.println("Actual original:");
+ System.out.println(retracedStackTrace.getOriginalStderr());
+ System.out.println("Parsed original:");
+ System.out.println(expectedStackTrace);
+ System.out.println("Parsed retraced:");
+ System.out.println(retracedStackTrace);
+ }
+ assertThat(retracedStackTrace, isSameExceptForFileName(expectedStackTrace));
+ } else {
+ // TODO(122440196): Additional code to locate issue.
+ if (!isSameExceptForFileName(expectedStackTrace)
+ .matches(retracedStackTrace.filter(line -> !isSynthesizedLambdaFrame(line)))) {
+ System.out.println("Expected original:");
+ System.out.println(expectedStackTrace.getOriginalStderr());
+ System.out.println("Actual original:");
+ System.out.println(retracedStackTrace.getOriginalStderr());
+ System.out.println("Parsed original:");
+ System.out.println(expectedStackTrace);
+ System.out.println("Parsed retraced:");
+ System.out.println(retracedStackTrace);
+ }
+ // With the frame from the lambda class filtered out the stack trace is the same.
+ assertThat(
+ retracedStackTrace.filter(line -> !isSynthesizedLambdaFrame(line)),
+ isSameExceptForFileName(expectedStackTrace));
+ // Check the frame from the lambda class.
+ checkLambdaFrame(retracedStackTrace);
+ }
+ assertEquals(expectedActualStackTraceHeight(), actualStackTrace.size());
+ }
+
+ private void checkIsSameExceptForFileNameAndLineNumber(
+ StackTrace actualStackTrace, StackTrace retracedStackTrace) {
+ // Even when SourceFile is present retrace replaces the file name in the stack trace.
+ if (backend == Backend.CF) {
+ // TODO(122440196): Additional code to locate issue.
+ if (!isSameExceptForFileNameAndLineNumber(expectedStackTrace).matches(retracedStackTrace)) {
+ System.out.println("Expected original:");
+ System.out.println(expectedStackTrace.getOriginalStderr());
+ System.out.println("Actual original:");
+ System.out.println(retracedStackTrace.getOriginalStderr());
+ System.out.println("Parsed original:");
+ System.out.println(expectedStackTrace);
+ System.out.println("Parsed retraced:");
+ System.out.println(retracedStackTrace);
+ }
+ assertThat(retracedStackTrace, isSameExceptForFileNameAndLineNumber(expectedStackTrace));
+ } else {
+ // TODO(122440196): Additional code to locate issue.
+ if (!isSameExceptForFileNameAndLineNumber(expectedStackTrace)
+ .matches(retracedStackTrace.filter(line -> !isSynthesizedLambdaFrame(line)))) {
+ System.out.println("Expected original:");
+ System.out.println(expectedStackTrace.getOriginalStderr());
+ System.out.println("Actual original:");
+ System.out.println(retracedStackTrace.getOriginalStderr());
+ System.out.println("Parsed original:");
+ System.out.println(expectedStackTrace);
+ System.out.println("Parsed retraced:");
+ System.out.println(retracedStackTrace);
+ }
+ // With the frame from the lambda class filtered out the stack trace is the same.
+ assertThat(
+ retracedStackTrace.filter(line -> !isSynthesizedLambdaFrame(line)),
+ isSameExceptForFileNameAndLineNumber(expectedStackTrace));
+ // Check the frame from the lambda class.
+ checkLambdaFrame(retracedStackTrace);
+ }
+ assertEquals(expectedActualStackTraceHeight(), actualStackTrace.size());
+ }
+
@Test
+ @Ignore("b/122440196")
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());
- if (lambdaFrames.get(0).hasLineNumber()) {
- 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());
- });
+ ImmutableList.of("-keepattributes SourceFile,LineNumberTable"),
+ this::checkIsSameExceptForFileName);
+ }
+
+ @Test
+ @Ignore("b/122440196")
+ public void testLineNumberTableOnly() throws Exception {
+ runTest(
+ ImmutableList.of("-keepattributes LineNumberTable"),
+ this::checkIsSameExceptForFileName);
+ }
+
+ @Test
+ @Ignore("b/122440196")
+ public void testNoLineNumberTable() throws Exception {
+ runTest(
+ ImmutableList.of(),
+ this::checkIsSameExceptForFileNameAndLineNumber);
}
}
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/DesugarStaticInterfaceMethodsRetraceTest.java b/src/test/java/com/android/tools/r8/naming/retrace/DesugarStaticInterfaceMethodsRetraceTest.java
new file mode 100644
index 0000000..8114108
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/retrace/DesugarStaticInterfaceMethodsRetraceTest.java
@@ -0,0 +1,76 @@
+// 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.assertThat;
+
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.NeverInline;
+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 DesugarStaticInterfaceMethodsRetraceTest extends RetraceTestBase {
+
+ @Parameters(name = "Backend: {0}, mode: {1}")
+ public static Collection<Object[]> data() {
+ return buildParameters(Backend.values(), CompilationMode.values());
+ }
+
+ public DesugarStaticInterfaceMethodsRetraceTest(Backend backend, CompilationMode mode) {
+ super(backend, mode);
+ }
+
+ @Override
+ public Collection<Class<?>> getClasses() {
+ return ImmutableList.of(
+ getMainClass(), InterfaceWithStaticMethod1.class, InterfaceWithStaticMethod2.class);
+ }
+
+ @Override
+ public Class<?> getMainClass() {
+ return MainDesugarStaticInterfaceMethodsRetraceTest.class;
+ }
+
+ @Test
+ public void testSourceFileAndLineNumberTable() throws Exception {
+ runTest(
+ ImmutableList.of("-keepattributes SourceFile,LineNumberTable"),
+ // For the desugaring to companion classes the retrace stacktrace is still the same
+ // as the mapping file has a fully qualified class name in the method mapping, e.g.:
+ //
+ // com.android.tools.r8.naming.retrace.InterfaceWithDefaultMethod1$-CC -> com.android.tools.r8.naming.retrace.a:
+ // 1:1:void com.android.tools.r8.naming.retrace.InterfaceWithDefaultMethod1.defaultMethod1():80:80 -> a
+ (StackTrace actualStackTrace, StackTrace retracedStackTrace) ->
+ assertThat(retracedStackTrace, isSameExceptForFileName(expectedStackTrace)));
+ }
+}
+
+interface InterfaceWithStaticMethod2 {
+
+ static void staticMethod1() {
+ throw null;
+ }
+}
+
+interface InterfaceWithStaticMethod1 {
+
+ @NeverInline
+ static void staticMethod2() {
+ InterfaceWithStaticMethod2.staticMethod1();
+ }
+}
+
+class MainDesugarStaticInterfaceMethodsRetraceTest {
+
+ public static void main(String[] args) {
+ InterfaceWithStaticMethod1.staticMethod2();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/InliningRetraceTest.java b/src/test/java/com/android/tools/r8/naming/retrace/InliningRetraceTest.java
index 6d7ce25..c252426 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/InliningRetraceTest.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/InliningRetraceTest.java
@@ -11,6 +11,7 @@
import com.android.tools.r8.CompilationMode;
import com.android.tools.r8.ForceInline;
+import com.google.common.collect.ImmutableList;
import java.util.Collection;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -41,7 +42,7 @@
@Test
public void testSourceFileAndLineNumberTable() throws Exception {
runTest(
- "-keepattributes SourceFile,LineNumberTable",
+ ImmutableList.of("-keepattributes SourceFile,LineNumberTable"),
(StackTrace actualStackTrace, StackTrace retracedStackTrace) -> {
// Even when SourceFile is present retrace replaces the file name in the stack trace.
assertThat(retracedStackTrace, isSameExceptForFileName(expectedStackTrace));
@@ -52,7 +53,7 @@
@Test
public void testLineNumberTableOnly() throws Exception {
runTest(
- "-keepattributes LineNumberTable",
+ ImmutableList.of("-keepattributes LineNumberTable"),
(StackTrace actualStackTrace, StackTrace retracedStackTrace) -> {
assertThat(retracedStackTrace, isSameExceptForFileName(expectedStackTrace));
assertEquals(expectedActualStackTraceHeight(), actualStackTrace.size());
@@ -62,7 +63,7 @@
@Test
public void testNoLineNumberTable() throws Exception {
runTest(
- "",
+ ImmutableList.of(),
(StackTrace actualStackTrace, StackTrace retracedStackTrace) -> {
assertThat(retracedStackTrace, isSameExceptForFileNameAndLineNumber(expectedStackTrace));
assertEquals(expectedActualStackTraceHeight(), actualStackTrace.size());
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 6243e26..2258d67 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
@@ -9,6 +9,7 @@
import com.android.tools.r8.TestBase;
import com.google.common.collect.ImmutableList;
import java.util.Collection;
+import java.util.List;
import java.util.function.BiConsumer;
import org.junit.Before;
@@ -40,7 +41,7 @@
.map(StackTrace::extractFromJvm);
}
- public void runTest(String keepRule, BiConsumer<StackTrace, StackTrace> checker)
+ public void runTest(List<String> keepRules, BiConsumer<StackTrace, StackTrace> checker)
throws Exception {
R8TestRunResult result =
@@ -50,7 +51,7 @@
.enableInliningAnnotations()
.addProgramClasses(getClasses())
.addKeepMainRule(getMainClass())
- .addKeepRules(keepRule)
+ .addKeepRules(keepRules)
.run(getMainClass())
.assertFailure();
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 9876c95..0ecd3e4 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
@@ -113,10 +113,12 @@
}
private final List<StackTraceLine> stackTraceLines;
+ private final String originalStderr;
- private StackTrace(List<StackTraceLine> stackTraceLines) {
+ private StackTrace(List<StackTraceLine> stackTraceLines, String originalStderr) {
assert stackTraceLines.size() > 0;
this.stackTraceLines = stackTraceLines;
+ this.originalStderr = originalStderr;
}
public int size() {
@@ -127,6 +129,10 @@
return stackTraceLines.get(index);
}
+ public String getOriginalStderr() {
+ return originalStderr;
+ }
+
public static StackTrace extractFromArt(String stderr) {
List<StackTraceLine> stackTraceLines = new ArrayList<>();
List<String> stderrLines = StringUtils.splitLines(stderr);
@@ -165,15 +171,18 @@
for (int i = first; i < last; i++) {
stackTraceLines.add(StackTraceLine.parse(stderrLines.get(i)));
}
- return new StackTrace(stackTraceLines);
+ return new StackTrace(stackTraceLines, stderr);
+ }
+
+ private static List<StackTraceLine> internalExtractFromJvm(String stderr) {
+ return StringUtils.splitLines(stderr).stream()
+ .filter(s -> s.startsWith(TAB_AT_PREFIX))
+ .map(StackTraceLine::parse)
+ .collect(Collectors.toList());
}
public static StackTrace extractFromJvm(String stderr) {
- return new StackTrace(
- StringUtils.splitLines(stderr).stream()
- .filter(s -> s.startsWith(TAB_AT_PREFIX))
- .map(StackTraceLine::parse)
- .collect(Collectors.toList()));
+ return new StackTrace(internalExtractFromJvm(stderr), stderr);
}
public static StackTrace extractFromJvm(TestRunResult result) {
@@ -188,11 +197,15 @@
FileUtils.writeTextFile(
stackTraceFile,
stackTraceLines.stream().map(line -> line.originalLine).collect(Collectors.toList()));
- return StackTrace.extractFromJvm(ToolHelper.runRetrace(mapFile, stackTraceFile));
+ // Keep the original stderr in the retraced stacktrace.
+ return new StackTrace(
+ internalExtractFromJvm(ToolHelper.runRetrace(mapFile, stackTraceFile)), originalStderr);
}
public StackTrace filter(Predicate<StackTraceLine> filter) {
- return new StackTrace(stackTraceLines.stream().filter(filter).collect(Collectors.toList()));
+ return new StackTrace(
+ stackTraceLines.stream().filter(filter).collect(Collectors.toList()),
+ originalStderr);
}
@Override
@@ -395,7 +408,9 @@
@Override
public void describeMismatchSafely(final StackTrace stackTrace, Description description) {
- description.appendText("stacktrace was " + stackTrace);
+ description.appendText("stacktrace was");
+ description.appendText(System.lineSeparator());
+ description.appendText(stackTrace.toString());
description.appendText(System.lineSeparator());
if (expected.size() != stackTrace.size()) {
description.appendText("They have different sizes.");
diff --git a/src/test/java/com/android/tools/r8/shaking/PrintConfigurationTest.java b/src/test/java/com/android/tools/r8/shaking/PrintConfigurationTest.java
index 7621522..94c9896 100644
--- a/src/test/java/com/android/tools/r8/shaking/PrintConfigurationTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/PrintConfigurationTest.java
@@ -4,10 +4,13 @@
package com.android.tools.r8.shaking;
+import static org.hamcrest.core.StringContains.containsString;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.StringUtils;
import com.google.common.base.Charsets;
import com.google.common.collect.ImmutableList;
import java.nio.file.Path;
@@ -15,23 +18,76 @@
class PrintConfigurationTestClass {
- public static void main(String[] args) {
-
- }
+ public static void main(String[] args) {}
}
public class PrintConfigurationTest extends TestBase {
+
@Test
- public void testSingleCOnfiguration() throws Exception {
- Class mainClass = PrintConfigurationTestClass.class;
- String proguardConfig = keepMainProguardConfiguration(mainClass);
+ public void testSingleConfigurationWithAbsolutePath() throws Exception {
Path printConfigurationFile = temp.newFile().toPath();
- proguardConfig += "\n-printconfiguration " + printConfigurationFile.toString();
- compileWithR8(ImmutableList.of(mainClass), proguardConfig);
+ String proguardConfig =
+ StringUtils.lines(
+ keepMainProguardConfiguration(PrintConfigurationTestClass.class),
+ "-printconfiguration " + printConfigurationFile);
+ testForR8(Backend.DEX)
+ .addProgramClasses(PrintConfigurationTestClass.class)
+ .addKeepRules(proguardConfig)
+ .compile();
assertEquals(proguardConfig, FileUtils.readTextFile(printConfigurationFile, Charsets.UTF_8));
}
@Test
+ public void testSingleConfigurationWithRelativePath() throws Exception {
+ Path subDirectory = temp.newFolder().toPath();
+ Path proguardConfigFile = subDirectory.resolve("proguard-config.txt");
+ Path proguardConfigOutFile = subDirectory.resolve("proguard-config-out.txt");
+
+ String proguardConfig =
+ StringUtils.lines(
+ keepMainProguardConfiguration(PrintConfigurationTestClass.class),
+ "-printconfiguration proguard-config-out.txt");
+ FileUtils.writeTextFile(proguardConfigFile, proguardConfig.trim());
+
+ testForExternalR8(Backend.DEX)
+ .addProgramClasses(PrintConfigurationTestClass.class)
+ .addKeepRuleFiles(proguardConfigFile)
+ .compile();
+
+ assertEquals(proguardConfig, FileUtils.readTextFile(proguardConfigOutFile, Charsets.UTF_8));
+ }
+
+ @Test
+ public void testSingleConfigurationWithRelativePathCompatibility() throws Exception {
+ Path subDirectory = temp.newFolder().toPath();
+ Path proguardConfigFile = subDirectory.resolve("proguard-config.txt");
+ Path proguardConfigOutFile = subDirectory.resolve("proguard-config-out.txt");
+
+ String proguardConfig =
+ StringUtils.lines(
+ keepMainProguardConfiguration(PrintConfigurationTestClass.class),
+ "-printconfiguration proguard-config-out.txt");
+ FileUtils.writeTextFile(proguardConfigFile, proguardConfig);
+
+ testForProguard()
+ .addProgramClasses(PrintConfigurationTestClass.class)
+ .addKeepRuleFiles(proguardConfigFile)
+ .compile();
+
+ String proguardConfigOut = FileUtils.readTextFile(proguardConfigOutFile, Charsets.UTF_8);
+ assertThat(
+ proguardConfigOut,
+ containsString(
+ StringUtils.lines(
+ "-keep class com.android.tools.r8.shaking.PrintConfigurationTestClass {",
+ " public static void main(java.lang.String[]);",
+ "}")));
+ assertThat(
+ proguardConfigOut,
+ containsString("-printconfiguration " + proguardConfigOutFile.toAbsolutePath().toString()));
+ }
+
+ @Test
public void testIncludeFile() throws Exception {
Class mainClass = PrintConfigurationTestClass.class;
String includeProguardConfig = keepMainProguardConfiguration(mainClass);
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
index 19cdf04..a7e3e46 100644
--- a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByAnnotatedMethodTestRunner.java
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByAnnotatedMethodTestRunner.java
@@ -86,7 +86,7 @@
.method(fooMethod)
.assertNotRenamed()
.assertNotInvokedFrom(mainMethod)
- // TODO(b/122297131): keepAnnotatedMethods should also be keeping foo alive!
+ .assertKeptBy(keepAnnotatedMethods)
.assertKeptBy(keepClassesOfAnnotatedMethods);
// Check baz is removed.
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByTwoRulesTest.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByTwoRulesTest.java
new file mode 100644
index 0000000..7cb464d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByTwoRulesTest.java
@@ -0,0 +1,18 @@
+// 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;
+
+@Keep
+public class KeptByTwoRulesTest {
+
+ public static void foo() {
+ System.out.println("called foo");
+ }
+
+ public static void main(String[] args) {
+ foo();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByTwoRulesTestRunner.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByTwoRulesTestRunner.java
new file mode 100644
index 0000000..38c3f00
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByTwoRulesTestRunner.java
@@ -0,0 +1,87 @@
+// 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.classFromClass;
+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.references.MethodReference;
+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 KeptByTwoRulesTestRunner extends TestBase {
+
+ private static final Class<?> CLASS = KeptByTwoRulesTest.class;
+ private static final Collection<Class<?>> CLASSES = Arrays.asList(CLASS);
+
+ private final String EXPECTED = StringUtils.lines("called foo");
+
+ private final Backend backend;
+
+ @Parameters(name = "{0}")
+ public static Backend[] data() {
+ return Backend.values();
+ }
+
+ public KeptByTwoRulesTestRunner(Backend backend) {
+ this.backend = backend;
+ }
+
+ @Test
+ public void test() throws Exception {
+ MethodReference mainMethod = methodFromMethod(CLASS.getDeclaredMethod("main", String[].class));
+ MethodReference fooMethod = methodFromMethod(CLASS.getDeclaredMethod("foo"));
+
+ if (backend == Backend.CF) {
+ testForJvm().addProgramClasses(CLASSES).run(CLASS).assertSuccessWithOutput(EXPECTED);
+ }
+
+ String keepPublicRule = "-keep @com.android.tools.r8.Keep class * { public *; }";
+ String keepFooRule = "-keep class " + CLASS.getTypeName() + " { public void foo(); }";
+ GraphInspector inspector =
+ testForR8(backend)
+ .enableGraphInspector()
+ .enableInliningAnnotations()
+ .addProgramClasses(CLASSES)
+ .addKeepRules(keepPublicRule, keepFooRule)
+ .run(CLASS)
+ .assertSuccessWithOutput(EXPECTED)
+ .graphInspector();
+
+ assertEquals(2, inspector.getRoots().size());
+ QueryNode keepPublic = inspector.rule(keepPublicRule).assertRoot();
+ QueryNode keepFoo = inspector.rule(keepFooRule).assertRoot();
+
+ inspector
+ .method(mainMethod)
+ .assertNotRenamed()
+ .assertKeptBy(keepPublic)
+ .assertNotKeptBy(keepFoo);
+
+ // Check foo is called from main and kept by two rules.
+ inspector
+ .method(fooMethod)
+ .assertNotRenamed()
+ .assertInvokedFrom(mainMethod)
+ .assertKeptBy(keepPublic)
+ .assertKeptBy(keepFoo);
+
+ // Check the class is also kept by both rules.
+ inspector
+ .clazz(classFromClass(CLASS))
+ .assertNotRenamed()
+ .assertKeptBy(keepPublic)
+ .assertKeptBy(keepFoo);
+ }
+}
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 107978b..940482b 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
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.utils.graphinspector;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -113,16 +114,26 @@
}
public QueryNode assertNotInvokedFrom(MethodReference method) {
- assertTrue(
- errorMessage("no invocation from " + method.toString(), "invoke"),
- !isInvokedFrom(method));
+ assertFalse(
+ 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 " + node.getNodeDescription(), "none"), isKeptBy(node));
+ assertTrue(
+ "Invalid call to assertKeptBy with: " + node.getNodeDescription(), node.isPresent());
+ assertTrue(
+ errorMessage("kept by " + node.getNodeDescription(), "was not kept by it"),
+ isKeptBy(node));
+ return this;
+ }
+
+ public QueryNode assertNotKeptBy(QueryNode node) {
+ assertTrue(
+ "Invalid call to assertNotKeptBy with: " + node.getNodeDescription(), node.isPresent());
+ assertFalse(
+ errorMessage("not kept by " + node.getNodeDescription(), "was kept by it"),
+ isKeptBy(node));
return this;
}
}
diff --git a/src/test/kotlinR8TestResources/class_inliner_lambda_j_style/main.kt b/src/test/kotlinR8TestResources/class_inliner_lambda_j_style/main.kt
index ebf26cd..f95125c 100644
--- a/src/test/kotlinR8TestResources/class_inliner_lambda_j_style/main.kt
+++ b/src/test/kotlinR8TestResources/class_inliner_lambda_j_style/main.kt
@@ -16,7 +16,6 @@
testStateful3()
}
-@Synchronized
fun testStateless() {
SamIface.Consumer.consume { "123" }
SamIface.Consumer.consume { "ABC" }
@@ -31,7 +30,6 @@
}
}
-@Synchronized
fun testStateful() {
var someVariable = 0
@@ -62,7 +60,6 @@
}
}
-@Synchronized
fun testStateful2() {
var someVariable = 0
SamIface.Consumer.consumeBig {
@@ -80,7 +77,6 @@
}
}
-@Synchronized
fun testStateful3() {
var someVariable = 0
SamIface.Consumer.consumeBig {
diff --git a/src/test/kotlinR8TestResources/class_inliner_lambda_k_style/main.kt b/src/test/kotlinR8TestResources/class_inliner_lambda_k_style/main.kt
index 06b937e..ea315ba 100644
--- a/src/test/kotlinR8TestResources/class_inliner_lambda_k_style/main.kt
+++ b/src/test/kotlinR8TestResources/class_inliner_lambda_k_style/main.kt
@@ -17,14 +17,12 @@
data class Record(val foo: String, val good: Boolean)
-@Synchronized
fun testKotlinSequencesStateless(strings: Sequence<String>) {
useRecord()
// Stateless k-style lambda
strings.map { Record(it, false) }.forEach { println(it) }
}
-@Synchronized
fun testKotlinSequencesStateful(a: Int, b: Int, strings: Sequence<String>) {
useRecord()
// Big stateful k-style lambda
@@ -40,7 +38,6 @@
}
}
-@Synchronized
fun testBigExtraMethod() {
useRecord()
bigUserWithNotNullChecksAndTwoCalls(next()) { next() }
@@ -62,7 +59,6 @@
return "$id: ${lambda()}"
}
-@Synchronized
fun testBigExtraMethodReturningLambda() {
useRecord()
bigUserReturningLambda(next()) { next() } // Not used
diff --git a/tools/as_utils.py b/tools/as_utils.py
index 1eaa206..7b373f4 100644
--- a/tools/as_utils.py
+++ b/tools/as_utils.py
@@ -8,7 +8,7 @@
import utils
-def add_r8_dependency(checkout_dir):
+def add_r8_dependency(checkout_dir, minified):
build_file = os.path.join(checkout_dir, 'build.gradle')
assert os.path.isfile(build_file), 'Expected a file to be present at {}'.format(build_file)
@@ -27,12 +27,21 @@
is_inside_dependencies = True
if is_inside_dependencies:
if utils.R8_JAR in stripped:
+ if minified:
+ # Skip line to avoid dependency on r8.jar
+ continue
+ added_r8_dependency = True
+ elif utils.R8LIB_JAR in stripped:
+ if not minified:
+ # Skip line to avoid dependency on r8lib.jar
+ continue
added_r8_dependency = True
elif 'com.android.tools.build:gradle:' in stripped:
gradle_version = stripped[stripped.rindex(':')+1:-1]
if not added_r8_dependency:
indent = ''.ljust(line.index('classpath'))
- f.write('{}classpath files(\'{}\')\n'.format(indent, utils.R8_JAR))
+ jar = utils.R8LIB_JAR if minified else utils.R8_JAR
+ f.write('{}classpath files(\'{}\')\n'.format(indent, jar))
added_r8_dependency = True
elif stripped == '}':
is_inside_dependencies = False
@@ -51,5 +60,5 @@
lines = f.readlines()
with open(build_file, 'w') as f:
for line in lines:
- if utils.R8_JAR not in line:
+ if (utils.R8_JAR not in line) and (utils.R8LIB_JAR not in line):
f.write(line)
diff --git a/tools/run_on_app.py b/tools/run_on_app.py
index 31cecfc..41d4578 100755
--- a/tools/run_on_app.py
+++ b/tools/run_on_app.py
@@ -22,8 +22,9 @@
TYPES = ['dex', 'deploy', 'proguarded']
APPS = ['gmscore', 'nest', 'youtube', 'gmail', 'chrome']
-COMPILERS = ['d8', 'r8', 'r8lib-r8', 'r8lib-d8']
-R8_COMPILERS = ['r8', 'r8lib-r8']
+COMPILERS = ['d8', 'r8']
+COMPILER_BUILDS = ['full', 'lib']
+
# We use this magic exit code to signal that the program OOM'ed
OOM_EXIT_CODE = 42
@@ -32,6 +33,10 @@
result.add_option('--compiler',
help='The compiler to use',
choices=COMPILERS)
+ result.add_option('--compiler-build',
+ help='Compiler build to use',
+ choices=COMPILER_BUILDS,
+ default='lib')
result.add_option('--app',
help='What app to run on',
choices=APPS)
@@ -152,18 +157,20 @@
assert len(args) == 0
for name, version, type, use_r8lib in get_permutations():
compiler = 'r8' if type == 'deploy' else 'd8'
- if use_r8lib:
- compiler = 'r8lib-' + compiler
- print('Executing %s with %s %s %s' % (compiler, name, version, type))
+ compiler_build = 'lib' if use_r8lib else 'full'
+ print('Executing %s/%s with %s %s %s' % (compiler, compiler_build, name,
+ version, type))
fixed_options = copy.copy(options)
fixed_options.app = name
fixed_options.version = version
fixed_options.compiler = compiler
+ fixed_options.compiler_build = compiler_build
fixed_options.type = type
exit_code = run_with_options(fixed_options, [])
if exit_code != 0:
- print('Failed %s %s %s with %s' % (name, version, type, compiler))
+ print('Failed %s %s %s with %s/%s' % (name, version, type, compiler,
+ compiler_build))
exit(exit_code)
def find_min_xmx(options, args):
@@ -208,10 +215,13 @@
return find_min_xmx(options, args)
return run_with_options(options, args)
-def run_with_options(options, args, extra_args=[]):
+def run_with_options(options, args, extra_args=None):
+ if extra_args is None:
+ extra_args = []
app_provided_pg_conf = False;
# todo(121018500): remove when memory is under control
- extra_args.append('-Xmx8G')
+ if not any('-Xmx' in arg for arg in extra_args):
+ extra_args.append('-Xmx8G')
if options.golem:
golem.link_third_party()
options.out = os.getcwd()
@@ -239,6 +249,10 @@
raise Exception("You need to specify '--compiler={}'"
.format('|'.join(COMPILERS)))
+ if options.compiler_build not in COMPILER_BUILDS:
+ raise Exception("You need to specify '--compiler-build={}'"
+ .format('|'.join(COMPILER_BUILDS)))
+
if not options.version in data.VERSIONS.keys():
print('No version {} for application {}'
.format(options.version, options.app))
@@ -248,7 +262,7 @@
version = data.VERSIONS[options.version]
if not options.type:
- options.type = 'deploy' if options.compiler in R8_COMPILERS \
+ options.type = 'deploy' if options.compiler == 'r8' \
else 'proguarded'
if options.type not in version:
@@ -260,7 +274,7 @@
# For R8 'deploy' the JAR is located using the Proguard configuration
# -injars option. For chrome and nest we don't have the injars in the
# proguard files.
- if 'inputs' in values and (options.compiler not in R8_COMPILERS
+ if 'inputs' in values and (options.compiler != 'r8'
or options.type != 'deploy'
or options.app == 'chrome'
or options.app == 'nest'):
@@ -273,7 +287,7 @@
if 'main-dex-list' in values:
args.extend(['--main-dex-list', values['main-dex-list']])
- if options.compiler in R8_COMPILERS:
+ if options.compiler == 'r8':
if 'pgconf' in values and not options.k:
for pgconf in values['pgconf']:
args.extend(['--pg-conf', pgconf])
@@ -297,7 +311,7 @@
# Additional flags for the compiler from the configuration file.
if 'flags' in values:
args.extend(values['flags'].split(' '))
- if options.compiler in R8_COMPILERS:
+ if options.compiler == 'r8':
if 'r8-flags' in values:
args.extend(values['r8-flags'].split(' '))
@@ -319,7 +333,7 @@
if options.print_memoryuse and not options.track_memory_to_file:
options.track_memory_to_file = os.path.join(temp,
utils.MEMORY_USE_TMP_FILE)
- if options.compiler in R8_COMPILERS and app_provided_pg_conf:
+ if options.compiler == 'r8' and app_provided_pg_conf:
# Ensure that output of -printmapping and -printseeds go to the output
# location and not where the app Proguard configuration places them.
if outdir.endswith('.zip') or outdir.endswith('.jar'):
@@ -332,7 +346,12 @@
build = not options.no_build and not options.golem
stderr_path = os.path.join(temp, 'stderr')
with open(stderr_path, 'w') as stderr:
- exit_code = toolhelper.run(options.compiler, args,
+ if options.compiler_build == 'full':
+ tool = options.compiler
+ else:
+ assert(options.compiler_build == 'lib')
+ tool = 'r8lib-' + options.compiler
+ exit_code = toolhelper.run(tool, args,
build=build,
debug=not options.no_debug,
profile=options.profile,
diff --git a/tools/run_on_as_app.py b/tools/run_on_as_app.py
index e2f70f0..8f37f43 100755
--- a/tools/run_on_as_app.py
+++ b/tools/run_on_as_app.py
@@ -14,7 +14,7 @@
import as_utils
-SHRINKERS = ['r8', 'r8full', 'proguard']
+SHRINKERS = ['r8', 'r8full', 'r8-minified', 'r8full-minified', 'proguard']
WORKING_DIR = utils.BUILD
if 'R8_BENCHMARK_DIR' in os.environ and os.path.isdir(os.environ['R8_BENCHMARK_DIR']):
@@ -107,6 +107,9 @@
script = os.path.join(utils.TOOLS_DIR, 'extractmarker.py')
return '~~R8' in subprocess.check_output(['python', script, apk]).strip()
+def IsMinifiedR8(shrinker):
+ return shrinker == 'r8-minified' or shrinker == 'r8full-minified'
+
def IsTrackedByGit(file):
return subprocess.check_output(['git', 'ls-files', file]).strip() != ''
@@ -163,11 +166,6 @@
with utils.ChangedWorkingDirectory(checkout_dir):
GitPull()
- if options.use_tot:
- as_utils.add_r8_dependency(checkout_dir)
- else:
- as_utils.remove_r8_dependency(checkout_dir)
-
result_per_shrinker = {}
with utils.ChangedWorkingDirectory(checkout_dir):
@@ -203,6 +201,11 @@
def BuildAppWithShrinker(app, config, shrinker, checkout_dir, options):
print('Building {} with {}'.format(app, shrinker))
+ if options.disable_tot:
+ as_utils.remove_r8_dependency(checkout_dir)
+ else:
+ as_utils.add_r8_dependency(checkout_dir, IsMinifiedR8(shrinker))
+
app_module = config.get('app_module', 'app')
archives_base_name = config.get(' archives_base_name', app_module)
flavor = config.get('flavor')
@@ -214,7 +217,7 @@
with open("gradle.properties", "a") as gradle_properties:
if 'r8' in shrinker:
gradle_properties.write('\nandroid.enableR8=true\n')
- if shrinker == 'r8full':
+ if shrinker == 'r8full' or shrinker == 'r8full-minified':
gradle_properties.write('android.enableR8.fullMode=true\n')
else:
assert shrinker == 'proguard'
@@ -328,16 +331,26 @@
result.add_option('--shrinker',
help='The shrinker to use (by default, all are run)',
choices=SHRINKERS)
- result.add_option('--use_tot',
+ result.add_option('--disable_tot',
help='Whether to disable the use of the ToT version of R8',
- default=True,
- action='store_false')
+ default=False,
+ action='store_true')
return result.parse_args(argv)
def main(argv):
+ global SHRINKERS
+
(options, args) = ParseOptions(argv)
- assert not options.use_tot or os.path.isfile(utils.R8_JAR), (
+ assert options.disable_tot or os.path.isfile(utils.R8_JAR), (
'Cannot build from ToT without r8.jar')
+ assert options.disable_tot or os.path.isfile(utils.R8LIB_JAR), (
+ 'Cannot build from ToT without r8lib.jar')
+
+ if options.disable_tot:
+ # Cannot run r8 lib without adding r8lib.jar as an dependency
+ SHRINKERS = [
+ shrinker for shrinker in SHRINKERS
+ if 'minified' not in shrinker]
result_per_shrinker_per_app = {}