Merge "Add test for tracking incorrect instrumentation of Jacoco"
diff --git a/.gitignore b/.gitignore
index 95b74c3..51e0c02 100644
--- a/.gitignore
+++ b/.gitignore
@@ -33,6 +33,8 @@
third_party/benchmarks/santa-tracker.tar.gz
third_party/chrome.tar.gz
third_party/chrome
+third_party/dart-sdk
+third_party/dart-sdk.tar.gz
third_party/desugar/desugar_*/
third_party/desugar/desugar_*.tar.gz
third_party/gmail/*
diff --git a/build.gradle b/build.gradle
index 32ebbad..4231129 100644
--- a/build.gradle
+++ b/build.gradle
@@ -513,6 +513,15 @@
baseName 'deps'
}
+task repackageDepsForLib(type: ShadowJar) {
+ configurations = [project.configurations.compile]
+ mergeServiceFiles(it)
+ configureRelocations(it)
+ exclude { it.getRelativePath().getPathString() == "module-info.class" }
+ exclude { it.getRelativePath().getPathString().startsWith("META-INF/maven/") }
+ baseName 'r8lib_deps'
+}
+
task repackageSources(type: ShadowJar) {
from sourceSets.main.output
mergeServiceFiles(it)
@@ -522,6 +531,18 @@
baseName 'sources'
}
+task R8libWithDeps(type: ShadowJar) {
+ from consolidatedLicense.outputs.files
+ baseName 'r8lib_with_deps'
+ classifier = null
+ version = null
+ manifest {
+ attributes 'Main-Class': 'com.android.tools.r8.SwissArmyKnife'
+ }
+ from repackageSources.outputs.files
+ from repackageDepsForLib.outputs.files
+}
+
task R8(type: ShadowJar) {
from consolidatedLicense.outputs.files
baseName 'r8'
@@ -1290,17 +1311,23 @@
if (project.property('tool') == 'r8') {
exclude "com/android/tools/r8/art/*/d8/**"
exclude "com/android/tools/r8/jctf/d8/**"
- } else {
- assert(project.property('tool') == 'd8')
+ exclude "com/android/tools/r8/jctf/r8cf/**"
+ } else if (project.property('tool') == 'd8') {
exclude "com/android/tools/r8/art/*/r8/**"
exclude "com/android/tools/r8/jctf/r8/**"
+ exclude "com/android/tools/r8/jctf/r8cf/**"
+ } else {
+ assert(project.property('tool') == 'r8cf')
+ exclude "com/android/tools/r8/art/*/d8/**"
+ exclude "com/android/tools/r8/art/*/r8/**"
+ exclude "com/android/tools/r8/jctf/d8/**"
+ exclude "com/android/tools/r8/jctf/r8/**"
}
}
if (!project.hasProperty('all_tests')) {
exclude "com/android/tools/r8/art/dx/**"
exclude "com/android/tools/r8/art/jack/**"
}
- // TODO(tamaskenez) enable jctf on all_tests when consolidated
if (!project.hasProperty('jctf') && !project.hasProperty('only_jctf')) {
exclude "com/android/tools/r8/jctf/**"
}
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index ec863ee..b7c538f 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -352,7 +352,7 @@
.rewrittenWithLense(application.asDirect(), appView.graphLense()));
timing.end();
}
- appView.setGraphLense(new MemberRebindingAnalysis(appViewWithLiveness).run());
+ appView.setGraphLense(new MemberRebindingAnalysis(appViewWithLiveness, options).run());
if (options.enableVerticalClassMerging) {
timing.begin("ClassMerger");
VerticalClassMerger verticalClassMerger =
@@ -476,21 +476,19 @@
ProguardMapSupplier proguardMapSupplier;
- if (options.lineNumberOptimization != LineNumberOptimization.OFF) {
- timing.begin("Line number remapping");
- ClassNameMapper classNameMapper =
- LineNumberOptimizer.run(
- application,
- appView.graphLense(),
- namingLens,
- options.lineNumberOptimization == LineNumberOptimization.IDENTITY_MAPPING);
- timing.end();
- proguardMapSupplier =
- ProguardMapSupplier.fromClassNameMapper(classNameMapper, options.minApiLevel);
- } else {
- proguardMapSupplier =
- ProguardMapSupplier.fromNamingLens(namingLens, application, options.minApiLevel);
- }
+ timing.begin("Line number remapping");
+ // When line number optimization is turned off the identity mapping for line numbers is
+ // used. We still run the line number optimizer to collect line numbers and inline frame
+ // information for the mapping file.
+ ClassNameMapper classNameMapper =
+ LineNumberOptimizer.run(
+ application,
+ appView.graphLense(),
+ namingLens,
+ options.lineNumberOptimization == LineNumberOptimization.OFF);
+ timing.end();
+ proguardMapSupplier =
+ ProguardMapSupplier.fromClassNameMapper(classNameMapper, options.minApiLevel);
// If a method filter is present don't produce output since the application is likely partial.
if (options.hasMethodsFilter()) {
diff --git a/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java b/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java
index d640a30..83f2e8e 100644
--- a/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java
+++ b/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java
@@ -52,23 +52,23 @@
return types.iterator().next();
}
Iterator<DexType> iterator = types.iterator();
- TypeLatticeElement join = getLatticeElement(iterator.next());
+ TypeLatticeElement result = getLatticeElement(iterator.next());
while (iterator.hasNext()) {
- join = TypeLatticeElement.join(appInfo, join, getLatticeElement(iterator.next()));
+ result = result.join(getLatticeElement(iterator.next()), appInfo);
}
// All types are reference types so the join is either a class or an array.
- if (join.isClassType()) {
- return join.asClassTypeLatticeElement().getClassType();
- } else if (join.isArrayType()) {
- return join.asArrayTypeLatticeElement().getArrayType();
+ if (result.isClassType()) {
+ return result.asClassTypeLatticeElement().getClassType();
+ } else if (result.isArrayType()) {
+ return result.asArrayTypeLatticeElement().getArrayType();
}
- throw new CompilationError("Unexpected join " + join + " of types: " +
+ throw new CompilationError("Unexpected join " + result + " of types: " +
String.join(", ",
types.stream().map(DexType::toSourceString).collect(Collectors.toList())));
}
private TypeLatticeElement getLatticeElement(DexType type) {
- return TypeLatticeElement.fromDexType(type, appInfo, true);
+ return TypeLatticeElement.fromDexType(type, true, appInfo);
}
public Map<Value, DexType> computeVerificationTypes() {
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index 22790b4..7538bb1 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -9,7 +9,8 @@
import com.android.tools.r8.kotlin.KotlinInfo;
import com.android.tools.r8.origin.Origin;
import com.google.common.base.MoreObjects;
-import com.google.common.collect.Iterators;
+import com.google.common.base.Predicates;
+import com.google.common.collect.Iterables;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
@@ -99,13 +100,23 @@
}
public Iterable<DexEncodedField> fields() {
- return () ->
- Iterators.concat(Iterators.forArray(instanceFields), Iterators.forArray(staticFields));
+ return fields(Predicates.alwaysTrue());
+ }
+
+ public Iterable<DexEncodedField> fields(final Predicate<? super DexEncodedField> predicate) {
+ return Iterables.concat(
+ Iterables.filter(Arrays.asList(instanceFields), predicate::test),
+ Iterables.filter(Arrays.asList(staticFields), predicate::test));
}
public Iterable<DexEncodedMethod> methods() {
- return () ->
- Iterators.concat(Iterators.forArray(directMethods), Iterators.forArray(virtualMethods));
+ return methods(Predicates.alwaysTrue());
+ }
+
+ public Iterable<DexEncodedMethod> methods(Predicate<? super DexEncodedMethod> predicate) {
+ return Iterables.concat(
+ Iterables.filter(Arrays.asList(directMethods), predicate::test),
+ Iterables.filter(Arrays.asList(virtualMethods), predicate::test));
}
@Override
@@ -412,6 +423,10 @@
return null;
}
+ public boolean isSerializable(AppInfo appInfo) {
+ return type.implementedInterfaces(appInfo).contains(appInfo.dexItemFactory.serializableType);
+ }
+
public boolean isExternalizable(AppInfo appInfo) {
return type.implementedInterfaces(appInfo).contains(appInfo.dexItemFactory.externalizableType);
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java
index 4b52a9e..0da9ead 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -95,17 +95,6 @@
this.debugInfo = debugInfo;
}
- public boolean hasDebugPositions() {
- if (debugInfo != null) {
- for (DexDebugEvent event : debugInfo.events) {
- if (event instanceof DexDebugEvent.Default) {
- return true;
- }
- }
- }
- return false;
- }
-
public DexDebugInfo debugInfoWithAdditionalFirstParameter(DexString name) {
if (debugInfo == null) {
return null;
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java b/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
index 1264a26..779678d 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
@@ -47,7 +47,7 @@
writer.putUleb128(delta);
}
- AdvancePC(int delta) {
+ public AdvancePC(int delta) {
this.delta = delta;
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java b/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
index fa856cb..6c6fda4 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
@@ -206,7 +206,10 @@
if (localsChanged()) {
assert emittedPc != pc;
int pcDelta = emittedPc == NO_PC_INFO ? pc : pc - emittedPc;
- events.add(factory.createAdvancePC(pcDelta));
+ assert pcDelta > 0 || emittedPc == NO_PC_INFO;
+ if (pcDelta > 0) {
+ events.add(factory.createAdvancePC(pcDelta));
+ }
emittedPc = pc;
emitLocalChangeEvents(emittedLocals, pendingLocals, lastKnownLocals, events, factory);
pendingLocalChanges = false;
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index f2cc53f..22a771b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -401,12 +401,6 @@
}
}
- public boolean hasDebugPositions() {
- checkIfObsolete();
- assert code != null && code.isDexCode();
- return code.asDexCode().hasDebugPositions();
- }
-
public int getClassFileVersion() {
checkIfObsolete();
assert classFileVersion >= 0;
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 90a098a..10e960c 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -217,6 +217,7 @@
public final LongMethods longMethods = new LongMethods();
public final ThrowableMethods throwableMethods = new ThrowableMethods();
public final ClassMethods classMethods = new ClassMethods();
+ public final EnumMethods enumMethods = new EnumMethods();
public final PrimitiveTypesBoxedTypeFields primitiveTypesBoxedTypeFields =
new PrimitiveTypesBoxedTypeFields();
public final AtomicFieldUpdaterMethods atomicFieldUpdaterMethods =
@@ -408,6 +409,20 @@
}
}
+ public class EnumMethods {
+
+ public DexMethod valueOf;
+
+ private EnumMethods() {
+ valueOf =
+ createMethod(
+ enumDescriptor,
+ valueOfMethodName,
+ enumDescriptor,
+ new DexString[] {classDescriptor, stringDescriptor});
+ }
+ }
+
/**
* All boxed types (Boolean, Byte, ...) have a field named TYPE which contains the Class object
* for the primitive type.
diff --git a/src/main/java/com/android/tools/r8/graph/DexTypeList.java b/src/main/java/com/android/tools/r8/graph/DexTypeList.java
index 8df5030..45a1548 100644
--- a/src/main/java/com/android/tools/r8/graph/DexTypeList.java
+++ b/src/main/java/com/android/tools/r8/graph/DexTypeList.java
@@ -66,8 +66,11 @@
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
- for (DexType type : values) {
- builder.append(' ').append(type);
+ if (values.length > 0) {
+ builder.append(values[0]);
+ for (int i = 1; i < values.length; i++) {
+ builder.append(' ').append(values[i]);
+ }
}
return builder.toString();
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ArrayTypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ArrayTypeLatticeElement.java
index 6b368b3..128e640 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/ArrayTypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ArrayTypeLatticeElement.java
@@ -52,7 +52,7 @@
@Override
public TypeLatticeElement arrayGet(AppInfo appInfo) {
- return fromDexType(getArrayElementType(appInfo.dexItemFactory), appInfo, true);
+ return fromDexType(getArrayElementType(appInfo.dexItemFactory), true, appInfo);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeAnalysis.java
index b8988c1..9711079 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeAnalysis.java
@@ -84,12 +84,12 @@
TypeLatticeElement derived;
if (argumentsSeen < 0) {
// Receiver
- derived = fromDexType(encodedMethod.method.holder, appInfo,
+ derived = fromDexType(encodedMethod.method.holder,
// Now we try inlining even when the receiver could be null.
- encodedMethod != context);
+ encodedMethod != context, appInfo);
} else {
DexType argType = encodedMethod.method.proto.parameters.values[argumentsSeen];
- derived = fromDexType(argType, appInfo, true);
+ derived = fromDexType(argType, true, appInfo);
}
argumentsSeen++;
updateTypeOfValue(outValue, derived);
@@ -149,8 +149,7 @@
private TypeLatticeElement computePhiType(Phi phi) {
// Type of phi(v1, v2, ..., vn) is the least upper bound of all those n operands.
- return TypeLatticeElement.join(
- appInfo, phi.getOperands().stream().map(Value::getTypeLattice));
+ return TypeLatticeElement.join(phi.getOperands().stream().map(Value::getTypeLattice), appInfo);
}
public static DexType getRefinedReceiverType(
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 a09039c..b63dbb2 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
@@ -11,6 +11,7 @@
import com.android.tools.r8.ir.code.MemberType;
import com.android.tools.r8.ir.code.Value;
import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Streams;
import java.util.ArrayDeque;
import java.util.HashSet;
import java.util.IdentityHashMap;
@@ -81,57 +82,55 @@
/**
* Computes the least upper bound of the current and the other elements.
*
+ * @param other {@link TypeLatticeElement} to join.
* @param appInfo {@link AppInfo}.
- * @param l1 {@link TypeLatticeElement} to join.
- * @param l2 {@link TypeLatticeElement} to join.
- * @return {@link TypeLatticeElement}, a least upper bound of {@param l1} and {@param l2}.
+ * @return {@link TypeLatticeElement}, a least upper bound of {@param this} and {@param other}.
*/
- public static TypeLatticeElement join(
- AppInfo appInfo, TypeLatticeElement l1, TypeLatticeElement l2) {
- if (l1.isBottom()) {
- return l2;
+ public TypeLatticeElement join(TypeLatticeElement other, AppInfo appInfo) {
+ if (isBottom()) {
+ return other;
}
- if (l2.isBottom()) {
- return l1;
+ if (other.isBottom()) {
+ return this;
}
- if (l1.isTop() || l2.isTop()) {
+ if (isTop() || other.isTop()) {
return TOP;
}
- if (l1.isNull()) {
- return l2.asNullable();
+ if (isNull()) {
+ return other.asNullable();
}
- if (l2.isNull()) {
- return l1.asNullable();
+ if (other.isNull()) {
+ return asNullable();
}
- if (l1.isPrimitive()) {
- return l2.isPrimitive()
+ if (isPrimitive()) {
+ return other.isPrimitive()
? PrimitiveTypeLatticeElement.join(
- l1.asPrimitiveTypeLatticeElement(), l2.asPrimitiveTypeLatticeElement())
+ asPrimitiveTypeLatticeElement(), other.asPrimitiveTypeLatticeElement())
: TOP;
}
- if (l2.isPrimitive()) {
- // By the above case, !(l1.isPrimitive())
+ if (other.isPrimitive()) {
+ // By the above case, !(isPrimitive())
return TOP;
}
- // From now on, l1 and l2 are reference types, but might be imprecise yet.
- assert l1.isReference() && l2.isReference();
- if (!l1.isPreciseType() || !l2.isPreciseType()) {
- if (l1.isReferenceInstance()) {
- return l1;
+ // From now on, this and other are reference types, but might be imprecise yet.
+ assert isReference() && other.isReference();
+ if (!isPreciseType() || !other.isPreciseType()) {
+ if (isReferenceInstance()) {
+ return this;
}
- assert l2.isReferenceInstance();
- return l2;
+ assert other.isReferenceInstance();
+ return other;
}
- // From now on, l1 and l2 are precise reference types, i.e., either ArrayType or ClassType.
- boolean isNullable = l1.isNullable() || l2.isNullable();
- if (l1.getClass() != l2.getClass()) {
+ // From now on, this and other are precise reference types, i.e., either ArrayType or ClassType.
+ boolean isNullable = isNullable() || other.isNullable();
+ if (getClass() != other.getClass()) {
return objectClassType(appInfo, isNullable);
}
- // From now on, l1.getClass() == l2.getClass()
- if (l1.isArrayType()) {
- assert l2.isArrayType();
- ArrayTypeLatticeElement a1 = l1.asArrayTypeLatticeElement();
- ArrayTypeLatticeElement a2 = l2.asArrayTypeLatticeElement();
+ // From now on, getClass() == other.getClass()
+ if (isArrayType()) {
+ assert other.isArrayType();
+ ArrayTypeLatticeElement a1 = asArrayTypeLatticeElement();
+ ArrayTypeLatticeElement a2 = other.asArrayTypeLatticeElement();
// Identical types are the same elements
if (a1.getArrayType() == a2.getArrayType()) {
return a1.isNullable() ? a1 : a2;
@@ -166,10 +165,10 @@
DexType arrayTypeLub = appInfo.dexItemFactory.createArrayType(a1Nesting, lub);
return new ArrayTypeLatticeElement(arrayTypeLub, isNullable);
}
- if (l1.isClassType()) {
- assert l2.isClassType();
- ClassTypeLatticeElement c1 = l1.asClassTypeLatticeElement();
- ClassTypeLatticeElement c2 = l2.asClassTypeLatticeElement();
+ if (isClassType()) {
+ assert other.isClassType();
+ ClassTypeLatticeElement c1 = asClassTypeLatticeElement();
+ ClassTypeLatticeElement c2 = other.asClassTypeLatticeElement();
DexType lubType =
c1.getClassType().computeLeastUpperBoundOfClasses(appInfo, c2.getClassType());
return new ClassTypeLatticeElement(lubType, isNullable,
@@ -258,47 +257,43 @@
}
public static BinaryOperator<TypeLatticeElement> joiner(AppInfo appInfo) {
- return (l1, l2) -> join(appInfo, l1, l2);
+ return (l1, l2) -> l1.join(l2, appInfo);
}
- public static TypeLatticeElement join(AppInfo appInfo, Stream<TypeLatticeElement> types) {
+ public static TypeLatticeElement join(Stream<TypeLatticeElement> types, AppInfo appInfo) {
BinaryOperator<TypeLatticeElement> joiner = joiner(appInfo);
return types.reduce(BottomTypeLatticeElement.getInstance(), joiner, joiner);
}
- public static TypeLatticeElement join(
- AppInfo appInfo, Stream<DexType> types, boolean isNullable) {
- return join(appInfo, types.map(t -> fromDexType(t, appInfo, isNullable)));
+ public static TypeLatticeElement joinTypes(
+ Iterable<DexType> types, boolean isNullable, AppInfo appInfo) {
+ return join(Streams.stream(types).map(t -> fromDexType(t, isNullable, appInfo)), appInfo);
}
/**
* Determines the strict partial order of the given {@link TypeLatticeElement}s.
*
+ * @param other expected to be *strictly* bigger than {@param this}
* @param appInfo {@link AppInfo} to compute the least upper bound of {@link TypeLatticeElement}
- * @param l1 subject {@link TypeLatticeElement}
- * @param l2 expected to be *strict* bigger than {@param l1}
- * @return {@code true} if {@param l1} is strictly less than {@param l2}.
+ * @return {@code true} if {@param this} is strictly less than {@param other}.
*/
- public static boolean strictlyLessThan(
- AppInfo appInfo, TypeLatticeElement l1, TypeLatticeElement l2) {
- if (l1.equals(l2)) {
+ public boolean strictlyLessThan(TypeLatticeElement other, AppInfo appInfo) {
+ if (equals(other)) {
return false;
}
- TypeLatticeElement lub = join(appInfo, Stream.of(l1, l2));
- return !l1.equals(lub) && l2.equals(lub);
+ TypeLatticeElement lub = join(other, appInfo);
+ return !equals(lub) && other.equals(lub);
}
/**
* Determines the partial order of the given {@link TypeLatticeElement}s.
*
+ * @param other expected to be bigger than or equal to {@param this}
* @param appInfo {@link AppInfo} to compute the least upper bound of {@link TypeLatticeElement}
- * @param l1 subject {@link TypeLatticeElement}
- * @param l2 expected to be bigger than or equal to {@param l1}
- * @return {@code true} if {@param l1} is less than or equal to {@param l2}.
+ * @return {@code true} if {@param this} is less than or equal to {@param other}.
*/
- public static boolean lessThanOrEqual(
- AppInfo appInfo, TypeLatticeElement l1, TypeLatticeElement l2) {
- return l1.equals(l2) || strictlyLessThan(appInfo, l1, l2);
+ public boolean lessThanOrEqual(TypeLatticeElement other, AppInfo appInfo) {
+ return equals(other) || strictlyLessThan(other, appInfo);
}
/**
@@ -404,14 +399,14 @@
}
public static TypeLatticeElement classClassType(AppInfo appInfo) {
- return fromDexType(appInfo.dexItemFactory.classType, appInfo, false);
+ return fromDexType(appInfo.dexItemFactory.classType, false, appInfo);
}
public static TypeLatticeElement stringClassType(AppInfo appInfo) {
- return fromDexType(appInfo.dexItemFactory.stringType, appInfo, false);
+ return fromDexType(appInfo.dexItemFactory.stringType, false, appInfo);
}
- public static TypeLatticeElement fromDexType(DexType type, AppInfo appInfo, boolean isNullable) {
+ public static TypeLatticeElement fromDexType(DexType type, boolean isNullable, AppInfo appInfo) {
if (type == DexItemFactory.nullValueType) {
return NULL;
}
@@ -483,8 +478,8 @@
}
public TypeLatticeElement checkCast(AppInfo appInfo, DexType castType) {
- TypeLatticeElement castTypeLattice = fromDexType(castType, appInfo, isNullable());
- if (lessThanOrEqual(appInfo, this, castTypeLattice)) {
+ TypeLatticeElement castTypeLattice = fromDexType(castType, isNullable(), appInfo);
+ if (lessThanOrEqual(castTypeLattice, appInfo)) {
return this;
}
return castTypeLattice;
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java
index 7caaff9..705bb97 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java
@@ -366,7 +366,7 @@
if (downcast != null) {
Value receiver = invoke.inValues().get(0);
TypeLatticeElement castTypeLattice = TypeLatticeElement.fromDexType(
- downcast, appInfo, receiver.getTypeLattice().isNullable());
+ downcast, receiver.getTypeLattice().isNullable(), appInfo);
CheckCast castInstruction =
new CheckCast(code.createValue(castTypeLattice), receiver, downcast);
castInstruction.setPosition(invoke.getPosition());
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 8d461bd..03d718f 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
@@ -133,12 +133,12 @@
TypeLatticeElement outType = outValue().getTypeLattice();
TypeLatticeElement castType =
- TypeLatticeElement.fromDexType(getType(), appInfo, inType.isNullable());
+ TypeLatticeElement.fromDexType(getType(), inType.isNullable(), appInfo);
- if (TypeLatticeElement.lessThanOrEqual(appInfo, inType, castType)) {
+ if (inType.lessThanOrEqual(castType, appInfo)) {
// Cast can be removed. Check that it is sound to replace all users of the out-value by the
// in-value.
- assert TypeLatticeElement.lessThanOrEqual(appInfo, inType, outType);
+ assert inType.lessThanOrEqual(outType, appInfo);
// 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/ConstClass.java b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
index 401656c..7aa9a62 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
@@ -105,7 +105,7 @@
@Override
public TypeLatticeElement evaluate(AppInfo appInfo) {
- return TypeLatticeElement.fromDexType(appInfo.dexItemFactory.classType, appInfo, false);
+ return TypeLatticeElement.fromDexType(appInfo.dexItemFactory.classType, false, appInfo);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java b/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java
index ffc4359..95aa81c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java
@@ -91,7 +91,7 @@
@Override
public TypeLatticeElement evaluate(AppInfo appInfo) {
- return TypeLatticeElement.fromDexType(appInfo.dexItemFactory.methodHandleType, appInfo, false);
+ return TypeLatticeElement.fromDexType(appInfo.dexItemFactory.methodHandleType, false, appInfo);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstMethodType.java b/src/main/java/com/android/tools/r8/ir/code/ConstMethodType.java
index 81d7b28..d258839 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstMethodType.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstMethodType.java
@@ -91,7 +91,7 @@
@Override
public TypeLatticeElement evaluate(AppInfo appInfo) {
- return TypeLatticeElement.fromDexType(appInfo.dexItemFactory.methodTypeType, appInfo, false);
+ return TypeLatticeElement.fromDexType(appInfo.dexItemFactory.methodTypeType, false, appInfo);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstString.java b/src/main/java/com/android/tools/r8/ir/code/ConstString.java
index 85fd784..e05a55f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstString.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstString.java
@@ -133,6 +133,6 @@
@Override
public TypeLatticeElement evaluate(AppInfo appInfo) {
- return TypeLatticeElement.fromDexType(appInfo.dexItemFactory.stringType, appInfo, false);
+ return TypeLatticeElement.fromDexType(appInfo.dexItemFactory.stringType, false, appInfo);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
index 0c8c372..3d30b82 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
@@ -135,7 +135,7 @@
@Override
public TypeLatticeElement evaluate(AppInfo appInfo) {
- return TypeLatticeElement.fromDexType(field.type, appInfo, true);
+ return TypeLatticeElement.fromDexType(field.type, true, appInfo);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index d3355b4..44a7a8e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -406,7 +406,13 @@
if (other.getClass() != getClass()) {
return false;
}
- if (!identicalNonValueParts(other)) {
+ // In debug mode or if the instruction can throw we must account for positions, in release mode
+ // we do want to share non-throwing instructions even if their positions differ.
+ if (instructionTypeCanThrow() || allocator.getOptions().debug) {
+ if (!identicalNonValueParts(other)) {
+ return false;
+ }
+ } else if (!identicalNonValueNonPositionParts(other)) {
return false;
}
if (isInvokeDirect() && !asInvokeDirect().sameConstructorReceiverValue(other.asInvoke())) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/Invoke.java b/src/main/java/com/android/tools/r8/ir/code/Invoke.java
index b755336..468f3d05 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Invoke.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Invoke.java
@@ -280,6 +280,6 @@
if (returnType.isVoidType()) {
throw new Unreachable("void methods have no type.");
}
- return TypeLatticeElement.fromDexType(returnType, appInfo, true);
+ return TypeLatticeElement.fromDexType(returnType, true, appInfo);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/MoveException.java b/src/main/java/com/android/tools/r8/ir/code/MoveException.java
index 562b33d..8166f6e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/MoveException.java
+++ b/src/main/java/com/android/tools/r8/ir/code/MoveException.java
@@ -105,7 +105,7 @@
if (targets.get(i) == currentBlock) {
DexType guard = guards.get(i);
exceptionTypes.add(
- guard == dexItemFactory.catchAllType
+ guard == DexItemFactory.catchAllType
? dexItemFactory.throwableType
: guard);
}
@@ -122,8 +122,6 @@
@Override
public TypeLatticeElement evaluate(AppInfo appInfo) {
Set<DexType> exceptionTypes = collectExceptionTypes(getBlock(), appInfo.dexItemFactory);
- return TypeLatticeElement.join(
- appInfo,
- exceptionTypes.stream().map(t -> TypeLatticeElement.fromDexType(t, appInfo, false)));
+ return TypeLatticeElement.joinTypes(exceptionTypes, false, appInfo);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
index 29b263f..18a60d4 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
@@ -107,7 +107,7 @@
@Override
public TypeLatticeElement evaluate(AppInfo appInfo) {
- return TypeLatticeElement.fromDexType(clazz, appInfo, false);
+ return TypeLatticeElement.fromDexType(clazz, false, appInfo);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
index 9ef7ed4..e767246 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
@@ -145,7 +145,7 @@
@Override
public TypeLatticeElement evaluate(AppInfo appInfo) {
- return TypeLatticeElement.fromDexType(field.type, appInfo, true);
+ return TypeLatticeElement.fromDexType(field.type, true, appInfo);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index b471e12..1853e8b 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -680,8 +680,7 @@
Position position = source.getCanonicalDebugPositionAtOffset(moveExceptionItem.targetOffset);
if (moveExceptionDest >= 0) {
Set<DexType> exceptionTypes = MoveException.collectExceptionTypes(currentBlock, getFactory());
- TypeLatticeElement typeLattice = TypeLatticeElement.join(appInfo,
- exceptionTypes.stream().map(t -> TypeLatticeElement.fromDexType(t, appInfo, false)));
+ TypeLatticeElement typeLattice = TypeLatticeElement.joinTypes(exceptionTypes, false, appInfo);
Value out = writeRegister(moveExceptionDest, typeLattice, ThrowingInfo.NO_THROW, null);
MoveException moveException = new MoveException(out);
moveException.setPosition(position);
@@ -731,7 +730,7 @@
DebugLocalInfo local = getOutgoingLocal(register);
// TODO(b/72693244): Update nullability if this is for building inlinee's IR.
TypeLatticeElement receiver =
- TypeLatticeElement.fromDexType(method.method.getHolder(), appInfo, false);
+ TypeLatticeElement.fromDexType(method.method.getHolder(), false, appInfo);
Value value = writeRegister(register, receiver, ThrowingInfo.NO_THROW, local);
addInstruction(new Argument(value));
value.markAsThis();
@@ -872,7 +871,7 @@
public void addCheckCast(int value, DexType type) {
Value in = readRegister(value, ValueType.OBJECT);
TypeLatticeElement castTypeLattice =
- TypeLatticeElement.fromDexType(type, appInfo, in.getTypeLattice().isNullable());
+ TypeLatticeElement.fromDexType(type, in.getTypeLattice().isNullable(), appInfo);
Value out = writeRegister(value, castTypeLattice, ThrowingInfo.CAN_THROW);
CheckCast instruction = new CheckCast(out, in, type);
assert instruction.instructionTypeCanThrow();
@@ -931,7 +930,7 @@
null /* sourceString */);
}
TypeLatticeElement typeLattice =
- TypeLatticeElement.fromDexType(appInfo.dexItemFactory.methodHandleType, appInfo, false);
+ TypeLatticeElement.fromDexType(appInfo.dexItemFactory.methodHandleType, false, appInfo);
Value out = writeRegister(dest, typeLattice, ThrowingInfo.CAN_THROW);
ConstMethodHandle instruction = new ConstMethodHandle(out, methodHandle);
add(instruction);
@@ -945,7 +944,7 @@
null /* sourceString */);
}
TypeLatticeElement typeLattice =
- TypeLatticeElement.fromDexType(appInfo.dexItemFactory.methodTypeType, appInfo, false);
+ TypeLatticeElement.fromDexType(appInfo.dexItemFactory.methodTypeType, false, appInfo);
Value out = writeRegister(dest, typeLattice, ThrowingInfo.CAN_THROW);
ConstMethodType instruction = new ConstMethodType(out, methodType);
add(instruction);
@@ -1104,7 +1103,7 @@
MemberType type = MemberType.fromDexType(field.type);
Value in = readRegister(object, ValueType.OBJECT);
Value out = writeRegister(
- dest, TypeLatticeElement.fromDexType(field.type, appInfo, true), ThrowingInfo.CAN_THROW);
+ dest, TypeLatticeElement.fromDexType(field.type, true, appInfo), ThrowingInfo.CAN_THROW);
out.setKnownToBeBoolean(type == MemberType.BOOLEAN);
InstanceGet instruction = new InstanceGet(type, out, in, field);
assert instruction.instructionTypeCanThrow();
@@ -1415,7 +1414,7 @@
public void addNewArrayEmpty(int dest, int size, DexType type) {
assert type.isArrayType();
Value in = readRegister(size, ValueType.INT);
- TypeLatticeElement arrayTypeLattice = TypeLatticeElement.fromDexType(type, appInfo, false);
+ TypeLatticeElement arrayTypeLattice = TypeLatticeElement.fromDexType(type, false, appInfo);
Value out = writeRegister(dest, arrayTypeLattice, ThrowingInfo.CAN_THROW);
NewArrayEmpty instruction = new NewArrayEmpty(out, in, type);
assert instruction.instructionTypeCanThrow();
@@ -1427,7 +1426,7 @@
}
public void addNewInstance(int dest, DexType type) {
- TypeLatticeElement instanceType = TypeLatticeElement.fromDexType(type, appInfo, false);
+ TypeLatticeElement instanceType = TypeLatticeElement.fromDexType(type, false, appInfo);
Value out = writeRegister(dest, instanceType, ThrowingInfo.CAN_THROW);
NewInstance instruction = new NewInstance(type, out);
assert instruction.instructionTypeCanThrow();
@@ -1455,7 +1454,7 @@
public void addStaticGet(int dest, DexField field) {
MemberType type = MemberType.fromDexType(field.type);
Value out = writeRegister(
- dest, TypeLatticeElement.fromDexType(field.type, appInfo, true), ThrowingInfo.CAN_THROW);
+ dest, TypeLatticeElement.fromDexType(field.type, true, appInfo), ThrowingInfo.CAN_THROW);
out.setKnownToBeBoolean(type == MemberType.BOOLEAN);
StaticGet instruction = new StaticGet(type, out, field);
assert instruction.instructionTypeCanThrow();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
index 129ed58..22113f8 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
@@ -277,7 +277,7 @@
if (lambdaInstanceValue == null) {
// The out value might be empty in case it was optimized out.
lambdaInstanceValue = code.createValue(
- TypeLatticeElement.fromDexType(lambdaClass.type, appInfo, true));
+ TypeLatticeElement.fromDexType(lambdaClass.type, true, appInfo));
}
// For stateless lambdas we replace InvokeCustom instruction with StaticGet
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/StringConcatRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/StringConcatRewriter.java
index c805494..1b13398 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/StringConcatRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/StringConcatRewriter.java
@@ -337,7 +337,7 @@
// new-instance v0, StringBuilder
TypeLatticeElement stringBuilderTypeLattice =
- TypeLatticeElement.fromDexType(factory.stringBuilderType, appInfo, false);
+ TypeLatticeElement.fromDexType(factory.stringBuilderType, false, appInfo);
Value sbInstance = code.createValue(stringBuilderTypeLattice);
appendInstruction(new NewInstance(factory.stringBuilderType, sbInstance));
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 f8b85de..93df273 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
@@ -297,7 +297,7 @@
BasicBlock rethrowBlock = BasicBlock.createRethrowBlock(
code,
lastSelfRecursiveCall.getPosition(),
- TypeLatticeElement.fromDexType(guard, appInfo, true));
+ TypeLatticeElement.fromDexType(guard, true, appInfo));
code.blocks.add(rethrowBlock);
// Add catch handler to the block containing the last recursive call.
newBlock.addCatchHandler(rethrowBlock, guard);
@@ -1660,11 +1660,11 @@
if (inTypeLattice.isPreciseType() || inTypeLattice.isNull()) {
TypeLatticeElement outTypeLattice = outValue.getTypeLattice();
TypeLatticeElement castTypeLattice =
- TypeLatticeElement.fromDexType(castType, appInfo, inTypeLattice.isNullable());
+ TypeLatticeElement.fromDexType(castType, inTypeLattice.isNullable(), appInfo);
assert inTypeLattice.nullElement().lessThanOrEqual(outTypeLattice.nullElement());
- if (TypeLatticeElement.lessThanOrEqual(appInfo, inTypeLattice, castTypeLattice)) {
+ if (inTypeLattice.lessThanOrEqual(castTypeLattice, appInfo)) {
// 1) Trivial cast.
// A a = ...
// A a' = (A) a;
@@ -1672,7 +1672,7 @@
// A < B
// A a = ...
// B b = (B) a;
- assert TypeLatticeElement.lessThanOrEqual(appInfo, inTypeLattice, outTypeLattice);
+ assert inTypeLattice.lessThanOrEqual(outTypeLattice, appInfo);
needToRemoveTrivialPhis = needToRemoveTrivialPhis || outValue.numberOfPhiUsers() != 0;
removeOrReplaceByDebugLocalWrite(checkCast, it, inValue, outValue);
} else {
@@ -3013,7 +3013,7 @@
DexType javaLangSystemType = dexItemFactory.createType("Ljava/lang/System;");
DexType javaIoPrintStreamType = dexItemFactory.createType("Ljava/io/PrintStream;");
Value out = code.createValue(
- TypeLatticeElement.fromDexType(javaIoPrintStreamType, appInfo, false));
+ TypeLatticeElement.fromDexType(javaIoPrintStreamType, false, appInfo));
DexProto proto = dexItemFactory.createProto(dexItemFactory.voidType, dexItemFactory.objectType);
DexMethod print = dexItemFactory.createMethod(javaIoPrintStreamType, proto, "print");
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
index 37cfb44..e7b6ca8 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
@@ -110,14 +110,13 @@
Value receiver = invoke.getReceiver();
TypeLatticeElement receiverTypeLattice = receiver.getTypeLattice();
TypeLatticeElement castTypeLattice =
- TypeLatticeElement.fromDexType(holderType, appInfo, receiverTypeLattice.isNullable());
+ TypeLatticeElement.fromDexType(holderType, receiverTypeLattice.isNullable(), appInfo);
// Avoid adding trivial cast and up-cast.
// We should not use strictlyLessThan(castType, receiverType), which detects downcast,
// due to side-casts, e.g., A (unused) < I, B < I, and cast from A to B.
// TODO(b/72693244): Soon, there won't be a value with imprecise type at this point.
if (!receiverTypeLattice.isPreciseType()
- || !TypeLatticeElement.lessThanOrEqual(
- appInfo, receiverTypeLattice, castTypeLattice)) {
+ || !receiverTypeLattice.lessThanOrEqual(castTypeLattice, appInfo)) {
Value newReceiver = null;
// If this value is ever downcast'ed to the same holder type before, and that casted
// value is safely accessible, i.e., the current line is dominated by that cast, use it.
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 eed1c47..9d36037 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
@@ -108,31 +108,12 @@
// goto A
//
// A: ...y // blockWithNonNullInstruction
- //
+ boolean split = block.hasCatchHandlers();
BasicBlock blockWithNonNullInstruction =
- block.hasCatchHandlers() ? iterator.split(code, blockIterator) : block;
- // Next, add non-null fake IR, e.g.,
- // ...x
- // invoke(rcv, ...)
- // goto A
- // ...
- // A: non_null_rcv <- non-null(rcv)
- // ...y
- Value nonNullValue = code.createValue(
- knownToBeNonNullValue.getTypeLattice(),
- knownToBeNonNullValue.getLocalInfo());
- nonNullValueCollector.add(nonNullValue);
- NonNull nonNull = new NonNull(nonNullValue, knownToBeNonNullValue, current);
- nonNull.setPosition(current.getPosition());
- if (blockWithNonNullInstruction != block) {
- // If we split, add non-null IR on top of the new split block.
- blockWithNonNullInstruction.listIterator().add(nonNull);
- } else {
- // Otherwise, just add it to the current block at the position of the iterator.
- iterator.add(nonNull);
- }
- // Then, replace all users of the original value that are dominated by either the current
- // block or the new split-off block. Since NPE can be explicitly caught, nullness should be
+ split ? iterator.split(code, blockIterator) : block;
+
+ // Find all users of the original value that are dominated by either the current block
+ // or the new split-off block. Since NPE can be explicitly caught, nullness should be
// propagated through dominance.
Set<Instruction> users = knownToBeNonNullValue.uniqueUsers();
Set<Instruction> dominatedUsers = Sets.newIdentityHashSet();
@@ -142,14 +123,13 @@
for (BasicBlock dominatee : dominatorTree.dominatedBlocks(blockWithNonNullInstruction)) {
dominatedBlocks.add(dominatee);
InstructionListIterator dominateeIterator = dominatee.listIterator();
- if (dominatee == blockWithNonNullInstruction) {
- // In the block with the inserted non null instruction, skip instructions up to and
- // including the newly inserted instruction.
- dominateeIterator.nextUntil(instruction -> instruction == nonNull);
+ if (dominatee == blockWithNonNullInstruction && !split) {
+ // In the block where the non null instruction will be inserted, skip instructions up
+ // to and including the insertion point.
+ dominateeIterator.nextUntil(instruction -> instruction == current);
}
while (dominateeIterator.hasNext()) {
Instruction potentialUser = dominateeIterator.next();
- assert potentialUser != nonNull;
if (users.contains(potentialUser)) {
dominatedUsers.add(potentialUser);
}
@@ -162,8 +142,35 @@
dominatedPhiUsersWithPositions.put(user, dominatedPredecessorIndexes);
}
}
- knownToBeNonNullValue.replaceSelectiveUsers(
- nonNullValue, dominatedUsers, dominatedPhiUsersWithPositions);
+
+ // Only insert non-null instruction if it is ever used.
+ if (!dominatedUsers.isEmpty() || !dominatedPhiUsersWithPositions.isEmpty()) {
+ // Add non-null fake IR, e.g.,
+ // ...x
+ // invoke(rcv, ...)
+ // goto A
+ // ...
+ // A: non_null_rcv <- non-null(rcv)
+ // ...y
+ Value nonNullValue =
+ code.createValue(
+ knownToBeNonNullValue.getTypeLattice(), knownToBeNonNullValue.getLocalInfo());
+ nonNullValueCollector.add(nonNullValue);
+ NonNull nonNull = new NonNull(nonNullValue, knownToBeNonNullValue, current);
+ nonNull.setPosition(current.getPosition());
+ if (blockWithNonNullInstruction != block) {
+ // If we split, add non-null IR on top of the new split block.
+ blockWithNonNullInstruction.listIterator().add(nonNull);
+ } else {
+ // Otherwise, just add it to the current block at the position of the iterator.
+ iterator.add(nonNull);
+ }
+
+ // Replace all users of the original value that are dominated by either the current
+ // block or the new split-off block.
+ knownToBeNonNullValue.replaceSelectiveUsers(
+ nonNullValue, dominatedUsers, dominatedPhiUsersWithPositions);
+ }
}
// Add non-null on top of the successor block if the current block ends with a null check.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java
index eb140c4..e6791f7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java
@@ -253,7 +253,8 @@
changed = true;
int otherPredIndex = blockToIndex.get(wrapper);
BasicBlock otherPred = block.getPredecessors().get(otherPredIndex);
- assert Objects.equals(pred.getPosition(), otherPred.getPosition());
+ assert !allocator.getOptions().debug
+ || Objects.equals(pred.getPosition(), otherPred.getPosition());
pred.clearCatchHandlers();
pred.getInstructions().clear();
equivalence.clearComputedHash(pred);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupCodeStrategy.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupCodeStrategy.java
index 538d7be..51da91d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupCodeStrategy.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupCodeStrategy.java
@@ -115,7 +115,7 @@
NewInstance patchedNewInstance = new NewInstance(
group.getGroupClassType(),
context.code.createValue(
- TypeLatticeElement.fromDexType(newInstance.clazz, context.appInfo, false)));
+ TypeLatticeElement.fromDexType(newInstance.clazz, false, context.appInfo)));
context.instructions().replaceCurrentInstruction(patchedNewInstance);
}
@@ -160,7 +160,7 @@
// Since all captured values of non-primitive types are stored in fields of type
// java.lang.Object, we need to cast them to appropriate type to satisfy the verifier.
TypeLatticeElement castTypeLattice =
- TypeLatticeElement.fromDexType(fieldType, context.appInfo, false);
+ TypeLatticeElement.fromDexType(fieldType, false, context.appInfo);
Value newValue = context.code.createValue(castTypeLattice, newInstanceGet.getLocalInfo());
newInstanceGet.outValue().replaceUsers(newValue);
CheckCast cast = new CheckCast(newValue, newInstanceGet.outValue(), fieldType);
@@ -187,7 +187,7 @@
new StaticGet(
staticGet.getType(),
context.code.createValue(
- TypeLatticeElement.fromDexType(staticGet.getField().type, context.appInfo, true)),
+ TypeLatticeElement.fromDexType(staticGet.getField().type, true, context.appInfo)),
mapSingletonInstanceField(context.factory, staticGet.getField())));
}
@@ -222,7 +222,7 @@
private Value createValueForType(CodeProcessor context, DexType returnType) {
return returnType == context.factory.voidType ? null :
context.code.createValue(
- TypeLatticeElement.fromDexType(returnType, context.appInfo, true));
+ TypeLatticeElement.fromDexType(returnType, true, context.appInfo));
}
private List<Value> mapInitializerArgs(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
index 669baf9..ac3a8af 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
@@ -273,7 +273,7 @@
new StaticGet(
MemberType.fromDexType(field.type),
code.createValue(
- TypeLatticeElement.fromDexType(field.type, classStaticizer.appInfo, true),
+ TypeLatticeElement.fromDexType(field.type, true, classStaticizer.appInfo),
outValue.getLocalInfo()),
field
)
@@ -303,7 +303,7 @@
Value newOutValue = method.proto.returnType.isVoidType() ? null
: code.createValue(
TypeLatticeElement.fromDexType(
- method.proto.returnType, classStaticizer.appInfo, true),
+ method.proto.returnType, true, classStaticizer.appInfo),
outValue == null ? null : outValue.getLocalInfo());
it.replaceCurrentInstruction(
new InvokeStatic(newMethod, newOutValue, invoke.inValues()));
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNamingForMapApplier.java b/src/main/java/com/android/tools/r8/naming/ClassNamingForMapApplier.java
index 5dcc7b8..ba7963b 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNamingForMapApplier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNamingForMapApplier.java
@@ -151,8 +151,8 @@
MemberNaming lookupByOriginalItem(DexField field) {
for (Map.Entry<FieldSignature, MemberNaming> entry : fieldMembers.entrySet()) {
FieldSignature signature = entry.getKey();
- if (signature.name.equals(field.name.toString())
- && signature.type.equals(field.type.getName())) {
+ if (signature.name.equals(field.name.toSourceString())
+ && signature.type.equals(field.type.toSourceString())) {
return entry.getValue();
}
}
@@ -162,11 +162,11 @@
protected MemberNaming lookupByOriginalItem(DexMethod method) {
for (Map.Entry<MethodSignature, MemberNaming> entry : methodMembers.entrySet()) {
MethodSignature signature = entry.getKey();
- if (signature.name.equals(method.name.toString())
- && signature.type.equals(method.proto.returnType.toString())
+ if (signature.name.equals(method.name.toSourceString())
+ && signature.type.equals(method.proto.returnType.toSourceString())
&& Arrays.equals(signature.parameters,
Arrays.stream(method.proto.parameters.values)
- .map(DexType::toString).toArray(String[]::new))) {
+ .map(DexType::toSourceString).toArray(String[]::new))) {
return entry.getValue();
}
}
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
index f45a3de..c47a7bd 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
@@ -401,7 +401,7 @@
builder.append(minifiedRange).append(':');
}
builder.append(signature);
- if (originalRange != null) {
+ if (originalRange != null && !minifiedRange.equals(originalRange)) {
builder.append(":").append(originalRange);
}
builder.append(" -> ").append(renamedName);
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java
index 25df058..bbf8259 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java
@@ -22,8 +22,8 @@
import com.android.tools.r8.utils.ArrayUtils;
import com.android.tools.r8.utils.ThrowingConsumer;
import com.android.tools.r8.utils.Timing;
-import com.google.common.collect.Sets;
import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap;
+import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Set;
@@ -128,42 +128,80 @@
private void applyMemberMapping(DexType from, ChainedClassNaming classNaming) {
DexClass clazz = appInfo.definitionFor(from);
- if (clazz == null) return;
+ if (clazz == null) {
+ return;
+ }
- final Set<MemberNaming> appliedMemberNaming = Sets.newIdentityHashSet();
+ // We regard mappings as _complete_ if they cover literally everything, but that's too ideal.
+ // When visiting members with member mappings, obviously, there are two incomplete cases:
+ // no matched member or no matched mapping.
+ //
+ // 1. No matched member
+ // class A { // : X
+ // void foo(); // : a
+ // }
+ //
+ // class B extends A { // : Y
+ // @Override void foo(); // no mapping
+ // }
+ //
+ // For this case, we have chained class naming and move upward to search for super class's
+ // member mapping. One corner case we should be careful here is to resolve on the correct
+ // mapping, e.g.,
+ //
+ // class B extends A { // : Y
+ // private void foo(); // no mapping, should be not renamed to a
+ // }
+
+ final Set<MemberNaming.Signature> appliedMemberSignature = new HashSet<>();
clazz.forEachField(encodedField -> {
MemberNaming memberNaming = classNaming.lookupByOriginalItem(encodedField.field);
if (memberNaming != null) {
- appliedMemberNaming.add(memberNaming);
+ appliedMemberSignature.add(memberNaming.getOriginalSignature());
applyFieldMapping(encodedField.field, memberNaming);
}
});
clazz.forEachMethod(encodedMethod -> {
- MemberNaming memberNaming = classNaming.lookupByOriginalItem(encodedMethod.method);
+ MemberNaming memberNaming =
+ classNaming.lookupByOriginalItem(encodedMethod.method, encodedMethod.isPrivateMethod());
if (memberNaming != null) {
- appliedMemberNaming.add(memberNaming);
+ appliedMemberSignature.add(memberNaming.getOriginalSignature());
applyMethodMapping(encodedMethod.method, memberNaming);
}
});
+ // 2. No matched mapping
+ // class A { // : X
+ // void foo(); // : a
+ // }
+ //
+ // class B extends A { // : Y
+ // // no overriding, but has mapping: void foo() -> a
+ // }
+ //
// We need to handle a class that extends another class where some members are not overridden,
// resulting in absence of definitions. References to those members need to be redirected via
// the lense as well.
+ // The caveat is, since such members don't exist, we pretend to see their definitions.
+ // We should ensure that they indeed don't exist. Otherwise, legitimately different members,
+ // e.g., private methods with same names, could be mapped to a wrong renamed name.
classNaming.forAllFieldNaming(memberNaming -> {
- if (!appliedMemberNaming.contains(memberNaming)) {
- DexField pretendedOriginalField =
- ((FieldSignature) memberNaming.getOriginalSignature())
- .toDexField(appInfo.dexItemFactory, from);
- applyFieldMapping(pretendedOriginalField, memberNaming);
+ FieldSignature signature = (FieldSignature) memberNaming.getOriginalSignature();
+ if (!appliedMemberSignature.contains(signature)) {
+ DexField pretendedOriginalField = signature.toDexField(appInfo.dexItemFactory, from);
+ if (appInfo.definitionFor(pretendedOriginalField) == null) {
+ applyFieldMapping(pretendedOriginalField, memberNaming);
+ }
}
});
classNaming.forAllMethodNaming(memberNaming -> {
- if (!appliedMemberNaming.contains(memberNaming)) {
- DexMethod pretendedOriginalMethod =
- ((MethodSignature) memberNaming.getOriginalSignature())
- .toDexMethod(appInfo.dexItemFactory, from);
- applyMethodMapping(pretendedOriginalMethod, memberNaming);
+ MethodSignature signature = (MethodSignature) memberNaming.getOriginalSignature();
+ if (!appliedMemberSignature.contains(signature)) {
+ DexMethod pretendedOriginalMethod = signature.toDexMethod(appInfo.dexItemFactory, from);
+ if (appInfo.definitionFor(pretendedOriginalMethod) == null) {
+ applyMethodMapping(pretendedOriginalMethod, memberNaming);
+ }
}
});
}
@@ -229,6 +267,15 @@
}
}
+ protected MemberNaming lookupByOriginalItem(DexMethod method, boolean isPrivate) {
+ // If the current method is overridable, use chained mappings.
+ if (!isPrivate) {
+ return lookupByOriginalItem(method);
+ }
+ // Otherwise, just look up the current class's mappings only.
+ return super.lookupByOriginalItem(method);
+ }
+
@Override
protected MemberNaming lookupByOriginalItem(DexMethod method) {
MemberNaming memberNaming = super.lookupByOriginalItem(method);
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapError.java b/src/main/java/com/android/tools/r8/naming/ProguardMapError.java
index ce659e1..684c79b 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapError.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapError.java
@@ -19,20 +19,20 @@
static ProguardMapError keptTypeWasRenamed(DexType type, String keptName, String rename) {
return new ProguardMapError(
- "Warning: " + type + createMessageForConflict(keptName, rename));
+ type + createMessageForConflict(keptName, rename));
}
static ProguardMapError keptMethodWasRenamed(DexMethod method, String keptName, String rename) {
return new ProguardMapError(
- "Warning: " + method.toSourceString() + createMessageForConflict(keptName, rename));
+ method.toSourceString() + createMessageForConflict(keptName, rename));
}
static ProguardMapError keptFieldWasRenamed(DexField field, String keptName, String rename) {
return new ProguardMapError(
- "Warning: " + field.toSourceString() + createMessageForConflict(keptName, rename));
+ field.toSourceString() + createMessageForConflict(keptName, rename));
}
private static String createMessageForConflict(String keptName, String rename) {
- return " is not being kept as '" + keptName + "', but remapped to '" + rename + "'";
+ return " is not being kept as " + keptName + ", but remapped to " + rename;
}
}
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
index 0b6a84d..3c300b6 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
@@ -12,8 +12,10 @@
import com.android.tools.r8.graph.DexProgramClass;
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.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.utils.InternalOptions;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import java.util.Collections;
@@ -27,12 +29,15 @@
private final AppInfoWithLiveness appInfo;
private final GraphLense lense;
+ private final InternalOptions options;
+
private final MemberRebindingLense.Builder builder;
- public MemberRebindingAnalysis(AppView<AppInfoWithLiveness> appView) {
+ public MemberRebindingAnalysis(AppView<AppInfoWithLiveness> appView, InternalOptions options) {
assert appView.graphLense().isContextFreeForMethods();
this.appInfo = appView.appInfo();
this.lense = appView.graphLense();
+ this.options = options;
this.builder = MemberRebindingLense.builder(appInfo);
}
@@ -114,8 +119,8 @@
return appInfo.resolveMethod(method.getHolder(), method).asResultOfResolve();
}
- private void computeMethodRebinding(Set<DexMethod> methods,
- Function<DexMethod, DexEncodedMethod> lookupTarget) {
+ private void computeMethodRebinding(
+ Set<DexMethod> methods, Function<DexMethod, DexEncodedMethod> lookupTarget, Type invokeType) {
for (DexMethod method : methods) {
// We can safely ignore array types, as the corresponding methods are defined in a library.
if (!method.getHolder().isClassType()) {
@@ -130,33 +135,107 @@
// Rebind to the lowest library class or program class.
if (target != null && target.method != method) {
DexClass targetClass = appInfo.definitionFor(target.method.holder);
+
+ // In Java bytecode, it is only possible to target interface methods that are in one of
+ // the immediate super-interfaces via a super-invocation (see IndirectSuperInterfaceTest).
+ // To avoid introducing an IncompatibleClassChangeError at runtime we therefore insert a
+ // bridge method when we are about to rebind to an interface method that is not the
+ // original target.
+ if (needsBridgeForInterfaceMethod(originalClass, targetClass, invokeType)) {
+ target =
+ insertBridgeForInterfaceMethod(
+ method, target, originalClass.asProgramClass(), targetClass, lookupTarget);
+ }
+
// If the target class is not public but the targeted method is, we might run into
// visibility problems when rebinding.
- if (!targetClass.accessFlags.isPublic() && target.accessFlags.isPublic()) {
- // If the original class is public and this method is public, it might have been called
- // from anywhere, so we need a bridge. Likewise, if the original is in a different
- // package, we might need a bridge, too.
- String packageDescriptor =
- originalClass.accessFlags.isPublic() ? null : method.holder.getPackageDescriptor();
- if (packageDescriptor == null
- || !packageDescriptor.equals(targetClass.type.getPackageDescriptor())) {
- DexProgramClass bridgeHolder = findBridgeMethodHolder(originalClass, targetClass,
- packageDescriptor);
- assert bridgeHolder != null;
- DexEncodedMethod bridgeMethod =
- target.toForwardingMethod(bridgeHolder, appInfo.dexItemFactory);
- bridgeHolder.addMethod(bridgeMethod);
- assert lookupTarget.apply(method) == bridgeMethod;
- target = bridgeMethod;
- }
+ if (mayNeedBridgeForVisibility(target, targetClass)) {
+ target =
+ insertBridgeForVisibilityIfNeeded(
+ method, target, originalClass, targetClass, lookupTarget);
}
+
builder.map(method, lense.lookupMethod(validTargetFor(target.method, method)));
}
}
}
- private DexProgramClass findBridgeMethodHolder(DexClass originalClass, DexClass targetClass,
- String packageDescriptor) {
+ private boolean needsBridgeForInterfaceMethod(
+ DexClass originalClass, DexClass targetClass, Type invokeType) {
+ return options.isGeneratingClassFiles()
+ && invokeType == Type.SUPER
+ && targetClass != originalClass
+ && targetClass.accessFlags.isInterface();
+ }
+
+ private DexEncodedMethod insertBridgeForInterfaceMethod(
+ DexMethod method,
+ DexEncodedMethod target,
+ DexProgramClass originalClass,
+ DexClass targetClass,
+ Function<DexMethod, DexEncodedMethod> lookupTarget) {
+ // If `targetClass` is a class, then insert the bridge method on the upper-most super class that
+ // implements the interface. Otherwise, if it is an interface, then insert the bridge method
+ // directly on the interface (because that interface must be the immediate super type, assuming
+ // that the super-invocation is not broken in advance).
+ //
+ // Note that, to support compiling from DEX to CF, we would need to rewrite the targets of
+ // invoke-super instructions that hit indirect interface methods such that they always target
+ // a method in an immediate super-interface, since this works on Art but not on the JVM.
+ DexProgramClass bridgeHolder =
+ findHolderForInterfaceMethodBridge(originalClass, targetClass.type);
+ assert bridgeHolder != null;
+ assert bridgeHolder != targetClass;
+ DexEncodedMethod bridgeMethod = target.toForwardingMethod(bridgeHolder, appInfo.dexItemFactory);
+ bridgeHolder.addMethod(bridgeMethod);
+ assert lookupTarget.apply(method) == bridgeMethod;
+ return bridgeMethod;
+ }
+
+ private DexProgramClass findHolderForInterfaceMethodBridge(DexProgramClass clazz, DexType iface) {
+ if (clazz.accessFlags.isInterface()) {
+ return clazz;
+ }
+ DexClass superClass = appInfo.definitionFor(clazz.superType);
+ if (superClass == null
+ || superClass.isLibraryClass()
+ || !superClass.type.isSubtypeOf(iface, appInfo)) {
+ return clazz;
+ }
+ return findHolderForInterfaceMethodBridge(superClass.asProgramClass(), iface);
+ }
+
+ private boolean mayNeedBridgeForVisibility(DexEncodedMethod target, DexClass targetClass) {
+ return !targetClass.accessFlags.isPublic() && target.accessFlags.isPublic();
+ }
+
+ private DexEncodedMethod insertBridgeForVisibilityIfNeeded(
+ DexMethod method,
+ DexEncodedMethod target,
+ DexClass originalClass,
+ DexClass targetClass,
+ Function<DexMethod, DexEncodedMethod> lookupTarget) {
+ // If the original class is public and this method is public, it might have been called
+ // from anywhere, so we need a bridge. Likewise, if the original is in a different
+ // package, we might need a bridge, too.
+ String packageDescriptor =
+ originalClass.accessFlags.isPublic() ? null : method.holder.getPackageDescriptor();
+ if (packageDescriptor == null
+ || !packageDescriptor.equals(targetClass.type.getPackageDescriptor())) {
+ DexProgramClass bridgeHolder =
+ findHolderForVisibilityBridge(originalClass, targetClass, packageDescriptor);
+ assert bridgeHolder != null;
+ DexEncodedMethod bridgeMethod =
+ target.toForwardingMethod(bridgeHolder, appInfo.dexItemFactory);
+ bridgeHolder.addMethod(bridgeMethod);
+ assert lookupTarget.apply(method) == bridgeMethod;
+ return bridgeMethod;
+ }
+ return target;
+ }
+
+ private DexProgramClass findHolderForVisibilityBridge(
+ DexClass originalClass, DexClass targetClass, String packageDescriptor) {
if (originalClass == targetClass || originalClass.isLibraryClass()) {
return null;
}
@@ -164,12 +243,12 @@
// Recurse through supertype chain.
if (originalClass.superType.isSubtypeOf(targetClass.type, appInfo)) {
DexClass superClass = appInfo.definitionFor(originalClass.superType);
- newHolder = findBridgeMethodHolder(superClass, targetClass, packageDescriptor);
+ newHolder = findHolderForVisibilityBridge(superClass, targetClass, packageDescriptor);
} else {
for (DexType iface : originalClass.interfaces.values) {
if (iface.isSubtypeOf(targetClass.type, appInfo)) {
DexClass interfaceClass = appInfo.definitionFor(iface);
- newHolder = findBridgeMethodHolder(interfaceClass, targetClass, packageDescriptor);
+ newHolder = findHolderForVisibilityBridge(interfaceClass, targetClass, packageDescriptor);
}
}
}
@@ -234,15 +313,15 @@
public GraphLense run() {
// Virtual invokes are on classes, so use class resolution.
- computeMethodRebinding(appInfo.virtualInvokes, this::classLookup);
+ computeMethodRebinding(appInfo.virtualInvokes, this::classLookup, Type.VIRTUAL);
// Interface invokes are always on interfaces, so use interface resolution.
- computeMethodRebinding(appInfo.interfaceInvokes, this::interfaceLookup);
+ computeMethodRebinding(appInfo.interfaceInvokes, this::interfaceLookup, Type.INTERFACE);
// Super invokes can be on both kinds, decide using the holder class.
- computeMethodRebinding(appInfo.superInvokes, this::anyLookup);
+ computeMethodRebinding(appInfo.superInvokes, this::anyLookup, Type.SUPER);
// Direct invokes (private/constructor) can also be on both kinds.
- computeMethodRebinding(appInfo.directInvokes, this::anyLookup);
+ computeMethodRebinding(appInfo.directInvokes, this::anyLookup, Type.DIRECT);
// Likewise static invokes.
- computeMethodRebinding(appInfo.staticInvokes, this::anyLookup);
+ computeMethodRebinding(appInfo.staticInvokes, this::anyLookup, Type.STATIC);
computeFieldRebinding(
mergeFieldAccessContexts(appInfo.staticFieldReads, appInfo.staticFieldWrites),
diff --git a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
index 0c47df1..d194bb2 100644
--- a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
@@ -11,8 +11,10 @@
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.InnerClassAttribute;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
+import java.util.List;
public class AnnotationRemover {
@@ -135,11 +137,36 @@
field.annotations = field.annotations.keepIf(this::filterAnnotations);
}
+ private boolean enclosingMethodPinned(DexClass clazz) {
+ return clazz.getEnclosingMethod() != null
+ && clazz.getEnclosingMethod().getEnclosingClass() != null
+ && appInfo.isPinned(clazz.getEnclosingMethod().getEnclosingClass());
+ }
+
+ private boolean innerClassPinned(DexClass clazz) {
+ List<InnerClassAttribute> innerClasses = clazz.getInnerClasses();
+ for (InnerClassAttribute innerClass : innerClasses) {
+ if (appInfo.isPinned(innerClass.getInner())) {
+ return true;
+ }
+ if (appInfo.isPinned(innerClass.getOuter())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private void stripAttributes(DexProgramClass clazz) {
// If [clazz] is mentioned by a keep rule, it could be used for reflection, and we therefore
// need to keep the enclosing method and inner classes attributes, if requested. In Proguard
// compatibility mode we keep these attributes independent of whether the given class is kept.
- if (appInfo.isPinned(clazz.type) || options.forceProguardCompatibility) {
+ // To ensure reflection from both inner to outer and and outer to inner for kept classes - even
+ // if only one side is kept - keep the attributes is any class mentioned in these attributes
+ // is kept.
+ if (appInfo.isPinned(clazz.type)
+ || enclosingMethodPinned(clazz)
+ || innerClassPinned(clazz)
+ || options.forceProguardCompatibility) {
if (!keep.enclosingMethod) {
clazz.clearEnclosingMethod();
}
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 de42c18..e0cf415 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -68,6 +68,7 @@
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
@@ -253,7 +254,6 @@
private void enqueueRootItems(Map<DexDefinition, ProguardKeepRule> items) {
items.entrySet().forEach(this::enqueueRootItem);
- pinnedItems.addAll(items.keySet());
}
private void enqueueRootItem(Entry<DexDefinition, ProguardKeepRule> root) {
@@ -261,7 +261,10 @@
}
private void enqueueRootItem(DexDefinition item, ProguardKeepRule rule) {
- KeepReason reason = KeepReason.dueToKeepRule(rule);
+ enqueueRootItem(item, KeepReason.dueToKeepRule(rule));
+ }
+
+ private void enqueueRootItem(DexDefinition item, KeepReason reason) {
if (item.isDexClass()) {
DexClass clazz = item.asDexClass();
workList.add(Action.markInstantiated(clazz, reason));
@@ -285,6 +288,19 @@
} else {
throw new IllegalArgumentException(item.toString());
}
+ pinnedItems.add(item);
+ }
+
+ private void enqueueFirstNonSerializableClassInitializer(DexClass clazz, KeepReason reason) {
+ assert clazz.isProgramClass() && clazz.isSerializable(appInfo);
+ // Clime up the class hierarchy. Break out if the definition is not found, or hit the library
+ // classes, which are kept by definition, or encounter the first non-serializable class.
+ while (clazz != null && clazz.isProgramClass() && clazz.isSerializable(appInfo)) {
+ clazz = appInfo.definitionFor(clazz.superType);
+ }
+ if (clazz != null && clazz.isProgramClass() && clazz.hasDefaultInitializer()) {
+ workList.add(Action.markMethodLive(clazz.getDefaultInitializer(), reason));
+ }
}
private void enqueueHolderIfDependentNonStaticMember(
@@ -391,6 +407,10 @@
// Revisit the current method to implicitly add -keep rule for items with reflective access.
pendingReflectiveUses.add(currentMethod);
}
+ // See comment in handleJavaLangEnumValueOf.
+ if (method == appInfo.dexItemFactory.enumMethods.valueOf) {
+ pendingReflectiveUses.add(currentMethod);
+ }
if (!registerItemWithTarget(staticInvokes, method)) {
return false;
}
@@ -689,14 +709,19 @@
}
// We also need to add the corresponding <clinit> to the set of live methods, as otherwise
// static field initialization (and other class-load-time sideeffects) will not happen.
+ KeepReason reason = KeepReason.reachableFromLiveType(type);
if (!holder.isLibraryClass() && holder.hasNonTrivialClassInitializer()) {
DexEncodedMethod clinit = holder.getClassInitializer();
if (clinit != null) {
assert clinit.method.holder == holder.type;
- markDirectStaticOrConstructorMethodAsLive(clinit, KeepReason.reachableFromLiveType(type));
+ markDirectStaticOrConstructorMethodAsLive(clinit, reason);
}
}
+ if (holder.isProgramClass() && holder.isSerializable(appInfo)) {
+ enqueueFirstNonSerializableClassInitializer(holder, reason);
+ }
+
// If this type has deferred annotations, we have to process those now, too.
Set<DexAnnotation> annotations = deferredAnnotations.remove(type);
if (annotations != null) {
@@ -1150,6 +1175,25 @@
}
}
+ private DexMethod generatedEnumValuesMethod(DexClass enumClass) {
+ DexType arrayOfEnumClass =
+ appInfo.dexItemFactory.createType(
+ appInfo.dexItemFactory.createString("[" + enumClass.type.toDescriptorString()));
+ DexProto proto = appInfo.dexItemFactory.createProto(arrayOfEnumClass);
+ return appInfo.dexItemFactory.createMethod(
+ enumClass.type, proto, appInfo.dexItemFactory.createString("values"));
+ }
+
+ private void markEnumValuesAsReachable(DexClass clazz, KeepReason reason) {
+ DexEncodedMethod valuesMethod = clazz.lookupMethod(generatedEnumValuesMethod(clazz));
+ if (valuesMethod != null) {
+ // TODO(sgjesse): Does this have to be enqueued as a root item? Right now it is done as the
+ // marking of not renaming is in the root set.
+ enqueueRootItem(valuesMethod, reason);
+ rootSet.noObfuscation.add(valuesMethod);
+ }
+ }
+
private static void fillWorkList(Deque<DexType> worklist, DexType type) {
if (type.isInterface()) {
// We need to check if the method is shadowed by a class that directly implements
@@ -1534,15 +1578,23 @@
DexType originHolder = method.method.holder;
Origin origin = appInfo.originFor(originHolder);
IRCode code = method.buildIR(appInfo, appView.graphLense(), options, origin);
- code.instructionIterator().forEachRemaining(this::handleReflectiveBehavior);
+ Iterator<Instruction> iterator = code.instructionIterator();
+ while (iterator.hasNext()) {
+ Instruction instruction = iterator.next();
+ handleReflectiveBehavior(method, instruction);
+ }
}
- private void handleReflectiveBehavior(Instruction instruction) {
+ private void handleReflectiveBehavior(DexEncodedMethod method, Instruction instruction) {
if (!instruction.isInvokeMethod()) {
return;
}
InvokeMethod invoke = instruction.asInvokeMethod();
DexMethod invokedMethod = invoke.getInvokedMethod();
+ if (invokedMethod == appInfo.dexItemFactory.enumMethods.valueOf) {
+ handleJavaLangEnumValueOf(method, invoke);
+ return;
+ }
if (!isReflectionMethod(appInfo.dexItemFactory, invokedMethod)) {
return;
}
@@ -1577,6 +1629,20 @@
}
}
+ private void handleJavaLangEnumValueOf(DexEncodedMethod method, InvokeMethod invoke) {
+ // The use of java.lang.Enum.valueOf(java.lang.Class, java.lang.String) will indirectly
+ // access the values() method of the enum class passed as the first argument. The method
+ // SomeEnumClass.valueOf(java.lang.String) which is generated by javac for all enums will
+ // call this method.
+ if (invoke.inValues().get(0).isConstClass()) {
+ DexClass clazz =
+ appInfo.definitionFor(invoke.inValues().get(0).definition.asConstClass().getValue());
+ if (clazz.accessFlags.isEnum() && clazz.superType == appInfo.dexItemFactory.enumType) {
+ markEnumValuesAsReachable(clazz, KeepReason.invokedFrom(method));
+ }
+ }
+ }
+
private static class Action {
final Kind kind;
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardClassSpecification.java b/src/main/java/com/android/tools/r8/shaking/ProguardClassSpecification.java
index 2aff5df..1d970a3 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardClassSpecification.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardClassSpecification.java
@@ -230,6 +230,10 @@
return inheritanceIsExtends;
}
+ public boolean getInheritanceIsImplements() {
+ return !inheritanceIsExtends;
+ }
+
public boolean hasInheritanceClassName() {
return inheritanceClassName != null;
}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
index 39737ea..5424cc2 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
@@ -4,8 +4,12 @@
package com.android.tools.r8.shaking;
import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.shaking.ProguardConfigurationParser.IdentifierPatternWithWildcards;
import com.android.tools.r8.utils.StringUtils;
@@ -160,11 +164,14 @@
return type;
}
- public boolean matches(DexEncodedField field, DexStringCache stringCache) {
+ public boolean matches(
+ DexEncodedField field, AppView<? extends AppInfo> appView, DexStringCache stringCache) {
+ DexField originalSignature = appView.graphLense().getOriginalFieldSignature(field.field);
switch (getRuleType()) {
case ALL:
case ALL_FIELDS:
// Access flags check.
+ // TODO(b/117330692): The access flags may have changed as a result of access relaxation.
if (!getAccessFlags().containsAll(field.accessFlags)
|| !getNegatedAccessFlags().containsNone(field.accessFlags)) {
break;
@@ -173,17 +180,18 @@
return RootSetBuilder.containsAnnotation(annotation, field.annotations);
case FIELD:
// Name check.
- String name = stringCache.lookupString(field.field.name);
+ String name = stringCache.lookupString(originalSignature.name);
if (!getName().matches(name)) {
break;
}
// Access flags check.
+ // TODO(b/117330692): The access flags may have changed as a result of access relaxation.
if (!getAccessFlags().containsAll(field.accessFlags)
|| !getNegatedAccessFlags().containsNone(field.accessFlags)) {
break;
}
// Type check.
- if (!this.type.matches(field.field.type)) {
+ if (!getType().matches(originalSignature.type, appView)) {
break;
}
// Annotations check
@@ -200,7 +208,9 @@
return false;
}
- public boolean matches(DexEncodedMethod method, DexStringCache stringCache) {
+ public boolean matches(
+ DexEncodedMethod method, AppView<? extends AppInfo> appView, DexStringCache stringCache) {
+ DexMethod originalSignature = appView.graphLense().getOriginalMethodSignature(method.method);
switch (getRuleType()) {
case ALL_METHODS:
if (method.isClassInitializer()) {
@@ -209,6 +219,7 @@
// Fall through for all other methods.
case ALL:
// Access flags check.
+ // TODO(b/117330692): The access flags may have changed as a result of access relaxation.
if (!getAccessFlags().containsAll(method.accessFlags)
|| !getNegatedAccessFlags().containsNone(method.accessFlags)) {
break;
@@ -217,18 +228,19 @@
return RootSetBuilder.containsAnnotation(annotation, method.annotations);
case METHOD:
// Check return type.
- if (!type.matches(method.method.proto.returnType)) {
+ if (!type.matches(originalSignature.proto.returnType, appView)) {
break;
}
// Fall through for access flags, name and arguments.
case CONSTRUCTOR:
case INIT:
// Name check.
- String name = stringCache.lookupString(method.method.name);
+ String name = stringCache.lookupString(originalSignature.name);
if (!getName().matches(name)) {
break;
}
// Access flags check.
+ // TODO(b/117330692): The access flags may have changed as a result of access relaxation.
if (!getAccessFlags().containsAll(method.accessFlags)
|| !getNegatedAccessFlags().containsNone(method.accessFlags)) {
break;
@@ -241,23 +253,18 @@
List<ProguardTypeMatcher> arguments = getArguments();
if (arguments.size() == 1 && arguments.get(0).isTripleDotPattern()) {
return true;
- } else {
- DexType[] parameters = method.method.proto.parameters.values;
- if (parameters.length != arguments.size()) {
- break;
- }
- int i = 0;
- for (; i < parameters.length; i++) {
- if (!arguments.get(i).matches(parameters[i])) {
- break;
- }
- }
- if (i == parameters.length) {
- // All parameters matched.
- return true;
+ }
+ DexType[] parameters = originalSignature.proto.parameters.values;
+ if (parameters.length != arguments.size()) {
+ break;
+ }
+ for (int i = 0; i < parameters.length; i++) {
+ if (!arguments.get(i).matches(parameters[i], appView)) {
+ return false;
}
}
- break;
+ // All parameters matched.
+ return true;
case ALL_FIELDS:
case FIELD:
break;
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardTypeMatcher.java b/src/main/java/com/android/tools/r8/shaking/ProguardTypeMatcher.java
index 34ee554..747dea9 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardTypeMatcher.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardTypeMatcher.java
@@ -5,6 +5,8 @@
import static com.android.tools.r8.utils.DescriptorUtils.javaTypeToDescriptor;
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.shaking.ProguardConfigurationParser.IdentifierPatternWithWildcards;
@@ -31,8 +33,21 @@
TYPE
}
+ // Evaluates this matcher on the given type.
public abstract boolean matches(DexType type);
+ // Evaluates this matcher on the given type, and on all types that have been merged into the given
+ // type, if any.
+ public final boolean matches(DexType type, AppView<? extends AppInfo> appView) {
+ if (matches(type)) {
+ return true;
+ }
+ if (appView.verticallyMergedClasses() != null) {
+ return appView.verticallyMergedClasses().getSourcesFor(type).stream().anyMatch(this::matches);
+ }
+ return false;
+ }
+
protected Iterable<ProguardWildcard> getWildcards() {
return Collections::emptyIterator;
}
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 e6f6a17..432ae10 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -29,6 +29,7 @@
import com.android.tools.r8.utils.ThreadUtils;
import com.google.common.base.Equivalence.Wrapper;
import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import java.io.PrintStream;
import java.util.ArrayList;
@@ -46,7 +47,6 @@
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
-import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@@ -79,7 +79,7 @@
public RootSetBuilder(
AppView<? extends AppInfo> appView,
DexApplication application,
- List<ProguardConfigurationRule> rules,
+ Collection<? extends ProguardConfigurationRule> rules,
InternalOptions options) {
this.appView = appView;
this.application = application.asDirect();
@@ -88,61 +88,10 @@
}
RootSetBuilder(
- AppView<? extends AppInfo> appView, Set<ProguardIfRule> ifRules, InternalOptions options) {
- this.appView = appView;
- this.application = appView.appInfo().app.asDirect();
- this.rules = Collections.unmodifiableCollection(ifRules);
- this.options = options;
- }
-
- private boolean anySuperTypeMatches(
- DexType type,
- Function<DexType, DexClass> definitionFor,
- ProguardTypeMatcher name,
- ProguardTypeMatcher annotation) {
- while (type != null) {
- DexClass clazz = definitionFor.apply(type);
- if (clazz == null) {
- // TODO(herhut): Warn about broken supertype chain?
- return false;
- }
- if (name.matches(clazz.type) && containsAnnotation(annotation, clazz.annotations)) {
- return true;
- }
- type = clazz.superType;
- }
- return false;
- }
-
- private boolean anyImplementedInterfaceMatches(
- DexClass clazz,
- Function<DexType, DexClass> definitionFor,
- ProguardTypeMatcher className,
- ProguardTypeMatcher annotation) {
- if (clazz == null) {
- return false;
- }
- for (DexType iface : clazz.interfaces.values) {
- DexClass ifaceClass = definitionFor.apply(iface);
- if (ifaceClass == null) {
- // TODO(herhut): Warn about broken supertype chain?
- return false;
- }
- // TODO(herhut): Maybe it would be better to do this breadth first.
- if ((className.matches(iface) && containsAnnotation(annotation, ifaceClass.annotations))
- || anyImplementedInterfaceMatches(ifaceClass, definitionFor, className, annotation)) {
- return true;
- }
- }
- if (clazz.superType == null) {
- return false;
- }
- DexClass superClass = definitionFor.apply(clazz.superType);
- if (superClass == null) {
- // TODO(herhut): Warn about broken supertype chain?
- return false;
- }
- return anyImplementedInterfaceMatches(superClass, definitionFor, className, annotation);
+ AppView<? extends AppInfo> appView,
+ Collection<ProguardIfRule> ifRules,
+ InternalOptions options) {
+ this(appView, appView.appInfo().app, ifRules, options);
}
// Process a class with the keep rule.
@@ -164,39 +113,8 @@
// seems not to care, so users have started to use this inconsistently. We are thus
// inconsistent, as well, but tell them.
// TODO(herhut): One day make this do what it says.
- if (rule.hasInheritanceClassName()) {
- boolean extendsExpected =
- anySuperTypeMatches(
- clazz.superType,
- application::definitionFor,
- rule.getInheritanceClassName(),
- rule.getInheritanceAnnotation());
- boolean implementsExpected = false;
- if (!extendsExpected) {
- implementsExpected =
- anyImplementedInterfaceMatches(
- clazz,
- application::definitionFor,
- rule.getInheritanceClassName(),
- rule.getInheritanceAnnotation());
- }
- if (!extendsExpected && !implementsExpected) {
- return;
- }
- // Warn if users got it wrong, but only warn once.
- if (extendsExpected && !rule.getInheritanceIsExtends()) {
- if (rulesThatUseExtendsOrImplementsWrong.add(rule)) {
- options.reporter.warning(
- new StringDiagnostic(
- "The rule `" + rule + "` uses implements but actually matches extends."));
- }
- } else if (implementsExpected && rule.getInheritanceIsExtends()) {
- if (rulesThatUseExtendsOrImplementsWrong.add(rule)) {
- options.reporter.warning(
- new StringDiagnostic(
- "The rule `" + rule + "` uses extends but actually matches implements."));
- }
- }
+ if (rule.hasInheritanceClassName() && !satisfyInheritanceRule(clazz, rule)) {
+ return;
}
if (rule.getClassNames().matches(clazz.type)) {
@@ -206,7 +124,7 @@
switch (((ProguardKeepRule) rule).getType()) {
case KEEP_CLASS_MEMBERS: {
// Members mentioned at -keepclassmembers always depend on their holder.
- preconditionSupplier = ImmutableMap.of((definition -> true), clazz);
+ preconditionSupplier = ImmutableMap.of(definition -> true, clazz);
markMatchingVisibleMethods(clazz, memberKeepRules, rule, preconditionSupplier);
markMatchingFields(clazz, memberKeepRules, rule, preconditionSupplier);
break;
@@ -340,95 +258,39 @@
Set<DexEncodedMethod> liveMethods,
Set<DexEncodedField> liveFields) throws ExecutionException {
application.timing.begin("Find consequent items for -if rules...");
- Function<DexType, DexClass> definitionForWithLiveTypes =
- type -> {
- DexClass clazz = appView.appInfo().definitionFor(type);
- if (clazz != null && liveTypes.contains(clazz.type)) {
- return clazz;
- }
- return null;
- };
try {
- List<Future<?>> futures = new ArrayList<>();
if (rules != null) {
+ IfRuleEvaluator evaluator =
+ new IfRuleEvaluator(liveTypes, liveMethods, liveFields, executorService);
for (ProguardConfigurationRule rule : rules) {
assert rule instanceof ProguardIfRule;
ProguardIfRule ifRule = (ProguardIfRule) rule;
- // Depending on which types trigger the -if rule, the application of the subsequent
+ // Depending on which types that trigger the -if rule, the application of the subsequent
// -keep rule may vary (due to back references). So, we need to try all pairs of -if rule
// and live types.
- for (DexType currentLiveType : liveTypes) {
- DexClass currentLiveClass = appView.appInfo().definitionFor(currentLiveType);
- if (currentLiveClass == null) {
+ for (DexType type : liveTypes) {
+ DexClass clazz = appView.appInfo().definitionFor(type);
+ if (clazz == null) {
continue;
}
- if (!satisfyClassType(rule, currentLiveClass)) {
- continue;
- }
- if (!satisfyAccessFlag(rule, currentLiveClass)) {
- continue;
- }
- if (!satisfyAnnotation(rule, currentLiveClass)) {
- continue;
- }
- if (ifRule.hasInheritanceClassName()) {
- if (!satisfyInheritanceRule(currentLiveType, definitionForWithLiveTypes, ifRule)) {
- // Try another live type since the current one doesn't satisfy the inheritance rule.
- continue;
- }
- }
- if (ifRule.getClassNames().matches(currentLiveType)) {
- Collection<ProguardMemberRule> memberKeepRules = ifRule.getMemberRules();
- if (memberKeepRules.isEmpty()) {
- ProguardIfRule materializedRule = ifRule.materialize();
- runPerRule(
- executorService, futures, materializedRule.subsequentRule, materializedRule);
- // No member rule to satisfy. Move on to the next live type.
- continue;
- }
- Set<DexDefinition> filteredFields = liveFields.stream()
- .filter(f -> f.field.getHolder() == currentLiveType)
- .collect(Collectors.toSet());
- Set<DexDefinition> filteredMethods = liveMethods.stream()
- .filter(m -> m.method.getHolder() == currentLiveType)
- .collect(Collectors.toSet());
- // If the number of member rules to hold is more than live members, we can't make it.
- if (filteredFields.size() + filteredMethods.size() < memberKeepRules.size()) {
- continue;
- }
- // Depending on which members trigger the -if rule, the application of the subsequent
- // -keep rule may vary (due to back references). So, we need to try literally all
- // combinations of live members.
- // TODO(b/79486261): Some of those are equivalent from the point of view of -if rule.
- Set<Set<DexDefinition>> combinationsOfMembers = Sets.combinations(
- Sets.union(filteredFields, filteredMethods), memberKeepRules.size());
- for (Set<DexDefinition> combination : combinationsOfMembers) {
- Set<DexEncodedField> fieldsInCombination =
- DexDefinition.filterDexEncodedField(combination.stream())
- .collect(Collectors.toSet());
- Set<DexEncodedMethod> methodsInCombination =
- DexDefinition.filterDexEncodedMethod(combination.stream())
- .collect(Collectors.toSet());
- // Member rules are combined as AND logic: if found unsatisfied member rule, this
- // combination of live members is not a good fit.
- boolean satisfied = true;
- for (ProguardMemberRule memberRule : memberKeepRules) {
- if (!ruleSatisfiedByFields(memberRule, fieldsInCombination)
- && !ruleSatisfiedByMethods(memberRule, methodsInCombination)) {
- satisfied = false;
- break;
- }
- }
- if (satisfied) {
- ProguardIfRule materializedRule = ifRule.materialize();
- runPerRule(
- executorService, futures, materializedRule.subsequentRule, materializedRule);
- }
+
+ // Check if the class matches the if-rule.
+ evaluator.evaluateIfRule(ifRule, clazz, clazz);
+
+ // Check if one of the types that have been merged into `clazz` satisfies the if-rule.
+ if (options.enableVerticalClassMerging && appView.verticallyMergedClasses() != null) {
+ for (DexType sourceType : appView.verticallyMergedClasses().getSourcesFor(type)) {
+ // Note that, although `sourceType` has been merged into `type`, the dex class for
+ // `sourceType` is still available until the second round of tree shaking. This way
+ // we can still retrieve the access flags of `sourceType`.
+ DexClass sourceClass = appView.appInfo().definitionFor(sourceType);
+ assert sourceClass != null;
+ evaluator.evaluateIfRule(ifRule, sourceClass, clazz);
}
}
}
}
- ThreadUtils.awaitFutures(futures);
+ ThreadUtils.awaitFutures(evaluator.futures);
}
} finally {
application.timing.end();
@@ -436,6 +298,112 @@
return new ConsequentRootSet(noShrinking, noOptimization, noObfuscation, dependentNoShrinking);
}
+ private class IfRuleEvaluator {
+
+ private final Set<DexType> liveTypes;
+ private final Set<DexEncodedMethod> liveMethods;
+ private final Set<DexEncodedField> liveFields;
+ private final ExecutorService executorService;
+
+ private final List<Future<?>> futures = new ArrayList<>();
+
+ public IfRuleEvaluator(
+ Set<DexType> liveTypes,
+ Set<DexEncodedMethod> liveMethods,
+ Set<DexEncodedField> liveFields,
+ ExecutorService executorService) {
+ this.liveTypes = liveTypes;
+ this.liveMethods = liveMethods;
+ this.liveFields = liveFields;
+ this.executorService = executorService;
+ }
+
+ /**
+ * Determines if `sourceClass` satisfies the given if-rule. If `sourceClass` has not been merged
+ * into another class, then `targetClass` is the same as `sourceClass`. Otherwise, `targetClass`
+ * denotes the class that `sourceClass` has been merged into.
+ */
+ private void evaluateIfRule(ProguardIfRule rule, DexClass sourceClass, DexClass targetClass) {
+ if (!satisfyClassType(rule, sourceClass)) {
+ return;
+ }
+ if (!satisfyAccessFlag(rule, sourceClass)) {
+ return;
+ }
+ if (!satisfyAnnotation(rule, sourceClass)) {
+ return;
+ }
+ if (!rule.getClassNames().matches(sourceClass.type)) {
+ return;
+ }
+ if (rule.hasInheritanceClassName()) {
+ // Note that, in presence of vertical class merging, we check if the resulting class
+ // (i.e., the target class) satisfies the implements/extends-matcher.
+ if (!satisfyInheritanceRule(targetClass, rule)) {
+ // Try another live type since the current one doesn't satisfy the inheritance rule.
+ return;
+ }
+ }
+ Collection<ProguardMemberRule> memberKeepRules = rule.getMemberRules();
+ if (memberKeepRules.isEmpty()) {
+ materializeIfRule(rule);
+ return;
+ }
+
+ Set<DexDefinition> filteredMembers = Sets.newIdentityHashSet();
+ Iterables.addAll(
+ filteredMembers,
+ targetClass.fields(
+ f ->
+ liveFields.contains(f)
+ && appView.graphLense().getOriginalFieldSignature(f.field).getHolder()
+ == sourceClass.type));
+ Iterables.addAll(
+ filteredMembers,
+ targetClass.methods(
+ m ->
+ liveMethods.contains(m)
+ && appView.graphLense().getOriginalMethodSignature(m.method).getHolder()
+ == sourceClass.type));
+
+ // If the number of member rules to hold is more than live members, we can't make it.
+ if (filteredMembers.size() < memberKeepRules.size()) {
+ return;
+ }
+
+ // Depending on which members trigger the -if rule, the application of the subsequent
+ // -keep rule may vary (due to back references). So, we need to try literally all
+ // combinations of live members.
+ // TODO(b/79486261): Some of those are equivalent from the point of view of -if rule.
+ Sets.combinations(filteredMembers, memberKeepRules.size())
+ .forEach(
+ combination -> {
+ Collection<DexEncodedField> fieldsInCombination =
+ DexDefinition.filterDexEncodedField(combination.stream())
+ .collect(Collectors.toList());
+ Collection<DexEncodedMethod> methodsInCombination =
+ DexDefinition.filterDexEncodedMethod(combination.stream())
+ .collect(Collectors.toList());
+ // Member rules are combined as AND logic: if found unsatisfied member rule, this
+ // combination of live members is not a good fit.
+ boolean satisfied =
+ memberKeepRules.stream()
+ .allMatch(
+ memberRule ->
+ ruleSatisfiedByFields(memberRule, fieldsInCombination)
+ || ruleSatisfiedByMethods(memberRule, methodsInCombination));
+ if (satisfied) {
+ materializeIfRule(rule);
+ }
+ });
+ }
+
+ private void materializeIfRule(ProguardIfRule rule) {
+ ProguardIfRule materializedRule = rule.materialize();
+ runPerRule(executorService, futures, materializedRule.subsequentRule, materializedRule);
+ }
+ }
+
private static DexDefinition testAndGetPrecondition(
DexDefinition definition, Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier) {
if (preconditionSupplier == null) {
@@ -497,7 +465,7 @@
});
}
- // TODO(67934426): Test this code.
+ // TODO(b/67934426): Test this code.
public static void writeSeeds(
AppInfoWithLiveness appInfo, PrintStream out, Predicate<DexType> include) {
for (DexReference seed : appInfo.getPinnedItems()) {
@@ -563,25 +531,114 @@
return containsAnnotation(rule.getClassAnnotation(), clazz.annotations);
}
- private boolean satisfyInheritanceRule(
- DexType type,
- Function<DexType, DexClass> definitionFor,
- ProguardConfigurationRule rule) {
- DexClass clazz = definitionFor.apply(type);
+ private boolean satisfyInheritanceRule(DexClass clazz, ProguardConfigurationRule rule) {
+ boolean extendsExpected = satisfyExtendsRule(clazz, rule);
+ boolean implementsExpected = false;
+ if (!extendsExpected) {
+ implementsExpected = satisfyImplementsRule(clazz, rule);
+ }
+ if (extendsExpected || implementsExpected) {
+ // Warn if users got it wrong, but only warn once.
+ if (rule.getInheritanceIsExtends()) {
+ if (implementsExpected && rulesThatUseExtendsOrImplementsWrong.add(rule)) {
+ assert options.testing.allowProguardRulesThatUseExtendsOrImplementsWrong;
+ options.reporter.warning(
+ new StringDiagnostic(
+ "The rule `" + rule + "` uses extends but actually matches implements."));
+ }
+ } else if (extendsExpected && rulesThatUseExtendsOrImplementsWrong.add(rule)) {
+ assert options.testing.allowProguardRulesThatUseExtendsOrImplementsWrong;
+ options.reporter.warning(
+ new StringDiagnostic(
+ "The rule `" + rule + "` uses implements but actually matches extends."));
+ }
+ return true;
+ }
+ return false;
+ }
+
+ private boolean satisfyExtendsRule(DexClass clazz, ProguardConfigurationRule rule) {
+ if (anySuperTypeMatchesExtendsRule(clazz.superType, rule)) {
+ return true;
+ }
+ // It is possible that this class used to inherit from another class X, but no longer does it,
+ // because X has been merged into `clazz`.
+ return anySourceMatchesInheritanceRuleDirectly(clazz, rule, false);
+ }
+
+ private boolean anySuperTypeMatchesExtendsRule(DexType type, ProguardConfigurationRule rule) {
+ while (type != null) {
+ DexClass clazz = application.definitionFor(type);
+ if (clazz == null) {
+ // TODO(herhut): Warn about broken supertype chain?
+ return false;
+ }
+ // TODO(b/110141157): Should the vertical class merger move annotations from the source to
+ // the target class? If so, it is sufficient only to apply the annotation-matcher to the
+ // annotations of `class`.
+ if (rule.getInheritanceClassName().matches(clazz.type, appView)
+ && containsAnnotation(rule.getInheritanceAnnotation(), clazz.annotations)) {
+ return true;
+ }
+ type = clazz.superType;
+ }
+ return false;
+ }
+
+ private boolean satisfyImplementsRule(DexClass clazz, ProguardConfigurationRule rule) {
+ if (anyImplementedInterfaceMatchesImplementsRule(clazz, rule)) {
+ return true;
+ }
+ // It is possible that this class used to implement an interface I, but no longer does it,
+ // because I has been merged into `clazz`.
+ return anySourceMatchesInheritanceRuleDirectly(clazz, rule, true);
+ }
+
+ private boolean anyImplementedInterfaceMatchesImplementsRule(
+ DexClass clazz, ProguardConfigurationRule rule) {
+ // TODO(herhut): Maybe it would be better to do this breadth first.
if (clazz == null) {
return false;
}
- return
- anySuperTypeMatches(
- clazz.superType,
- definitionFor,
- rule.getInheritanceClassName(),
- rule.getInheritanceAnnotation())
- || anyImplementedInterfaceMatches(
- clazz,
- definitionFor,
- rule.getInheritanceClassName(),
- rule.getInheritanceAnnotation());
+ for (DexType iface : clazz.interfaces.values) {
+ DexClass ifaceClass = application.definitionFor(iface);
+ if (ifaceClass == null) {
+ // TODO(herhut): Warn about broken supertype chain?
+ return false;
+ }
+ // TODO(b/110141157): Should the vertical class merger move annotations from the source to
+ // the target class? If so, it is sufficient only to apply the annotation-matcher to the
+ // annotations of `ifaceClass`.
+ if (rule.getInheritanceClassName().matches(iface, appView)
+ && containsAnnotation(rule.getInheritanceAnnotation(), ifaceClass.annotations)) {
+ return true;
+ }
+ if (anyImplementedInterfaceMatchesImplementsRule(ifaceClass, rule)) {
+ return true;
+ }
+ }
+ if (clazz.superType == null) {
+ return false;
+ }
+ DexClass superClass = application.definitionFor(clazz.superType);
+ if (superClass == null) {
+ // TODO(herhut): Warn about broken supertype chain?
+ return false;
+ }
+ return anyImplementedInterfaceMatchesImplementsRule(superClass, rule);
+ }
+
+ private boolean anySourceMatchesInheritanceRuleDirectly(
+ DexClass clazz, ProguardConfigurationRule rule, boolean isInterface) {
+ // TODO(b/110141157): Figure out what to do with annotations. Should the annotations of
+ // the DexClass corresponding to `sourceType` satisfy the `annotation`-matcher?
+ return appView.verticallyMergedClasses() != null
+ && appView.verticallyMergedClasses().getSourcesFor(clazz.type).stream()
+ .filter(
+ sourceType ->
+ appView.appInfo().definitionFor(sourceType).accessFlags.isInterface()
+ == isInterface)
+ .anyMatch(rule.getInheritanceClassName()::matches);
}
private boolean allRulesSatisfied(Collection<ProguardMemberRule> memberKeepRules,
@@ -606,11 +663,10 @@
}
private boolean ruleSatisfiedByMethods(
- ProguardMemberRule rule,
- Iterable<DexEncodedMethod> methods) {
+ ProguardMemberRule rule, Iterable<DexEncodedMethod> methods) {
if (rule.getRuleType().includesMethods()) {
for (DexEncodedMethod method : methods) {
- if (rule.matches(method, dexStringCache)) {
+ if (rule.matches(method, appView, dexStringCache)) {
return true;
}
}
@@ -619,22 +675,13 @@
}
private boolean ruleSatisfiedByMethods(ProguardMemberRule rule, DexEncodedMethod[] methods) {
- if (rule.getRuleType().includesMethods()) {
- for (DexEncodedMethod method : methods) {
- if (rule.matches(method, dexStringCache)) {
- return true;
- }
- }
- }
- return false;
+ return ruleSatisfiedByMethods(rule, Arrays.asList(methods));
}
- private boolean ruleSatisfiedByFields(
- ProguardMemberRule rule,
- Iterable<DexEncodedField> fields) {
+ private boolean ruleSatisfiedByFields(ProguardMemberRule rule, Iterable<DexEncodedField> fields) {
if (rule.getRuleType().includesFields()) {
for (DexEncodedField field : fields) {
- if (rule.matches(field, dexStringCache)) {
+ if (rule.matches(field, appView, dexStringCache)) {
return true;
}
}
@@ -643,14 +690,7 @@
}
private boolean ruleSatisfiedByFields(ProguardMemberRule rule, DexEncodedField[] fields) {
- if (rule.getRuleType().includesFields()) {
- for (DexEncodedField field : fields) {
- if (rule.matches(field, dexStringCache)) {
- return true;
- }
- }
- }
- return false;
+ return ruleSatisfiedByFields(rule, Arrays.asList(fields));
}
static boolean containsAnnotation(ProguardTypeMatcher classAnnotation,
@@ -680,7 +720,7 @@
return;
}
for (ProguardMemberRule rule : rules) {
- if (rule.matches(method, dexStringCache)) {
+ if (rule.matches(method, appView, dexStringCache)) {
if (Log.ENABLED) {
Log.verbose(getClass(), "Marking method `%s` due to `%s { %s }`.", method, context,
rule);
@@ -699,7 +739,7 @@
ProguardConfigurationRule context,
DexDefinition precondition) {
for (ProguardMemberRule rule : rules) {
- if (rule.matches(field, dexStringCache)) {
+ if (rule.matches(field, appView, dexStringCache)) {
if (Log.ENABLED) {
Log.verbose(getClass(), "Marking field `%s` due to `%s { %s }`.", field, context,
rule);
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index 66801c0..d41bd69 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -49,7 +49,9 @@
import com.android.tools.r8.utils.Timing;
import com.google.common.base.Equivalence;
import com.google.common.base.Equivalence.Wrapper;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import it.unimi.dsi.fastutil.objects.Reference2BooleanOpenHashMap;
@@ -91,9 +93,19 @@
public static class VerticallyMergedClasses {
private final Map<DexType, DexType> mergedClasses;
+ private final Map<DexType, List<DexType>> sources;
private VerticallyMergedClasses(Map<DexType, DexType> mergedClasses) {
+ Map<DexType, List<DexType>> sources = Maps.newIdentityHashMap();
+ mergedClasses.forEach(
+ (source, target) ->
+ sources.computeIfAbsent(target, key -> new ArrayList<>()).add(source));
this.mergedClasses = mergedClasses;
+ this.sources = sources;
+ }
+
+ public List<DexType> getSourcesFor(DexType type) {
+ return sources.getOrDefault(type, ImmutableList.of());
}
public DexType getTargetFor(DexType type) {
@@ -1288,6 +1300,21 @@
return field.toTypeSubstitutedField(newSignature);
}
+
+ private void makeStatic(DexEncodedMethod method) {
+ method.accessFlags.setStatic();
+
+ Code code = method.getCode();
+ if (code.isJarCode()) {
+ MethodNode node = code.asJarCode().getNode();
+ node.access |= Opcodes.ACC_STATIC;
+ node.desc = method.method.proto.toDescriptorString();
+ } else {
+ // Due to member rebinding we may have inserted bridge methods with synthesized code.
+ // Currently, there is no easy way to make such code static.
+ abortMerge = true;
+ }
+ }
}
private static void makePrivate(DexEncodedMethod method) {
@@ -1297,17 +1324,6 @@
method.accessFlags.setPrivate();
}
- private static void makeStatic(DexEncodedMethod method) {
- method.accessFlags.setStatic();
-
- Code code = method.getCode();
- if (code.isJarCode()) {
- MethodNode node = code.asJarCode().getNode();
- node.access |= Opcodes.ACC_STATIC;
- node.desc = method.method.proto.toDescriptorString();
- }
- }
-
private DexProto getStaticProto(DexType receiverType, DexProto proto) {
DexType[] parameterTypes = new DexType[proto.parameters.size() + 1];
parameterTypes[0] = receiverType;
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
index a4f55c3..ed12177 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -60,6 +60,58 @@
private final List<StringResource> mainDexListResources;
private final List<String> mainDexClasses;
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ try {
+ if (!programResourceProviders.isEmpty()) {
+ builder.append(" Program resources:").append(System.lineSeparator());
+ printProgramResourceProviders(builder, programResourceProviders);
+ }
+ if (!classpathResourceProviders.isEmpty()) {
+ builder.append(" Classpath resources:").append(System.lineSeparator());
+ printClassFileProviders(builder, classpathResourceProviders);
+ }
+ if (!libraryResourceProviders.isEmpty()) {
+ builder.append(" Library resources:").append(System.lineSeparator());
+ printClassFileProviders(builder, libraryResourceProviders);
+ }
+ } catch (ResourceException e) {
+ e.printStackTrace();
+ }
+ return builder.toString();
+ }
+
+ private static void printProgramResourceProviders(
+ StringBuilder builder, Collection<ProgramResourceProvider> providers)
+ throws ResourceException {
+ for (ProgramResourceProvider provider : providers) {
+ for (ProgramResource resource : provider.getProgramResources()) {
+ printProgramResource(builder, resource);
+ }
+ }
+ }
+
+ private static void printClassFileProviders(
+ StringBuilder builder, Collection<ClassFileResourceProvider> providers) {
+ for (ClassFileResourceProvider provider : providers) {
+ for (String descriptor : provider.getClassDescriptors()) {
+ ProgramResource resource = provider.getProgramResource(descriptor);
+ printProgramResource(builder, resource);
+ }
+ }
+ }
+
+ private static void printProgramResource(StringBuilder builder, ProgramResource resource) {
+ builder.append(" ").append(resource.getOrigin());
+ Set<String> descriptors = resource.getClassDescriptors();
+ if (descriptors != null && !descriptors.isEmpty()) {
+ builder.append(" contains ");
+ StringUtils.append(builder, descriptors);
+ }
+ builder.append(System.lineSeparator());
+ }
+
// See factory methods and AndroidApp.Builder below.
private AndroidApp(
ImmutableList<ProgramResourceProvider> programResourceProviders,
diff --git a/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java b/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java
index d77adfd..8fa85bf 100644
--- a/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java
@@ -65,7 +65,7 @@
} catch (ResourceException e) {
throw reporter.fatalError(new ExceptionDiagnostic(e, e.getOrigin()));
} catch (AssertionError e) {
- throw reporter.fatalError(new ExceptionDiagnostic(e, Origin.unknown()));
+ throw reporter.fatalError(new ExceptionDiagnostic(e, Origin.unknown()), e);
}
reporter.failIfPendingErrors();
} catch (AbortException e) {
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 828a463..d83ad75 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -41,8 +41,7 @@
public enum LineNumberOptimization {
OFF,
- ON,
- IDENTITY_MAPPING
+ ON
}
public final DexItemFactory itemFactory;
@@ -454,6 +453,7 @@
? NondeterministicIROrdering.getInstance()
: IdentityIROrdering.getInstance();
+ public boolean allowProguardRulesThatUseExtendsOrImplementsWrong = true;
public boolean alwaysUsePessimisticRegisterAllocation = false;
public boolean invertConditionals = false;
public boolean placeExceptionalBlocksLast = false;
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 66490b2..e81d920 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -10,6 +10,16 @@
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;
import com.android.tools.r8.graph.DexDebugEventVisitor;
import com.android.tools.r8.graph.DexDebugInfo;
@@ -41,86 +51,6 @@
public class LineNumberOptimizer {
- // EventFilter is a visitor for DebugEvents, splits events into two sinks:
- // - Forwards non-positional events unchanged into a BypassedEventReceiver
- // - Forwards positional events, accumulated into DexDebugPositionStates, into
- // positionEventReceiver.
- private static class EventFilter implements DexDebugEventVisitor {
- private final BypassedEventReceiver bypassedEventReceiver;
- private final PositionEventReceiver positionEventReceiver;
-
- private interface BypassedEventReceiver {
- void receiveBypassedEvent(DexDebugEvent event);
- }
-
- private interface PositionEventReceiver {
- void receivePositionEvent(DexDebugPositionState positionState);
- }
-
- private final DexDebugPositionState positionState;
-
- private EventFilter(
- int startLine,
- DexMethod method,
- BypassedEventReceiver bypassedEventReceiver,
- PositionEventReceiver positionEventReceiver) {
- positionState = new DexDebugPositionState(startLine, method);
- this.bypassedEventReceiver = bypassedEventReceiver;
- this.positionEventReceiver = positionEventReceiver;
- }
-
- @Override
- public void visit(DexDebugEvent.SetPrologueEnd event) {
- bypassedEventReceiver.receiveBypassedEvent(event);
- }
-
- @Override
- public void visit(DexDebugEvent.SetEpilogueBegin event) {
- bypassedEventReceiver.receiveBypassedEvent(event);
- }
-
- @Override
- public void visit(DexDebugEvent.StartLocal event) {
- bypassedEventReceiver.receiveBypassedEvent(event);
- }
-
- @Override
- public void visit(DexDebugEvent.EndLocal event) {
- bypassedEventReceiver.receiveBypassedEvent(event);
- }
-
- @Override
- public void visit(DexDebugEvent.RestartLocal event) {
- bypassedEventReceiver.receiveBypassedEvent(event);
- }
-
- @Override
- public void visit(DexDebugEvent.AdvancePC advancePC) {
- positionState.visit(advancePC);
- }
-
- @Override
- public void visit(DexDebugEvent.AdvanceLine advanceLine) {
- positionState.visit(advanceLine);
- }
-
- @Override
- public void visit(DexDebugEvent.SetInlineFrame setInlineFrame) {
- positionState.visit(setInlineFrame);
- }
-
- @Override
- public void visit(DexDebugEvent.Default defaultEvent) {
- positionState.visit(defaultEvent);
- positionEventReceiver.receivePositionEvent(positionState);
- }
-
- @Override
- public void visit(DexDebugEvent.SetFile setFile) {
- positionState.visit(setFile);
- }
- }
-
// PositionRemapper is a stateful function which takes a position (represented by a
// DexDebugPositionState) and returns a remapped Position.
private interface PositionRemapper {
@@ -165,6 +95,11 @@
this.processedEvents = processedEvents;
}
+ private void emitAdvancePc(int pc) {
+ processedEvents.add(new AdvancePC(pc - previousPc));
+ previousPc = pc;
+ }
+
private void emitPositionEvents(int currentPc, Position currentPosition) {
if (previousPosition == null) {
startLine = currentPosition.line;
@@ -255,7 +190,6 @@
for (DexEncodedMethod method : methods) {
List<MappedPosition> mappedPositions = new ArrayList<>();
-
Code code = method.getCode();
if (code != null) {
if (code.isDexCode() && doesContainPositions(code.asDexCode())) {
@@ -469,36 +403,96 @@
DexDebugInfo debugInfo = dexCode.getDebugInfo();
List<DexDebugEvent> processedEvents = new ArrayList<>();
- // Our pipeline will be:
- // [debugInfo.events] -> eventFilter -> positionRemapper -> positionEventEmitter ->
- // [processedEvents]
PositionEventEmitter positionEventEmitter =
new PositionEventEmitter(application.dexItemFactory, method.method, processedEvents);
- EventFilter eventFilter =
- new EventFilter(
- debugInfo.startLine,
- method.method,
- processedEvents::add,
- positionState -> {
- int currentLine = positionState.getCurrentLine();
- assert currentLine >= 0;
- Position position =
- positionRemapper.createRemappedPosition(
- positionState.getCurrentLine(),
- positionState.getCurrentFile(),
- positionState.getCurrentMethod(),
- positionState.getCurrentCallerPosition());
- mappedPositions.add(
- new MappedPosition(
- positionState.getCurrentMethod(),
- currentLine,
- positionState.getCurrentCallerPosition(),
- position.line));
- positionEventEmitter.emitPositionEvents(positionState.getCurrentPc(), position);
- });
+ // 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;
+
+ private void flushPc() {
+ if (currentPc != state.getCurrentPc()) {
+ positionEventEmitter.emitAdvancePc(state.getCurrentPc());
+ currentPc = state.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);
+ }
+
+ @Override
+ public void visit(Default defaultEvent) {
+ state.visit(defaultEvent);
+ int currentLine = state.getCurrentLine();
+ assert currentLine >= 0;
+ Position position =
+ positionRemapper.createRemappedPosition(
+ state.getCurrentLine(),
+ state.getCurrentFile(),
+ state.getCurrentMethod(),
+ state.getCurrentCallerPosition());
+ mappedPositions.add(
+ new MappedPosition(
+ state.getCurrentMethod(),
+ currentLine,
+ state.getCurrentCallerPosition(),
+ position.line));
+ positionEventEmitter.emitPositionEvents(state.getCurrentPc(), position);
+ currentPc = state.getCurrentPc();
+ }
+
+ @Override
+ public void visit(SetFile setFile) {
+ processedEvents.add(setFile);
+ }
+
+ @Override
+ public void visit(SetPrologueEnd setPrologueEnd) {
+ processedEvents.add(setPrologueEnd);
+ }
+
+ @Override
+ public void visit(SetEpilogueBegin setEpilogueBegin) {
+ processedEvents.add(setEpilogueBegin);
+ }
+
+ @Override
+ public void visit(StartLocal startLocal) {
+ flushPc();
+ processedEvents.add(startLocal);
+ }
+
+ @Override
+ public void visit(EndLocal endLocal) {
+ flushPc();
+ processedEvents.add(endLocal);
+ }
+
+ @Override
+ public void visit(RestartLocal restartLocal) {
+ flushPc();
+ processedEvents.add(restartLocal);
+ }
+ };
+
for (DexDebugEvent event : debugInfo.events) {
- event.accept(eventFilter);
+ event.accept(visitor);
}
DexDebugInfo optimizedDebugInfo =
diff --git a/src/main/java/com/android/tools/r8/utils/Reporter.java b/src/main/java/com/android/tools/r8/utils/Reporter.java
index 5a1bd06..928058e 100644
--- a/src/main/java/com/android/tools/r8/utils/Reporter.java
+++ b/src/main/java/com/android/tools/r8/utils/Reporter.java
@@ -108,7 +108,7 @@
}
private <T extends Throwable> T addSuppressedExceptions(T t) {
- suppressedExceptions.forEach(throwable -> t.addSuppressed(throwable));
+ suppressedExceptions.forEach(t::addSuppressed);
return t;
}
diff --git a/src/test/java/com/android/tools/r8/D8TestBuilder.java b/src/test/java/com/android/tools/r8/D8TestBuilder.java
new file mode 100644
index 0000000..368e8e4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/D8TestBuilder.java
@@ -0,0 +1,51 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import com.android.tools.r8.D8Command.Builder;
+import com.android.tools.r8.TestBase.Backend;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Collection;
+
+public class D8TestBuilder extends TestCompilerBuilder<D8Command, Builder, D8TestBuilder> {
+
+ private final D8Command.Builder builder;
+
+ private D8TestBuilder(TestState state, D8Command.Builder builder) {
+ super(state, builder, Backend.DEX);
+ this.builder = builder;
+ }
+
+ public static D8TestBuilder create(TestState state) {
+ return new D8TestBuilder(state, D8Command.builder());
+ }
+
+ @Override
+ D8TestBuilder self() {
+ return this;
+ }
+
+ @Override
+ void internalCompile(Builder builder) throws CompilationFailedException {
+ D8.run(builder.build());
+ }
+
+ public D8TestBuilder addClasspathClasses(Class<?>... classes) {
+ return addClasspathClasses(Arrays.asList(classes));
+ }
+
+ public D8TestBuilder addClasspathClasses(Collection<Class<?>> classes) {
+ return addClasspathFiles(getFilesForClasses(classes));
+ }
+
+ public D8TestBuilder addClasspathFiles(Path... files) {
+ return addClasspathFiles(Arrays.asList(files));
+ }
+
+ public D8TestBuilder addClasspathFiles(Collection<Path> files) {
+ builder.addClasspathFiles(files);
+ return self();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/JvmTestBuilder.java b/src/test/java/com/android/tools/r8/JvmTestBuilder.java
new file mode 100644
index 0000000..484adee
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/JvmTestBuilder.java
@@ -0,0 +1,158 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.origin.PathOrigin;
+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.ListUtils;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+public class JvmTestBuilder extends TestBuilder<JvmTestBuilder> {
+
+ private static class ClassFileResource implements ProgramResource {
+
+ private final Path file;
+ private final String descriptor;
+ private final Origin origin;
+
+ ClassFileResource(Class<?> clazz) {
+ this(
+ ToolHelper.getClassFileForTestClass(clazz),
+ DescriptorUtils.javaTypeToDescriptor(clazz.getTypeName()));
+ }
+
+ ClassFileResource(Path file, String descriptor) {
+ this.file = file;
+ this.descriptor = descriptor;
+ origin = new PathOrigin(file);
+ }
+
+ @Override
+ public Kind getKind() {
+ return Kind.CF;
+ }
+
+ @Override
+ public InputStream getByteStream() throws ResourceException {
+ try {
+ return Files.newInputStream(file);
+ } catch (IOException e) {
+ throw new ResourceException(getOrigin(), e);
+ }
+ }
+
+ @Override
+ public Set<String> getClassDescriptors() {
+ return Collections.singleton(descriptor);
+ }
+
+ @Override
+ public Origin getOrigin() {
+ return origin;
+ }
+ }
+
+ private static class ClassFileResourceProvider implements ProgramResourceProvider {
+
+ private final List<ProgramResource> resources;
+
+ public ClassFileResourceProvider(List<ProgramResource> resources) {
+ this.resources = resources;
+ }
+
+ @Override
+ public Collection<ProgramResource> getProgramResources() throws ResourceException {
+ return resources;
+ }
+
+ @Override
+ public DataResourceProvider getDataResourceProvider() {
+ return null;
+ }
+ }
+
+ // Ordered list of classpath entries.
+ private List<Path> classpath = new ArrayList<>();
+
+ private AndroidApp.Builder builder = AndroidApp.builder();
+
+ private JvmTestBuilder(TestState state) {
+ super(state);
+ }
+
+ public static JvmTestBuilder create(TestState state) {
+ return new JvmTestBuilder(state);
+ }
+
+ @Override
+ JvmTestBuilder self() {
+ return this;
+ }
+
+ @Override
+ public TestRunResult run(String mainClass) throws IOException {
+ ProcessResult result = ToolHelper.runJava(classpath, mainClass);
+ return new TestRunResult(builder.build(), result);
+ }
+
+ @Override
+ public JvmTestBuilder addLibraryFiles(Collection<Path> files) {
+ throw new Unimplemented("No support for changing the Java runtime library.");
+ }
+
+ @Override
+ public JvmTestBuilder 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.
+ List<ProgramResource> resources = ListUtils.map(classes, ClassFileResource::new);
+ AndroidApp build = AndroidApp.builder()
+ .addProgramResourceProvider(new ClassFileResourceProvider(resources)).build();
+ Path out;
+ try {
+ out = getState().getNewTempFolder().resolve("out.zip");
+ build.writeToZip(out, OutputMode.ClassFile);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ classpath.add(out);
+ builder.addProgramFiles(out);
+ return self();
+ }
+
+ @Override
+ public JvmTestBuilder addProgramFiles(Collection<Path> files) {
+ throw new Unimplemented(
+ "No support for adding paths directly (we need to compute the descriptor)");
+ }
+
+ public JvmTestBuilder addClasspath(Path... paths) {
+ return addClasspath(Arrays.asList(paths));
+ }
+
+ public JvmTestBuilder addClasspath(List<Path> paths) {
+ for (Path path : paths) {
+ assert Files.isDirectory(path) || FileUtils.isArchive(path);
+ classpath.add(path);
+ }
+ return self();
+ }
+
+ public JvmTestBuilder addTestClasspath() {
+ return addClasspath(ToolHelper.getClassPathForTests());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
new file mode 100644
index 0000000..50e659c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -0,0 +1,84 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import com.android.tools.r8.R8Command.Builder;
+import com.android.tools.r8.TestBase.Backend;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+
+public class R8TestBuilder extends TestCompilerBuilder<R8Command, Builder, R8TestBuilder> {
+
+ private final R8Command.Builder builder;
+
+ private R8TestBuilder(TestState state, Builder builder, Backend backend) {
+ super(state, builder, backend);
+ this.builder = builder;
+ }
+
+ public static R8TestBuilder create(TestState state, Backend backend) {
+ return new R8TestBuilder(state, R8Command.builder(), backend);
+ }
+
+ private boolean enableInliningAnnotations = false;
+
+ @Override
+ R8TestBuilder self() {
+ return this;
+ }
+
+ @Override
+ public void internalCompile(Builder builder) throws CompilationFailedException {
+ if (enableInliningAnnotations) {
+ ToolHelper.allowTestProguardOptions(builder);
+ }
+ R8.run(builder.build());
+ }
+
+ public R8TestBuilder addKeepRules(String... rules) {
+ return addKeepRules(Arrays.asList(rules));
+ }
+
+ public R8TestBuilder addKeepRules(Collection<String> rules) {
+ builder.addProguardConfiguration(new ArrayList<>(rules), Origin.unknown());
+ return self();
+ }
+
+ public R8TestBuilder addKeepAllClassesRule() {
+ addKeepRules("-keep class ** { *; }");
+ return self();
+ }
+
+ public R8TestBuilder addKeepClassRules(Class<?>... classes) {
+ for (Class<?> clazz : classes) {
+ addKeepRules("-keep class " + clazz.getTypeName());
+ }
+ return self();
+ }
+
+ public R8TestBuilder addKeepPackageRules(Package pkg) {
+ return addKeepRules("-keep class " + pkg.getName() + ".*");
+ }
+
+ public R8TestBuilder addKeepMainRule(Class<?> mainClass) {
+ return addKeepRules(
+ StringUtils.joinLines(
+ "-keep class " + mainClass.getTypeName() + " {",
+ " public static void main(java.lang.String[]);",
+ "}"));
+ }
+
+ public R8TestBuilder enableInliningAnnotations() {
+ if (!enableInliningAnnotations) {
+ enableInliningAnnotations = true;
+ addKeepRules(
+ "-forceinline class * { @com.android.tools.r8.ForceInline *; }",
+ "-neverinline class * { @com.android.tools.r8.NeverInline *; }");
+ }
+ return self();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 6f93df4..efec188 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -12,10 +12,8 @@
import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
import com.android.tools.r8.ToolHelper.DexVm;
import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.cf.code.CfInstruction;
import com.android.tools.r8.code.Instruction;
import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.SmaliWriter;
@@ -61,6 +59,19 @@
import org.objectweb.asm.ClassVisitor;
public class TestBase {
+
+ public R8TestBuilder testForR8(Backend backend) {
+ return R8TestBuilder.create(new TestState(temp), backend);
+ }
+
+ public D8TestBuilder testForD8() {
+ return D8TestBuilder.create(new TestState(temp));
+ }
+
+ public JvmTestBuilder testForJvm() {
+ return JvmTestBuilder.create(new TestState(temp));
+ }
+
public enum Backend {
CF,
DEX
@@ -443,7 +454,7 @@
String proguardConfig,
Consumer<InternalOptions> optionsConsumer,
Backend backend)
- throws IOException, CompilationFailedException {
+ throws CompilationFailedException {
R8Command command =
ToolHelper.prepareR8CommandBuilder(app, emptyConsumer(backend))
.addProguardConfiguration(ImmutableList.of(proguardConfig), Origin.unknown())
@@ -455,7 +466,7 @@
/** Compile an application with R8 using the supplied proguard configuration. */
protected AndroidApp compileWithR8(
AndroidApp app, Path proguardConfig, Consumer<InternalOptions> optionsConsumer)
- throws IOException, CompilationFailedException {
+ throws CompilationFailedException {
R8Command command =
ToolHelper.prepareR8CommandBuilder(app)
.addProguardConfigurationFiles(proguardConfig)
diff --git a/src/test/java/com/android/tools/r8/TestBuilder.java b/src/test/java/com/android/tools/r8/TestBuilder.java
new file mode 100644
index 0000000..6d5a158
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/TestBuilder.java
@@ -0,0 +1,89 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
+
+import com.android.tools.r8.utils.ListUtils;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+public abstract class TestBuilder<T extends TestBuilder<T>> {
+
+ private final TestState state;
+
+ public TestBuilder(TestState state) {
+ this.state = state;
+ }
+
+ public TestState getState() {
+ return state;
+ }
+
+ abstract T self();
+
+ public abstract TestRunResult run(String mainClass)
+ throws IOException, CompilationFailedException;
+
+ public TestRunResult run(Class mainClass) throws IOException, CompilationFailedException {
+ return run(mainClass.getTypeName());
+ }
+
+ public abstract T addProgramFiles(Collection<Path> files);
+
+ public T addProgramClasses(Class<?>... classes) {
+ return addProgramClasses(Arrays.asList(classes));
+ }
+
+ public T addProgramClasses(Collection<Class<?>> classes) {
+ return addProgramFiles(getFilesForClasses(classes));
+ }
+
+ public T addProgramFiles(Path... files) {
+ return addProgramFiles(Arrays.asList(files));
+ }
+
+ public T addProgramClassesAndInnerClasses(Class<?>... classes) throws IOException {
+ return addProgramClassesAndInnerClasses(Arrays.asList(classes));
+ }
+
+ public T addProgramClassesAndInnerClasses(Collection<Class<?>> classes) throws IOException {
+ return addProgramFiles(getFilesForClassesAndInnerClasses(classes));
+ }
+
+ public abstract T addLibraryFiles(Collection<Path> files);
+
+ public T addLibraryClasses(Class<?>... classes) {
+ return addLibraryClasses(Arrays.asList(classes));
+ }
+
+ public T addLibraryClasses(Collection<Class<?>> classes) {
+ return addLibraryFiles(getFilesForClasses(classes));
+ }
+
+ public T addLibraryFiles(Path... files) {
+ return addLibraryFiles(Arrays.asList(files));
+ }
+
+ static Collection<Path> getFilesForClasses(Collection<Class<?>> classes) {
+ return ListUtils.map(classes, ToolHelper::getClassFileForTestClass);
+ }
+
+ static Collection<Path> getFilesForClassesAndInnerClasses(Collection<Class<?>> classes)
+ throws IOException {
+ Set<Path> paths = new HashSet<>();
+ for (Class clazz : classes) {
+ Path path = ToolHelper.getClassFileForTestClass(clazz);
+ String prefix = path.toString().replace(CLASS_EXTENSION, "$");
+ paths.addAll(
+ ToolHelper.getClassFilesForTestDirectory(
+ path.getParent(), p -> p.equals(path) || p.toString().startsWith(prefix)));
+ }
+ return paths;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/TestCompileResult.java b/src/test/java/com/android/tools/r8/TestCompileResult.java
new file mode 100644
index 0000000..9996dde
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/TestCompileResult.java
@@ -0,0 +1,48 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import com.android.tools.r8.TestBase.Backend;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.utils.AndroidApp;
+import java.io.IOException;
+import java.nio.file.Path;
+
+public class TestCompileResult {
+ private final TestState state;
+ private final Backend backend;
+ private final AndroidApp app;
+
+ public TestCompileResult(TestState state, Backend backend, AndroidApp app) {
+ this.state = state;
+ this.backend = backend;
+ this.app = app;
+ }
+
+ public TestRunResult run(String mainClass) throws IOException {
+ switch (backend) {
+ case DEX:
+ return runArt(mainClass);
+ case CF:
+ return runJava(mainClass);
+ default:
+ throw new Unreachable();
+ }
+ }
+
+ private TestRunResult runJava(String mainClass) throws IOException {
+ Path out = state.getNewTempFolder().resolve("out.zip");
+ app.writeToZip(out, OutputMode.ClassFile);
+ ProcessResult result = ToolHelper.runJava(out, mainClass);
+ return new TestRunResult(app, result);
+ }
+
+ private TestRunResult 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 new TestRunResult(app, result);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
new file mode 100644
index 0000000..c6af2c7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -0,0 +1,95 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import com.android.tools.r8.TestBase.Backend;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.AndroidAppConsumers;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Collection;
+
+public abstract class TestCompilerBuilder<
+ C extends BaseCompilerCommand,
+ B extends BaseCompilerCommand.Builder<C, B>,
+ T extends TestCompilerBuilder<C, B, T>>
+ extends TestBuilder<T> {
+
+ private final B builder;
+ private final Backend backend;
+
+ // Default initialized setup. Can be overwritten if needed.
+ private Path defaultLibrary;
+ private ProgramConsumer programConsumer;
+ private AndroidApiLevel defaultMinApiLevel = ToolHelper.getMinApiLevelForDexVm();
+
+ TestCompilerBuilder(TestState state, B builder, Backend backend) {
+ super(state);
+ this.builder = builder;
+ this.backend = backend;
+ defaultLibrary = TestBase.runtimeJar(backend);
+ programConsumer = TestBase.emptyConsumer(backend);
+ }
+
+ abstract T self();
+
+ abstract void internalCompile(B builder) throws CompilationFailedException;
+
+ public TestCompileResult compile() throws CompilationFailedException {
+ AndroidAppConsumers sink = new AndroidAppConsumers();
+ builder.setProgramConsumer(sink.wrapProgramConsumer(programConsumer));
+ if (defaultLibrary != null) {
+ builder.addLibraryFiles(defaultLibrary);
+ }
+ if (backend == Backend.DEX && defaultMinApiLevel != null) {
+ builder.setMinApiLevel(defaultMinApiLevel.getLevel());
+ }
+ internalCompile(builder);
+ return new TestCompileResult(getState(), backend, sink.build());
+ }
+
+ @Override
+ public TestRunResult run(String mainClass) throws IOException, CompilationFailedException {
+ return compile().run(mainClass);
+ }
+
+ public T setMode(CompilationMode mode) {
+ builder.setMode(mode);
+ return self();
+ }
+
+ public T debug() {
+ return setMode(CompilationMode.DEBUG);
+ }
+
+ public T release() {
+ return setMode(CompilationMode.RELEASE);
+ }
+
+ public T setMinApi(AndroidApiLevel minApiLevel) {
+ // Should we ignore min-api calls when backend == CF?
+ this.defaultMinApiLevel = null;
+ builder.setMinApiLevel(minApiLevel.getLevel());
+ return self();
+ }
+
+ public T setProgramConsumer(ProgramConsumer programConsumer) {
+ assert programConsumer != null;
+ this.programConsumer = programConsumer;
+ return self();
+ }
+
+ @Override
+ public T addProgramFiles(Collection<Path> files) {
+ builder.addProgramFiles(files);
+ return self();
+ }
+
+ @Override
+ public T addLibraryFiles(Collection<Path> files) {
+ defaultLibrary = null;
+ builder.addLibraryFiles(files);
+ return self();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/TestRunResult.java b/src/test/java/com/android/tools/r8/TestRunResult.java
new file mode 100644
index 0000000..462ca1f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/TestRunResult.java
@@ -0,0 +1,68 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+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.util.concurrent.ExecutionException;
+
+public class TestRunResult {
+ private final AndroidApp app;
+ private final ProcessResult result;
+
+ public TestRunResult(AndroidApp app, ProcessResult result) {
+ this.app = app;
+ this.result = result;
+ }
+
+ public TestRunResult assertSuccess() {
+ assertEquals(errorMessage("Expected run to succeed."), 0, result.exitCode);
+ return this;
+ }
+
+ public TestRunResult assertFailure() {
+ assertNotEquals(errorMessage("Expected run to fail."), 0, result.exitCode);
+ return this;
+ }
+
+ public void assertSuccessWithOutput(String expected) {
+ assertSuccess();
+ assertEquals(errorMessage("Run std output incorrect."), expected, result.stdout);
+ }
+
+ public CodeInspector inspector() throws IOException, ExecutionException {
+ // Inspection post run implies success. If inspection of an invalid program is needed it should
+ // be done on the compilation result or on the input.
+ assertSuccess();
+ assertNotNull(app);
+ return new CodeInspector(app);
+ }
+
+ private String errorMessage(String message) {
+ StringBuilder builder = new StringBuilder(message).append('\n');
+ printInfo(builder);
+ return builder.toString();
+ }
+
+ private void printInfo(StringBuilder builder) {
+ builder.append("APPLICATION: ");
+ printApplication(builder);
+ builder.append('\n');
+ printProcessResult(builder);
+ }
+
+ private void printApplication(StringBuilder builder) {
+ builder.append(app == null ? "<default>" : app.toString());
+ }
+
+ private void printProcessResult(StringBuilder builder) {
+ builder.append("COMMAND: ").append(result.command).append('\n').append(result);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/TestState.java b/src/test/java/com/android/tools/r8/TestState.java
new file mode 100644
index 0000000..d516c7c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/TestState.java
@@ -0,0 +1,21 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import org.junit.rules.TemporaryFolder;
+
+public class TestState {
+
+ private final TemporaryFolder temp;
+
+ public TestState(TemporaryFolder temp) {
+ this.temp = temp;
+ }
+
+ public Path getNewTempFolder() throws IOException {
+ return temp.newFolder().toPath();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index bc42c7c..fa8ca13 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -106,7 +106,11 @@
private static final String PROGUARD = PROGUARD5_2_1;
public static final String JACOCO_AGENT = "third_party/jacoco/org.jacoco.agent-0.8.2-runtime.jar";
+ private static final String RETRACE6_0_1 = "third_party/proguard/proguard6.0.1/bin/retrace";
+ private static final String RETRACE = RETRACE6_0_1;
+
public static final Path R8_JAR = Paths.get(LIBS_DIR, "r8.jar");
+ public static final Path R8_LIB_JAR = Paths.get(LIBS_DIR, "r8lib_with_deps.jar");
public enum DexVm {
ART_4_0_4_TARGET(Version.V4_0_4, Kind.TARGET),
@@ -525,6 +529,13 @@
return PROGUARD6_0_1 + ".sh";
}
+ private static String getRetraceScript() {
+ if (isWindows()) {
+ return RETRACE + ".bat";
+ }
+ return RETRACE + ".sh";
+ }
+
private static Path getDxExecutablePath() {
String toolsDir = toolsDir();
String executableName = toolsDir.equals("windows") ? "dx.bat" : "dx";
@@ -1499,16 +1510,39 @@
return runProguard(getProguard6Script(), inJar, outJar, configs, map);
}
+ public static ProcessResult runRetraceRaw(Path map, Path stackTrace) throws IOException {
+ List<String> command = new ArrayList<>();
+ command.add(getRetraceScript());
+ command.add(map.toString());
+ command.add(stackTrace.toString());
+ ProcessBuilder builder = new ProcessBuilder(command);
+ return ToolHelper.runProcess(builder);
+ }
+
+ public static String runRetrace(Path map, Path stackTrace) throws IOException {
+ ProcessResult result = runRetraceRaw(map, stackTrace);
+ if (result.exitCode != 0) {
+ fail("Retrace failed, exit code " + result.exitCode + ", stderr:\n" + result.stderr);
+ }
+ return result.stdout;
+ }
+
public static class ProcessResult {
public final int exitCode;
public final String stdout;
public final String stderr;
+ public final String command;
- ProcessResult(int exitCode, String stdout, String stderr) {
+ ProcessResult(int exitCode, String stdout, String stderr, String command) {
this.exitCode = exitCode;
this.stdout = stdout;
this.stderr = stderr;
+ this.command = command;
+ }
+
+ ProcessResult(int exitCode, String stdout, String stderr) {
+ this(exitCode, stdout, stderr, null);
}
@Override
@@ -1547,7 +1581,8 @@
}
public static ProcessResult runProcess(ProcessBuilder builder) throws IOException {
- System.out.println(String.join(" ", builder.command()));
+ String command = String.join(" ", builder.command());
+ System.out.println(command);
Process p = builder.start();
// Drain stdout and stderr so that the process does not block. Read stdout and stderr
// in parallel to make sure that neither buffer can get filled up which will cause the
@@ -1565,7 +1600,8 @@
} catch (InterruptedException e) {
throw new RuntimeException("Execution interrupted", e);
}
- return new ProcessResult(p.exitValue(), stdoutReader.getResult(), stderrReader.getResult());
+ return new ProcessResult(
+ p.exitValue(), stdoutReader.getResult(), stderrReader.getResult(), command);
}
public static R8Command.Builder addProguardConfigurationConsumer(
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 02df3d1..7202914 100644
--- a/src/test/java/com/android/tools/r8/cf/BootstrapCurrentEqualityTest.java
+++ b/src/test/java/com/android/tools/r8/cf/BootstrapCurrentEqualityTest.java
@@ -35,6 +35,10 @@
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
+/**
+ * This test relies on a freshly built from builds/libs/r8lib_with_deps.jar. If this test fails
+ * 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";
@@ -45,7 +49,7 @@
"-keep class " + HELLO_NAME + " {", " public static void main(...);", "}",
};
- private class R8Result {
+ private static class R8Result {
final ProcessResult processResult;
final Path outputJar;
@@ -70,18 +74,19 @@
@BeforeClass
public static void beforeAll() throws Exception {
- r8R8Debug = compileR8(CompilationMode.DEBUG);
- r8R8Release = compileR8(CompilationMode.RELEASE);
+ r8R8Debug = compileR8("--debug");
+ r8R8Release = compileR8("--release");
}
- private static Path compileR8(CompilationMode mode) throws Exception {
+ private static Path compileR8(String mode) throws Exception {
// Run R8 on r8.jar.
- Path output = runR8(ToolHelper.R8_JAR, testFolder.newFolder().toPath(), mode);
+ R8Result output = runExternalR8(
+ ToolHelper.R8_LIB_JAR, ToolHelper.R8_LIB_JAR, testFolder.newFolder().toPath(), MAIN_KEEP, mode);
// Check that all non-abstract classes in the R8'd R8 implement all abstract/interface methods
// from their supertypes. This is a sanity check for the tree shaking and minification.
- AndroidApp app = AndroidApp.builder().addProgramFile(output).build();
+ AndroidApp app = AndroidApp.builder().addProgramFile(output.outputJar).build();
new ClassHierarchyVerifier(new CodeInspector(app)).run();
- return output;
+ return output.outputJar;
}
@Test
@@ -95,10 +100,10 @@
private void compareR8(Path program, ProcessResult runResult, String[] keep, String... args)
throws Exception {
R8Result runR8Debug =
- runExternalR8(ToolHelper.R8_JAR, program, temp.newFolder().toPath(), keep, "--debug");
+ runExternalR8(ToolHelper.R8_LIB_JAR, program, temp.newFolder().toPath(), keep, "--debug");
assertEquals(runResult.toString(), ToolHelper.runJava(runR8Debug.outputJar, args).toString());
R8Result runR8Release =
- runExternalR8(ToolHelper.R8_JAR, program, temp.newFolder().toPath(), keep, "--release");
+ runExternalR8(ToolHelper.R8_LIB_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");
@@ -115,25 +120,17 @@
assertProgramsEqual(result.outputJar, runR8R8.outputJar);
}
- private static Path runR8(Path inputJar, Path outputPath, CompilationMode mode) throws Exception {
- Path outputJar = outputPath.resolve("output.jar");
- ToolHelper.runR8(
- R8Command.builder()
- .setMode(mode)
- .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
- .setProgramConsumer(new ClassFileConsumer.ArchiveConsumer(outputJar, true))
- .addProgramFiles(inputJar)
- .addProguardConfigurationFiles(MAIN_KEEP)
- .build());
- return outputJar;
- }
-
- private R8Result runExternalR8(
+ 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("map.txt");
- FileUtils.writeTextFile(pgConfigFile, keepRules);
ProcessResult processResult =
ToolHelper.runJava(
r8Jar,
@@ -145,7 +142,7 @@
"--output",
outputJar.toString(),
"--pg-conf",
- pgConfigFile.toString(),
+ keepRules.toString(),
mode,
"--pg-map-output",
pgMapFile.toString());
diff --git a/src/test/java/com/android/tools/r8/debug/LineNumberOptimizationTest.java b/src/test/java/com/android/tools/r8/debug/LineNumberOptimizationTest.java
index 616256e..78e9a56 100644
--- a/src/test/java/com/android/tools/r8/debug/LineNumberOptimizationTest.java
+++ b/src/test/java/com/android/tools/r8/debug/LineNumberOptimizationTest.java
@@ -96,12 +96,6 @@
}
@Test
- public void testIdentityCompilation() throws Throwable {
- // Compilation will fail if the identity translation does.
- makeConfig(LineNumberOptimization.IDENTITY_MAPPING, true, false, runtimeKind);
- }
-
- @Test
public void testNotOptimized() throws Throwable {
testRelease(
makeConfig(LineNumberOptimization.OFF, false, false, runtimeKind), ORIGINAL_LINE_NUMBERS);
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java
index 109a6cb..bd0f8fb 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java
@@ -120,9 +120,9 @@
DexType mainClass = appInfo.dexItemFactory.createType(
DescriptorUtils.javaTypeToDescriptor(NonNullAfterInvoke.class.getCanonicalName()));
Map<Class<? extends Instruction>, TypeLatticeElement> expectedLattices = ImmutableMap.of(
- InvokeVirtual.class, fromDexType(appInfo.dexItemFactory.stringType, appInfo, true),
- NonNull.class, fromDexType(appInfo.dexItemFactory.stringType, appInfo, false),
- NewInstance.class, fromDexType(assertionErrorType, appInfo, false));
+ InvokeVirtual.class, fromDexType(appInfo.dexItemFactory.stringType, true, appInfo),
+ NonNull.class, fromDexType(appInfo.dexItemFactory.stringType, false, appInfo),
+ NewInstance.class, fromDexType(assertionErrorType, false, appInfo));
forEachOutValue(irCode, (v, l) -> verifyClassTypeLattice(expectedLattices, mainClass, v, l));
});
}
@@ -136,9 +136,9 @@
DexType mainClass = appInfo.dexItemFactory.createType(
DescriptorUtils.javaTypeToDescriptor(NonNullAfterInvoke.class.getCanonicalName()));
Map<Class<? extends Instruction>, TypeLatticeElement> expectedLattices = ImmutableMap.of(
- InvokeVirtual.class, fromDexType(appInfo.dexItemFactory.stringType, appInfo, true),
- NonNull.class, fromDexType(appInfo.dexItemFactory.stringType, appInfo, false),
- NewInstance.class, fromDexType(assertionErrorType, appInfo, false));
+ InvokeVirtual.class, fromDexType(appInfo.dexItemFactory.stringType, true, appInfo),
+ NonNull.class, fromDexType(appInfo.dexItemFactory.stringType, false, appInfo),
+ NewInstance.class, fromDexType(assertionErrorType, false, appInfo));
forEachOutValue(irCode, (v, l) -> verifyClassTypeLattice(expectedLattices, mainClass, v, l));
});
}
@@ -153,8 +153,8 @@
DescriptorUtils.javaTypeToDescriptor(NonNullAfterArrayAccess.class.getCanonicalName()));
Map<Class<? extends Instruction>, TypeLatticeElement> expectedLattices = ImmutableMap.of(
// An element inside a non-null array could be null.
- ArrayGet.class, fromDexType(appInfo.dexItemFactory.stringType, appInfo, true),
- NewInstance.class, fromDexType(assertionErrorType, appInfo, false));
+ ArrayGet.class, fromDexType(appInfo.dexItemFactory.stringType, true, appInfo),
+ NewInstance.class, fromDexType(assertionErrorType, false, appInfo));
forEachOutValue(irCode, (v, l) -> {
if (l.isArrayType()) {
ArrayTypeLatticeElement lattice = l.asArrayTypeLatticeElement();
@@ -179,8 +179,8 @@
DescriptorUtils.javaTypeToDescriptor(NonNullAfterArrayAccess.class.getCanonicalName()));
Map<Class<? extends Instruction>, TypeLatticeElement> expectedLattices = ImmutableMap.of(
// An element inside a non-null array could be null.
- ArrayGet.class, fromDexType(appInfo.dexItemFactory.stringType, appInfo, true),
- NewInstance.class, fromDexType(assertionErrorType, appInfo, false));
+ ArrayGet.class, fromDexType(appInfo.dexItemFactory.stringType, true, appInfo),
+ NewInstance.class, fromDexType(assertionErrorType, false, appInfo));
forEachOutValue(irCode, (v, l) -> {
if (l.isArrayType()) {
ArrayTypeLatticeElement lattice = l.asArrayTypeLatticeElement();
@@ -206,11 +206,11 @@
DexType testClass = appInfo.dexItemFactory.createType(
DescriptorUtils.javaTypeToDescriptor(FieldAccessTest.class.getCanonicalName()));
Map<Class<? extends Instruction>, TypeLatticeElement> expectedLattices = ImmutableMap.of(
- Argument.class, fromDexType(testClass, appInfo, true),
- NonNull.class, fromDexType(testClass, appInfo, false),
+ Argument.class, fromDexType(testClass, true, appInfo),
+ NonNull.class, fromDexType(testClass, false, appInfo),
// instance may not be initialized.
- InstanceGet.class, fromDexType(appInfo.dexItemFactory.stringType, appInfo, true),
- NewInstance.class, fromDexType(assertionErrorType, appInfo, false));
+ InstanceGet.class, fromDexType(appInfo.dexItemFactory.stringType, true, appInfo),
+ NewInstance.class, fromDexType(assertionErrorType, false, appInfo));
forEachOutValue(irCode, (v, l) -> verifyClassTypeLattice(expectedLattices, mainClass, v, l));
});
}
@@ -226,11 +226,11 @@
DexType testClass = appInfo.dexItemFactory.createType(
DescriptorUtils.javaTypeToDescriptor(FieldAccessTest.class.getCanonicalName()));
Map<Class<? extends Instruction>, TypeLatticeElement> expectedLattices = ImmutableMap.of(
- Argument.class, fromDexType(testClass, appInfo, true),
- NonNull.class, fromDexType(testClass, appInfo, false),
+ Argument.class, fromDexType(testClass, true, appInfo),
+ NonNull.class, fromDexType(testClass, false, appInfo),
// instance may not be initialized.
- InstanceGet.class, fromDexType(appInfo.dexItemFactory.stringType, appInfo, true),
- NewInstance.class, fromDexType(assertionErrorType, appInfo, false));
+ InstanceGet.class, fromDexType(appInfo.dexItemFactory.stringType, true, appInfo),
+ NewInstance.class, fromDexType(assertionErrorType, false, appInfo));
forEachOutValue(irCode, (v, l) -> verifyClassTypeLattice(expectedLattices, mainClass, v, l));
});
}
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
index 8d956a8..08a220f 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
@@ -75,7 +75,7 @@
}
private TypeLatticeElement element(DexType type) {
- return TypeLatticeElement.fromDexType(type, appInfo, true);
+ return TypeLatticeElement.fromDexType(type, true, appInfo);
}
private ArrayTypeLatticeElement array(int nesting, DexType base) {
@@ -84,15 +84,15 @@
private TypeLatticeElement join(TypeLatticeElement... elements) {
assertTrue(elements.length > 1);
- return TypeLatticeElement.join(appInfo, Arrays.stream(elements));
+ return TypeLatticeElement.join(Arrays.stream(elements), appInfo);
}
private boolean strictlyLessThan(TypeLatticeElement l1, TypeLatticeElement l2) {
- return TypeLatticeElement.strictlyLessThan(appInfo, l1, l2);
+ return l1.strictlyLessThan(l2, appInfo);
}
private boolean lessThanOrEqual(TypeLatticeElement l1, TypeLatticeElement l2) {
- return TypeLatticeElement.lessThanOrEqual(appInfo, l1, l2);
+ return l1.lessThanOrEqual(l2, appInfo);
}
@Test
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
index 564a81c..e569ad2 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
@@ -69,6 +69,8 @@
|| NonNullTracker.throwsOnNullInput(prev)
|| (prev.isIf() && prev.asIf().isZeroTest())
|| !curr.getBlock().getPredecessors().contains(prev.getBlock()));
+ // Make sure non-null is used.
+ assertTrue(curr.outValue().numberOfAllUsers() > 0);
count++;
}
}
@@ -114,7 +116,7 @@
buildAndTest(NonNullAfterInvoke.class, foo, 1, this::checkInvokeGetsNonNullReceiver);
MethodSignature bar =
new MethodSignature("bar", "int", new String[]{"java.lang.String"});
- buildAndTest(NonNullAfterInvoke.class, bar, 2, this::checkInvokeGetsNullReceiver);
+ buildAndTest(NonNullAfterInvoke.class, bar, 1, this::checkInvokeGetsNullReceiver);
}
@Test
@@ -176,6 +178,6 @@
buildAndTest(NonNullAfterNullCheck.class, bar, 1, this::checkInvokeGetsNonNullReceiver);
MethodSignature baz =
new MethodSignature("baz", "int", new String[]{"java.lang.String"});
- buildAndTest(NonNullAfterNullCheck.class, baz, 2, this::checkInvokeGetsNullReceiver);
+ buildAndTest(NonNullAfterNullCheck.class, baz, 1, this::checkInvokeGetsNullReceiver);
}
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CheckCastDebugTestRunner.java b/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CheckCastDebugTestRunner.java
index d7a5732..1eddbd3 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CheckCastDebugTestRunner.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CheckCastDebugTestRunner.java
@@ -29,7 +29,6 @@
import java.util.Arrays;
import java.util.Collection;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -77,7 +76,6 @@
assertThat(classSubject, isPresent());
}
- @Ignore("todo: jsjeon")
@Test
public void test_differentLocals() throws Throwable {
ClassSubject classSubject = inspector.clazz(MAIN);
@@ -131,7 +129,6 @@
);
}
- @Ignore("todo: jsjeon")
@Test
public void test_sameLocal() throws Throwable {
ClassSubject classSubject = inspector.clazz(MAIN);
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/IndirectSuperInterfaceTest.java b/src/test/java/com/android/tools/r8/memberrebinding/IndirectSuperInterfaceTest.java
index b280242..977b925 100644
--- a/src/test/java/com/android/tools/r8/memberrebinding/IndirectSuperInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/memberrebinding/IndirectSuperInterfaceTest.java
@@ -3,19 +3,11 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.memberrebinding;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
import com.android.tools.r8.NeverInline;
-import com.android.tools.r8.R8;
-import com.android.tools.r8.R8Command;
-import com.android.tools.r8.R8Command.Builder;
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.AndroidAppConsumers;
+import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.ImmutableList;
+import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -24,26 +16,115 @@
@RunWith(Parameterized.class)
public class IndirectSuperInterfaceTest extends TestBase {
- public interface Interface {
+ private static final List<Class<?>> CLASSES =
+ ImmutableList.of(
+ InterfaceA.class, InterfaceASub.class, A.class,
+ InterfaceB.class, InterfaceBSub.class, B.class,
+ InterfaceC.class, InterfaceCSub.class, C.class,
+ InterfaceD.class, InterfaceDSub.class, D.class,
+ TestClass.class);
+
+ // Test A: class A extends an empty class that implements a non-empty interface.
+
+ interface InterfaceA {
@NeverInline
- default void foo() {
- System.out.print("Interface::foo ");
+ default String method() {
+ return "InterfaceA::method";
}
}
- public static class A implements Interface {
+ static class InterfaceASub implements InterfaceA {
// Intentionally empty.
}
- public static class B extends A {
+ static class A extends InterfaceASub {
@Override
- public void foo() {
- System.out.print("B::foo ");
- super.foo();
+ public String method() {
+ return "A::method -> " + super.method();
}
+ }
+
+ // Test B: class B implements an empty interface that extends a non-empty interface.
+
+ interface InterfaceB {
+ @NeverInline
+ default String method() {
+ return "InterfaceB::method";
+ }
+ }
+
+ interface InterfaceBSub extends InterfaceB {
+ // Intentionally empty.
+ }
+
+ static class B implements InterfaceBSub {
+ @Override
+ public String method() {
+ return "B::method -> " + InterfaceBSub.super.method();
+ }
+ }
+
+ // Test C: class C extends a non-empty class that implements a non-empty interface.
+
+ interface InterfaceC {
+ @NeverInline
+ default String method() {
+ return "InterfaceC::method";
+ }
+ }
+
+ static class InterfaceCSub implements InterfaceC {
+ // This method is intentionally not annotated with @NeverInline. If we were to inline this
+ // method we would risk introducing a super-invocation to InterfaceC.method() in C.method,
+ // which would lead to an IncompatibleClassChangeError on the JVM.
+ // (See also Art978_virtual_interfaceTest.)
+ @Override
+ public String method() {
+ return InterfaceC.super.method();
+ }
+ }
+
+ static class C extends InterfaceCSub {
+ @Override
+ public String method() {
+ return "C::method -> " + super.method();
+ }
+ }
+
+ // Test D: class D implements a non-empty empty interface that extends a non-empty interface.
+
+ interface InterfaceD {
+ @NeverInline
+ default String method() {
+ return "InterfaceD::method";
+ }
+ }
+
+ interface InterfaceDSub extends InterfaceD {
+ // This method is intentionally not annotated with @NeverInline. If we were to inline this
+ // method we would risk introducing a super-invocation to InterfaceC.method() in C.method,
+ // which would lead to an IncompatibleClassChangeError on the JVM.
+ // (See also Art978_virtual_interfaceTest.)
+ @Override
+ default String method() {
+ return InterfaceD.super.method();
+ }
+ }
+
+ static class D implements InterfaceDSub {
+ @Override
+ public String method() {
+ return "D::method -> " + InterfaceDSub.super.method();
+ }
+ }
+
+ static class TestClass {
public static void main(String[] args) {
- new B().foo();
+ System.out.println(new A().method());
+ System.out.println(new B().method());
+ System.out.println(new C().method());
+ System.out.print(new D().method());
}
}
@@ -51,7 +132,7 @@
@Parameters(name = "{0}")
public static Backend[] setup() {
- return new Backend[] {Backend.CF, Backend.DEX};
+ return Backend.values();
}
public IndirectSuperInterfaceTest(Backend backend) {
@@ -60,36 +141,23 @@
@Test
public void test() throws Exception {
- String expected = "B::foo Interface::foo ";
- String reference = runOnJava(B.class);
- assertEquals(expected, reference);
+ String expected = StringUtils.joinLines(
+ "A::method -> InterfaceA::method",
+ "B::method -> InterfaceB::method",
+ "C::method -> InterfaceC::method",
+ "D::method -> InterfaceD::method");
- AndroidAppConsumers sink = new AndroidAppConsumers();
- Builder builder =
- R8Command.builder()
- .addClassProgramData(ToolHelper.getClassAsBytes(Interface.class), Origin.unknown())
- .addClassProgramData(ToolHelper.getClassAsBytes(A.class), Origin.unknown())
- .addClassProgramData(ToolHelper.getClassAsBytes(B.class), Origin.unknown())
- .setProgramConsumer(sink.wrapProgramConsumer(emptyConsumer(backend)))
- .addLibraryFiles(runtimeJar(backend))
- .addProguardConfiguration(
- ImmutableList.of(
- "-keep class " + Interface.class.getTypeName(),
- "-keep class " + A.class.getTypeName(),
- keepMainProguardConfigurationWithInliningAnnotation(B.class)),
- Origin.unknown());
- ToolHelper.allowTestProguardOptions(builder);
- if (backend == Backend.DEX) {
- builder.setMinApiLevel(ToolHelper.getMinApiLevelForDexVm().getLevel());
- }
- R8.run(builder.build());
+ testForJvm()
+ .addTestClasspath()
+ .run(TestClass.class)
+ .assertSuccessWithOutput(expected);
- ProcessResult result = runOnVMRaw(sink.build(), B.class, backend);
-
- // TODO(b/117407667): Assert the test does not fail once fixed.
- assertTrue(result.toString(), result.exitCode == (backend == Backend.DEX ? 0 : 1));
- if (result.exitCode == 0) {
- assertEquals(reference, result.stdout);
- }
+ testForR8(backend)
+ .addProgramClasses(CLASSES)
+ .addKeepPackageRules(TestClass.class.getPackage())
+ .addKeepMainRule(TestClass.class)
+ .enableInliningAnnotations()
+ .run(TestClass.class)
+ .assertSuccessWithOutput(expected);
}
}
diff --git a/src/test/java/com/android/tools/r8/naming/EnumMinification.java b/src/test/java/com/android/tools/r8/naming/EnumMinification.java
index a8708eb..66cd45e 100644
--- a/src/test/java/com/android/tools/r8/naming/EnumMinification.java
+++ b/src/test/java/com/android/tools/r8/naming/EnumMinification.java
@@ -4,17 +4,19 @@
package com.android.tools.r8.naming;
-import static org.hamcrest.CoreMatchers.containsString;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import com.android.tools.r8.R8Command;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.DexVm;
-import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import java.util.Arrays;
import java.util.Collection;
@@ -22,6 +24,12 @@
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
@RunWith(Parameterized.class)
public class EnumMinification extends TestBase {
@@ -37,29 +45,57 @@
this.backend = backend;
}
+ private AndroidApp buildApp(Class<?> mainClass, byte[] enumClassFile) throws Exception {
+ return ToolHelper.runR8(
+ R8Command.builder()
+ .addClassProgramData(ToolHelper.getClassAsBytes(mainClass), Origin.unknown())
+ .addClassProgramData(enumClassFile, Origin.unknown())
+ .addProguardConfiguration(
+ ImmutableList.of(keepMainProguardConfiguration(mainClass)), Origin.unknown())
+ .setProgramConsumer(emptyConsumer(backend))
+ .build());
+ }
+
+ public void runTest(
+ Class<?> mainClass, byte[] enumClass, String enumTypeName, boolean valueOfKept)
+ throws Exception {
+ AndroidApp output = buildApp(mainClass, enumClass);
+
+ CodeInspector inspector = new CodeInspector(output);
+ ClassSubject clazz = inspector.clazz(enumTypeName);
+ // The class and fields - including field $VALUES and method valueOf - can be renamed. Only
+ // the values() method needs to be
+ assertThat(clazz, isRenamed());
+ assertThat(clazz.field(enumTypeName, "VALUE1"), isRenamed());
+ assertThat(clazz.field(enumTypeName, "VALUE2"), isRenamed());
+ assertThat(clazz.field(enumTypeName + "[]", "$VALUES"), isRenamed());
+ assertThat(
+ clazz.method(enumTypeName, "valueOf", ImmutableList.of("java.lang.String")),
+ valueOfKept ? isRenamed() : not(isPresent()));
+ assertThat(clazz.method(enumTypeName + "[]", "values", ImmutableList.of()), not(isRenamed()));
+
+ assertEquals("VALUE1", runOnVM(output, mainClass, backend));
+ }
+
@Test
public void test() throws Exception {
- AndroidApp output =
- ToolHelper.runR8(
- R8Command.builder()
- .addClassProgramData(ToolHelper.getClassAsBytes(Main.class), Origin.unknown())
- .addClassProgramData(ToolHelper.getClassAsBytes(Enum.class), Origin.unknown())
- .addProguardConfiguration(
- ImmutableList.of(keepMainProguardConfiguration(Main.class)), Origin.unknown())
- .setProgramConsumer(emptyConsumer(backend))
- .build());
+ runTest(Main.class, ToolHelper.getClassAsBytes(Enum.class), Enum.class.getTypeName(), true);
+ }
- // TODO(117299356): valueOf on enum fails for minified enums.
- ProcessResult result = runOnVMRaw(output, Main.class, backend);
- assertEquals(1, result.exitCode);
- assertThat(
- result.stderr,
- containsString(
- backend == Backend.DEX
- ? ToolHelper.getDexVm().isNewerThan(DexVm.ART_4_4_4_HOST)
- ? "java.lang.NoSuchMethodException"
- : "java.lang.NullPointerException"
- : "java.lang.IllegalArgumentException"));
+ @Test
+ public void testAsmDump() throws Exception {
+ runTest(Main.class, EnumDump.dump(true), "com.android.tools.r8.naming.Enum", true);
+ }
+
+ @Test
+ public void testWithoutValuesMethod() throws Exception {
+ // This should not fail even if the values method is not present.
+ buildApp(Main.class, EnumDump.dump(false));
+ }
+
+ @Test
+ public void testJavaLangEnumValueOf() throws Exception {
+ runTest(Main2.class, ToolHelper.getClassAsBytes(Enum.class), Enum.class.getTypeName(), false);
}
}
@@ -67,7 +103,7 @@
public static void main(String[] args) {
Enum e = Enum.valueOf("VALUE1");
- System.out.println(e);
+ System.out.print(e);
}
}
@@ -75,3 +111,219 @@
VALUE1,
VALUE2
}
+
+class Main2 {
+ public static void main(String[] args) {
+ // Use java.lang.Enum.valueOf instead of com.android.tools.r8.naming.Enum.valueOf.
+ System.out.print(java.lang.Enum.valueOf(Enum.class, "VALUE1"));
+ }
+}
+/* Dump of javac generated code from the following enum class (the one just above):
+ *
+ * package com.android.tools.r8.naming;
+ *
+ * enum Enum {
+ * VALUE1,
+ * VALUE2
+ * }
+ *
+ */
+class EnumDump implements Opcodes {
+
+ public static byte[] dump(boolean includeValuesMethod) {
+ ClassWriter classWriter = new ClassWriter(0);
+ FieldVisitor fieldVisitor;
+ MethodVisitor methodVisitor;
+
+ classWriter.visit(
+ V1_8,
+ ACC_FINAL | ACC_SUPER | ACC_ENUM,
+ "com/android/tools/r8/naming/Enum",
+ "Ljava/lang/Enum<Lcom/android/tools/r8/naming/Enum;>;",
+ "java/lang/Enum",
+ null);
+
+ classWriter.visitSource("EnumMinification.java", null);
+
+ {
+ fieldVisitor =
+ classWriter.visitField(
+ ACC_PUBLIC | ACC_FINAL | ACC_STATIC | ACC_ENUM,
+ "VALUE1",
+ "Lcom/android/tools/r8/naming/Enum;",
+ null,
+ null);
+ fieldVisitor.visitEnd();
+ }
+ {
+ fieldVisitor =
+ classWriter.visitField(
+ ACC_PUBLIC | ACC_FINAL | ACC_STATIC | ACC_ENUM,
+ "VALUE2",
+ "Lcom/android/tools/r8/naming/Enum;",
+ null,
+ null);
+ fieldVisitor.visitEnd();
+ }
+ {
+ fieldVisitor =
+ classWriter.visitField(
+ ACC_PRIVATE | ACC_FINAL | ACC_STATIC | ACC_SYNTHETIC,
+ "$VALUES",
+ "[Lcom/android/tools/r8/naming/Enum;",
+ null,
+ null);
+ fieldVisitor.visitEnd();
+ }
+ if (includeValuesMethod) {
+ {
+ methodVisitor =
+ classWriter.visitMethod(
+ ACC_PUBLIC | ACC_STATIC,
+ "values",
+ "()[Lcom/android/tools/r8/naming/Enum;",
+ null,
+ null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(72, label0);
+ methodVisitor.visitFieldInsn(
+ GETSTATIC,
+ "com/android/tools/r8/naming/Enum",
+ "$VALUES",
+ "[Lcom/android/tools/r8/naming/Enum;");
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL,
+ "[Lcom/android/tools/r8/naming/Enum;",
+ "clone",
+ "()Ljava/lang/Object;",
+ false);
+ methodVisitor.visitTypeInsn(CHECKCAST, "[Lcom/android/tools/r8/naming/Enum;");
+ methodVisitor.visitInsn(ARETURN);
+ methodVisitor.visitMaxs(1, 0);
+ methodVisitor.visitEnd();
+ }
+ }
+ {
+ methodVisitor =
+ classWriter.visitMethod(
+ ACC_PUBLIC | ACC_STATIC,
+ "valueOf",
+ "(Ljava/lang/String;)Lcom/android/tools/r8/naming/Enum;",
+ null,
+ null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(72, label0);
+ methodVisitor.visitLdcInsn(Type.getType("Lcom/android/tools/r8/naming/Enum;"));
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(
+ INVOKESTATIC,
+ "java/lang/Enum",
+ "valueOf",
+ "(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;",
+ false);
+ methodVisitor.visitTypeInsn(CHECKCAST, "com/android/tools/r8/naming/Enum");
+ methodVisitor.visitInsn(ARETURN);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLocalVariable("name", "Ljava/lang/String;", null, label0, label1, 0);
+ methodVisitor.visitMaxs(2, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor =
+ classWriter.visitMethod(ACC_PRIVATE, "<init>", "(Ljava/lang/String;I)V", "()V", null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(72, label0);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitVarInsn(ALOAD, 1);
+ methodVisitor.visitVarInsn(ILOAD, 2);
+ methodVisitor.visitMethodInsn(
+ INVOKESPECIAL, "java/lang/Enum", "<init>", "(Ljava/lang/String;I)V", false);
+ methodVisitor.visitInsn(RETURN);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLocalVariable(
+ "this", "Lcom/android/tools/r8/naming/Enum;", null, label0, label1, 0);
+ methodVisitor.visitMaxs(3, 3);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor = classWriter.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(73, label0);
+ methodVisitor.visitTypeInsn(NEW, "com/android/tools/r8/naming/Enum");
+ methodVisitor.visitInsn(DUP);
+ methodVisitor.visitLdcInsn("VALUE1");
+ methodVisitor.visitInsn(ICONST_0);
+ methodVisitor.visitMethodInsn(
+ INVOKESPECIAL,
+ "com/android/tools/r8/naming/Enum",
+ "<init>",
+ "(Ljava/lang/String;I)V",
+ false);
+ methodVisitor.visitFieldInsn(
+ PUTSTATIC,
+ "com/android/tools/r8/naming/Enum",
+ "VALUE1",
+ "Lcom/android/tools/r8/naming/Enum;");
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(74, label1);
+ methodVisitor.visitTypeInsn(NEW, "com/android/tools/r8/naming/Enum");
+ methodVisitor.visitInsn(DUP);
+ methodVisitor.visitLdcInsn("VALUE2");
+ methodVisitor.visitInsn(ICONST_1);
+ methodVisitor.visitMethodInsn(
+ INVOKESPECIAL,
+ "com/android/tools/r8/naming/Enum",
+ "<init>",
+ "(Ljava/lang/String;I)V",
+ false);
+ methodVisitor.visitFieldInsn(
+ PUTSTATIC,
+ "com/android/tools/r8/naming/Enum",
+ "VALUE2",
+ "Lcom/android/tools/r8/naming/Enum;");
+ Label label2 = new Label();
+ methodVisitor.visitLabel(label2);
+ methodVisitor.visitLineNumber(72, label2);
+ methodVisitor.visitInsn(ICONST_2);
+ methodVisitor.visitTypeInsn(ANEWARRAY, "com/android/tools/r8/naming/Enum");
+ methodVisitor.visitInsn(DUP);
+ methodVisitor.visitInsn(ICONST_0);
+ methodVisitor.visitFieldInsn(
+ GETSTATIC,
+ "com/android/tools/r8/naming/Enum",
+ "VALUE1",
+ "Lcom/android/tools/r8/naming/Enum;");
+ methodVisitor.visitInsn(AASTORE);
+ methodVisitor.visitInsn(DUP);
+ methodVisitor.visitInsn(ICONST_1);
+ methodVisitor.visitFieldInsn(
+ GETSTATIC,
+ "com/android/tools/r8/naming/Enum",
+ "VALUE2",
+ "Lcom/android/tools/r8/naming/Enum;");
+ methodVisitor.visitInsn(AASTORE);
+ methodVisitor.visitFieldInsn(
+ PUTSTATIC,
+ "com/android/tools/r8/naming/Enum",
+ "$VALUES",
+ "[Lcom/android/tools/r8/naming/Enum;");
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(4, 0);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/MemberResolutionAsmTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/MemberResolutionAsmTest.java
new file mode 100644
index 0000000..102a6a3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/MemberResolutionAsmTest.java
@@ -0,0 +1,230 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.naming.applymapping;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.AsmTestBase;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MemberResolutionAsmTest extends AsmTestBase {
+ private final Backend backend;
+
+ @Parameterized.Parameters(name = "backend: {0}")
+ public static Collection<Backend> data() {
+ return Arrays.asList(Backend.values());
+ }
+
+ public MemberResolutionAsmTest(Backend backend) {
+ this.backend = backend;
+ }
+
+ // class HasMapping { // : X
+ // HasMapping() {
+ // foo();
+ // }
+ //
+ // void foo() { // : a
+ // System.out.println("HasMapping#foo");
+ // }
+ // }
+ //
+ // class NoMapping extends HasMapping { // : Y
+ // NoMapping() {
+ // super();
+ // foo();
+ // }
+ //
+ // private void foo() { // no mapping
+ // System.out.println("NoMapping#foo");
+ // }
+ // }
+ //
+ // class NoMappingMain {
+ // public static void main(String[] args) {
+ // new NoMapping();
+ // }
+ // }
+ @Test
+ public void test_noMapping() throws Exception {
+ String main = "NoMappingMain";
+ AndroidApp input = buildAndroidApp(
+ HasMappingDump.dump(), NoMappingDump.dump(), NoMappingMainDump.dump());
+
+ Path mapPath = temp.newFile("test-mapping.txt").toPath();
+ List<String> pgMap = ImmutableList.of(
+ "HasMapping -> X:",
+ " void foo() -> a",
+ "NoMapping -> Y:"
+ // Intentionally missing a mapping for `private` foo().
+ );
+ FileUtils.writeTextFile(mapPath, pgMap);
+
+ R8Command.Builder builder = ToolHelper.prepareR8CommandBuilder(input, emptyConsumer(backend));
+ builder
+ .addProguardConfiguration(
+ ImmutableList.of(
+ keepMainProguardConfiguration(main),
+ // Do not turn on -allowaccessmodification
+ "-applymapping " + mapPath,
+ "-dontobfuscate"), // to use the renamed names in test-mapping.txt
+ Origin.unknown())
+ .addLibraryFiles(runtimeJar(backend));
+ AndroidApp processedApp =
+ ToolHelper.runR8(
+ builder.build(),
+ options -> {
+ options.enableInlining = false;
+ options.enableVerticalClassMerging = false;
+ });
+
+ List<byte[]> classBytes = ImmutableList.of(
+ HasMappingDump.dump(), NoMappingDump.dump(), NoMappingMainDump.dump());
+ ProcessResult outputBefore = runOnJavaRaw(main, classBytes, ImmutableList.of());
+ assertEquals(0, outputBefore.exitCode);
+ String outputAfter = runOnVM(processedApp, main, backend);
+ assertEquals(outputBefore.stdout.trim(), outputAfter.trim());
+
+ CodeInspector codeInspector = new CodeInspector(processedApp, mapPath);
+ ClassSubject base = codeInspector.clazz("HasMapping");
+ assertThat(base, isPresent());
+ assertThat(base, isRenamed());
+ assertEquals("X", base.getFinalName());
+ MethodSubject x = base.method("void", "foo", ImmutableList.of());
+ assertThat(x, isPresent());
+ assertThat(x, isRenamed());
+ assertEquals("a", x.getFinalName());
+
+ ClassSubject sub = codeInspector.clazz("NoMapping");
+ assertThat(sub, isPresent());
+ assertThat(sub, isRenamed());
+ assertEquals("Y", sub.getFinalName());
+ MethodSubject y = sub.method("void", "foo", ImmutableList.of());
+ assertThat(y, isPresent());
+ assertThat(y, not(isRenamed()));
+ assertEquals("foo", y.getFinalName());
+ }
+
+ // class A { // : X
+ // A() {
+ // x();
+ // y();
+ // }
+ //
+ // private void x() { // : y
+ // System.out.println("A#x");
+ // }
+ //
+ // public void y() { // : x
+ // System.out.println("A#y");
+ // }
+ // }
+ //
+ // class B extends A { // : Y
+ // }
+ //
+ // class Main {
+ // public static void main(String[] args) {
+ // new B().x(); // IllegalAccessError
+ // }
+ // }
+ @Test
+ public void test_swapping() throws Exception {
+ String main = "Main";
+ AndroidApp input = buildAndroidApp(
+ ADump.dump(), BDump.dump(), MainDump.dump());
+
+ Path mapPath = temp.newFile("test-mapping.txt").toPath();
+ List<String> pgMap = ImmutableList.of(
+ "A -> X:",
+ " void x() -> y",
+ " void y() -> x",
+ "B -> Y:"
+ // Intentionally missing mappings for non-overridden members
+ );
+ FileUtils.writeTextFile(mapPath, pgMap);
+
+ R8Command.Builder builder = ToolHelper.prepareR8CommandBuilder(input, emptyConsumer(backend));
+ builder
+ .addProguardConfiguration(
+ ImmutableList.of(
+ keepMainProguardConfiguration(main),
+ // Do not turn on -allowaccessmodification
+ "-applymapping " + mapPath,
+ "-dontobfuscate"), // to use the renamed names in test-mapping.txt
+ Origin.unknown())
+ .addLibraryFiles(runtimeJar(backend));
+ AndroidApp processedApp =
+ ToolHelper.runR8(
+ builder.build(),
+ options -> {
+ options.enableInlining = false;
+ options.enableVerticalClassMerging = false;
+ });
+
+ List<byte[]> classBytes = ImmutableList.of(ADump.dump(), BDump.dump(), MainDump.dump());
+ ProcessResult outputBefore = runOnJavaRaw(main, classBytes, ImmutableList.of());
+ assertNotEquals(0, outputBefore.exitCode);
+ String expectedErrorMessage = "IllegalAccessError";
+ String expectedErrorSignature = "A.x()V";
+ assertThat(outputBefore.stderr, containsString(expectedErrorMessage));
+ assertThat(outputBefore.stderr, containsString(expectedErrorSignature));
+ ProcessResult outputAfter = runOnVMRaw(processedApp, main, backend);
+ assertNotEquals(0, outputAfter.exitCode);
+ expectedErrorSignature = "X.y()V";
+ if (backend == Backend.DEX) {
+ expectedErrorSignature = "void X.y()";
+ if (ToolHelper.getDexVm().getVersion().isOlderThanOrEqual(Version.V6_0_1)) {
+ expectedErrorMessage ="IncompatibleClassChangeError";
+ }
+ if (ToolHelper.getDexVm().getVersion().isOlderThanOrEqual(Version.V4_4_4)) {
+ expectedErrorMessage ="illegal method access";
+ expectedErrorSignature = "LX;.y ()V";
+ }
+ }
+ assertThat(outputAfter.stderr, containsString(expectedErrorMessage));
+ assertThat(outputAfter.stderr, containsString(expectedErrorSignature));
+
+ CodeInspector codeInspector = new CodeInspector(processedApp, mapPath);
+ ClassSubject base = codeInspector.clazz("A");
+ assertThat(base, isPresent());
+ assertThat(base, isRenamed());
+ assertEquals("X", base.getFinalName());
+ MethodSubject x = base.method("void", "x", ImmutableList.of());
+ assertThat(x, isPresent());
+ assertThat(x, isRenamed());
+ assertEquals("y", x.getFinalName());
+
+ ClassSubject sub = codeInspector.clazz("B");
+ assertThat(sub, isPresent());
+ assertThat(sub, isRenamed());
+ assertEquals("Y", sub.getFinalName());
+ MethodSubject subX = sub.method("void", "x", ImmutableList.of());
+ assertThat(subX, not(isPresent()));
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/MemberResolutionTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/MemberResolutionTest.java
new file mode 100644
index 0000000..61e51be
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/MemberResolutionTest.java
@@ -0,0 +1,155 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.naming.applymapping;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+// AbstractChecker -> X:
+abstract class AbstractChecker {
+ // String tag -> p
+ private String tag = "PrivateInitialTag_AbstractChecker";
+
+ // check() -> x
+ private void check() {
+ System.out.println("AbstractChecker#check:" + tag);
+ }
+
+ // foo() -> a
+ protected void foo() {
+ check();
+ }
+}
+
+// ConcreteChecker -> Y:
+class ConcreteChecker extends AbstractChecker {
+ // This should not be conflict with AbstractChecker#tag due to the access control.
+ // String tag -> q
+ private String tag = "PrivateInitialTag_ConcreteChecker";
+
+ ConcreteChecker(String tag){
+ this.tag = tag;
+ }
+
+ // This should not be conflict with AbstractChecker#check due to the access control.
+ // check() -> y
+ private void check() {
+ System.out.println("ConcreteChecker#check:" + tag);
+ }
+
+ // foo() -> a
+ @Override
+ protected void foo() {
+ super.foo();
+ check();
+ }
+}
+
+class MemberResolutionTestMain {
+ public static void main(String[] args) {
+ ConcreteChecker c = new ConcreteChecker("NewTag");
+ c.foo();
+ }
+}
+
+@RunWith(Parameterized.class)
+public class MemberResolutionTest extends TestBase {
+ private final static List<Class> CLASSES = ImmutableList.of(
+ AbstractChecker.class, ConcreteChecker.class, MemberResolutionTestMain.class);
+
+ private Backend backend;
+
+ @Parameterized.Parameters(name = "Backend: {0}")
+ public static Collection<Backend> data() {
+ return Arrays.asList(Backend.values());
+ }
+
+ public MemberResolutionTest(Backend backend) {
+ this.backend = backend;
+ }
+
+ @Test
+ public void testPrivateMethodsWithSameName() throws Exception {
+ String pkg = this.getClass().getPackage().getName();
+ Path mapPath = temp.newFile("test-mapping.txt").toPath();
+ List<String> pgMap = ImmutableList.of(
+ pkg + ".AbstractChecker -> " + pkg + ".X:",
+ " java.lang.String tag -> p",
+ " void check() -> x",
+ " void foo() -> a",
+ pkg + ".ConcreteChecker -> " + pkg + ".Y:",
+ " java.lang.String tag -> q",
+ " void check() -> y",
+ " void foo() -> a"
+ );
+ FileUtils.writeTextFile(mapPath, pgMap);
+
+ AndroidApp app = readClasses(CLASSES);
+ R8Command.Builder builder = ToolHelper.prepareR8CommandBuilder(app, emptyConsumer(backend));
+ builder
+ .addProguardConfiguration(
+ ImmutableList.of(
+ keepMainProguardConfiguration(MemberResolutionTestMain.class),
+ // Do not turn on -allowaccessmodification
+ "-applymapping " + mapPath,
+ "-dontobfuscate"), // to use the renamed names in test-mapping.txt
+ Origin.unknown())
+ .addLibraryFiles(runtimeJar(backend));
+ AndroidApp processedApp =
+ ToolHelper.runR8(
+ builder.build(),
+ options -> {
+ options.enableInlining = false;
+ options.enableVerticalClassMerging = false;
+ });
+
+ String outputBefore = runOnJava(MemberResolutionTestMain.class);
+ String outputAfter = runOnVM(processedApp, MemberResolutionTestMain.class, backend);
+ assertEquals(outputBefore, outputAfter);
+
+ CodeInspector codeInspector = new CodeInspector(processedApp, mapPath);
+ ClassSubject base = codeInspector.clazz(AbstractChecker.class);
+ assertThat(base, isPresent());
+ FieldSubject p = base.field("java.lang.String", "tag");
+ assertThat(p, isPresent());
+ assertThat(p, isRenamed());
+ assertEquals("p", p.getFinalName());
+ MethodSubject x = base.method("void", "check", ImmutableList.of());
+ assertThat(x, isPresent());
+ assertThat(x, isRenamed());
+ assertEquals("x", x.getFinalName());
+
+ ClassSubject sub = codeInspector.clazz(ConcreteChecker.class);
+ assertThat(sub, isPresent());
+ FieldSubject q = sub.field("java.lang.String", "tag");
+ assertThat(q, isPresent());
+ assertThat(q, isRenamed());
+ assertEquals("q", q.getFinalName());
+ MethodSubject y = sub.method("void", "check", ImmutableList.of());
+ assertThat(y, isPresent());
+ assertThat(y, isRenamed());
+ assertEquals("y", y.getFinalName());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/NoMappingDump.java b/src/test/java/com/android/tools/r8/naming/applymapping/NoMappingDump.java
new file mode 100644
index 0000000..ae46c2a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/NoMappingDump.java
@@ -0,0 +1,197 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.naming.applymapping;
+
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+// Generated by running tools/asmifier.py on the following code snippet:
+//
+// class HasMapping {
+// HasMapping() {
+// foo();
+// }
+//
+// void foo() {
+// System.out.println("HasMapping#foo");
+// }
+// }
+class HasMappingDump implements Opcodes {
+
+ public static byte[] dump () throws Exception {
+
+ ClassWriter classWriter = new ClassWriter(0);
+ MethodVisitor methodVisitor;
+
+ classWriter.visit(V1_8, ACC_SUPER, "HasMapping", null, "java/lang/Object", null);
+
+ classWriter.visitSource("HasMapping.java", null);
+
+ {
+ methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(2, label0);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(3, label1);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "HasMapping", "foo", "()V", false);
+ Label label2 = new Label();
+ methodVisitor.visitLabel(label2);
+ methodVisitor.visitLineNumber(4, label2);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(1, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor = classWriter.visitMethod(0, "foo", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(3, label0);
+ methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ methodVisitor.visitLdcInsn("HasMapping#foo");
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(4, label1);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 1);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
+}
+
+// Generated by running tools/asmifier.py on the following code snippet:
+//
+// class NoMapping extends HasMapping {
+// NoMapping() {
+// super();
+// bar();
+// }
+//
+// private void bar() {
+// System.out.println("NoMapping#foo");
+// }
+// }
+//
+// then renamed bar() to foo() to introduce name clash.
+class NoMappingDump implements Opcodes {
+
+ public static byte[] dump () throws Exception {
+
+ ClassWriter classWriter = new ClassWriter(0);
+ MethodVisitor methodVisitor;
+
+ classWriter.visit(V1_8, ACC_SUPER, "NoMapping", null, "HasMapping", null);
+
+ classWriter.visitSource("NoMapping.java", null);
+
+ {
+ methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(12, label0);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "HasMapping", "<init>", "()V", false);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(13, label1);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "NoMapping", "foo", "()V", false);
+ Label label2 = new Label();
+ methodVisitor.visitLabel(label2);
+ methodVisitor.visitLineNumber(14, label2);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(1, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor = classWriter.visitMethod(ACC_PRIVATE, "foo", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(9, label0);
+ methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ methodVisitor.visitLdcInsn("NoMapping#foo");
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(10, label1);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 1);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
+}
+
+// Generated by running tools/asmifier.py on the following code snippet:
+//
+// public class NoMappingMain {
+// public static void main(String[] args) {
+// new NoMapping();
+// }
+// }
+class NoMappingMainDump implements Opcodes {
+
+ public static byte[] dump () throws Exception {
+
+ ClassWriter classWriter = new ClassWriter(0);
+ MethodVisitor methodVisitor;
+
+ classWriter.visit(
+ V1_8, ACC_PUBLIC | ACC_SUPER, "NoMappingMain", null, "java/lang/Object", null);
+
+ classWriter.visitSource("NoMappingMain.java", null);
+
+ {
+ methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(19, label0);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(1, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor = classWriter.visitMethod(
+ ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(22, label0);
+ methodVisitor.visitTypeInsn(NEW, "NoMapping");
+ methodVisitor.visitInsn(DUP);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "NoMapping", "<init>", "()V", false);
+ methodVisitor.visitInsn(POP);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(23, label1);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 1);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/SwappingDump.java b/src/test/java/com/android/tools/r8/naming/applymapping/SwappingDump.java
new file mode 100644
index 0000000..0dea699
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/SwappingDump.java
@@ -0,0 +1,191 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.naming.applymapping;
+
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+// Generated by running tools/asmifier.py on the following code snippet:
+//
+// class A {
+// A() {
+// x();
+// y();
+// }
+//
+// private void x() {
+// System.out.println("A#x");
+// }
+//
+// public void y() {
+// System.out.println("A#y");
+// }
+// }
+class ADump implements Opcodes {
+
+ public static byte[] dump () throws Exception {
+
+ ClassWriter classWriter = new ClassWriter(0);
+ MethodVisitor methodVisitor;
+
+ classWriter.visit(V1_8, ACC_SUPER, "A", null, "java/lang/Object", null);
+
+ classWriter.visitSource("Test.java", null);
+
+ {
+ methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(2, label0);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(3, label1);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "A", "x", "()V", false);
+ Label label2 = new Label();
+ methodVisitor.visitLabel(label2);
+ methodVisitor.visitLineNumber(4, label2);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "A", "y", "()V", false);
+ Label label3 = new Label();
+ methodVisitor.visitLabel(label3);
+ methodVisitor.visitLineNumber(5, label3);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(1, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor = classWriter.visitMethod(ACC_PRIVATE, "x", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(7, label0);
+ methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ methodVisitor.visitLdcInsn("A#x");
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(8, label1);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "y", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(11, label0);
+ methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ methodVisitor.visitLdcInsn("A#y");
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(12, label1);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 1);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
+}
+
+// Generated by running tools/asmifier.py on the following code snippet:
+//
+// class B extends A {
+// }
+class BDump implements Opcodes {
+
+ public static byte[] dump () throws Exception {
+
+ ClassWriter classWriter = new ClassWriter(0);
+ MethodVisitor methodVisitor;
+
+ classWriter.visit(V1_8, ACC_SUPER, "B", null, "A", null);
+
+ classWriter.visitSource("Test.java", null);
+
+ {
+ methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(15, label0);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "A", "<init>", "()V", false);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(1, 1);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
+}
+
+// Generated by running tools/asmifier.py on the following code snippet:
+//
+// public class Main {
+// public static void main(String[] args) {
+// new B().y();
+// }
+// }
+//
+// then replaced use of y() with x() to introduce IllegalAccessError.
+class MainDump implements Opcodes {
+
+ public static byte[] dump () throws Exception {
+
+ ClassWriter classWriter = new ClassWriter(0);
+ MethodVisitor methodVisitor;
+
+ classWriter.visit(V1_8, ACC_PUBLIC | ACC_SUPER, "Main", null, "java/lang/Object", null);
+
+ classWriter.visitSource("Test.java", null);
+
+ {
+ methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(18, label0);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(1, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor = classWriter.visitMethod(
+ ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(20, label0);
+ methodVisitor.visitTypeInsn(NEW, "B");
+ methodVisitor.visitInsn(DUP);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "B", "<init>", "()V", false);
+ methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "B", "x", "()V", false);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(21, label1);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 1);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
+}
+
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/RetraceTest.java b/src/test/java/com/android/tools/r8/naming/retrace/RetraceTest.java
new file mode 100644
index 0000000..e1c61c1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/retrace/RetraceTest.java
@@ -0,0 +1,166 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.naming.retrace;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.function.BiConsumer;
+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 RetraceTest extends TestBase {
+ private Backend backend;
+ private CompilationMode mode;
+
+ @Parameters(name = "Backend: {0}, mode: {1}")
+ public static Collection<Object[]> data() {
+ List<Object[]> parameters = new ArrayList<>();
+ for (Backend backend : Backend.values()) {
+ for (CompilationMode mode : CompilationMode.values()) {
+ parameters.add(new Object[] {backend, mode});
+ }
+ }
+ return parameters;
+ }
+
+ public RetraceTest(Backend backend, CompilationMode mode) {
+ this.backend = backend;
+ this.mode = mode;
+ }
+
+ private List<String> retrace(String map, List<String> stackTrace) throws IOException {
+ Path t = temp.newFolder().toPath();
+ Path mapFile = t.resolve("map");
+ Path stackTraceFile = t.resolve("stackTrace");
+ FileUtils.writeTextFile(mapFile, map);
+ FileUtils.writeTextFile(stackTraceFile, stackTrace);
+ return StringUtils.splitLines(ToolHelper.runRetrace(mapFile, stackTraceFile));
+ }
+
+ private boolean isDalvik() {
+ return backend == Backend.DEX && ToolHelper.getDexVm().isOlderThanOrEqual(DexVm.ART_4_4_4_HOST);
+ }
+
+ private List<String> extractStackTrace(ProcessResult result) {
+ ImmutableList.Builder<String> builder = ImmutableList.builder();
+ List<String> stderr = StringUtils.splitLines(result.stderr);
+ Iterator<String> iterator = stderr.iterator();
+
+ // A Dalvik stacktrace looks like this:
+ // W(209693) threadid=1: thread exiting with uncaught exception (group=0xf616cb20) (dalvikvm)
+ // java.lang.NullPointerException
+ // \tat com.android.tools.r8.naming.retrace.Main.a(:133)
+ // \tat com.android.tools.r8.naming.retrace.Main.a(:139)
+ // \tat com.android.tools.r8.naming.retrace.Main.main(:145)
+ // \tat dalvik.system.NativeStart.main(Native Method)
+ //
+ // An Art 5.1.1 and 6.0.1 stacktrace looks like this:
+ // java.lang.NullPointerException: throw with null exception
+ // \tat com.android.tools.r8.naming.retrace.Main.a(:154)
+ // \tat com.android.tools.r8.naming.retrace.Main.a(:160)
+ // \tat com.android.tools.r8.naming.retrace.Main.main(:166)
+ //
+ // An Art 7.0.0 and latest stacktrace looks like this:
+ // Exception in thread "main" java.lang.NullPointerException: throw with null exception
+ // \tat com.android.tools.r8.naming.retrace.Main.a(:150)
+ // \tat com.android.tools.r8.naming.retrace.Main.a(:156)
+ // \tat com.android.tools.r8.naming.retrace.Main.main(:162)
+ int last = stderr.size();
+ if (isDalvik()) {
+ // Skip the bottom frame "dalvik.system.NativeStart.main".
+ last--;
+ }
+ // Take all lines from the bottom starting with "\tat ".
+ int first = last;
+ while (first - 1 >= 0 && stderr.get(first - 1).startsWith("\tat ")) {
+ first--;
+ }
+ for (int i = first; i < last; i++) {
+ builder.add(stderr.get(i));
+ }
+ return builder.build();
+ }
+
+ public void runTest(Class<?> mainClass, BiConsumer<List<String>, List<String>> checker)
+ throws Exception {
+ StringBuilder proguardMapBuilder = new StringBuilder();
+ AndroidApp output =
+ ToolHelper.runR8(
+ R8Command.builder()
+ .setMode(mode)
+ .addClassProgramData(ToolHelper.getClassAsBytes(mainClass), Origin.unknown())
+ .addProguardConfiguration(
+ ImmutableList.of(keepMainProguardConfiguration(mainClass)), Origin.unknown())
+ .setProgramConsumer(emptyConsumer(backend))
+ .setProguardMapConsumer((string, ignore) -> proguardMapBuilder.append(string))
+ .build());
+
+ ProcessResult result = runOnVMRaw(output, mainClass, backend);
+ List<String> stackTrace = extractStackTrace(result);
+ List<String> retracesStackTrace = retrace(proguardMapBuilder.toString(), stackTrace);
+ checker.accept(stackTrace, retracesStackTrace);
+ }
+
+ @Test
+ public void test() throws Exception {
+ runTest(
+ Main.class,
+ (List<String> stackTrace, List<String> retracesStackTrace) -> {
+ assertEquals(
+ mode == CompilationMode.RELEASE, stackTrace.size() != retracesStackTrace.size());
+ if (mode == CompilationMode.DEBUG) {
+ assertThat(stackTrace.get(0), not(containsString("method2")));
+ assertThat(stackTrace.get(1), not(containsString("method1")));
+ assertThat(stackTrace.get(2), containsString("main"));
+ }
+ assertEquals(3, retracesStackTrace.size());
+ assertThat(retracesStackTrace.get(0), containsString("method2"));
+ assertThat(retracesStackTrace.get(1), containsString("method1"));
+ assertThat(retracesStackTrace.get(2), containsString("main"));
+ });
+ }
+}
+
+class Main {
+ public static void method2(int i) {
+ System.out.println("In method2");
+ throw null;
+ }
+
+ public static void method1(String s) {
+ System.out.println("In method1");
+ for (int i = 0; i < 10; i++) {
+ method2(Integer.parseInt(s));
+ }
+ }
+
+ public static void main(String[] args) {
+ System.out.println("In main");
+ method1("1");
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/release/ShareCommonCodeOnDistinctPositionsTest.java b/src/test/java/com/android/tools/r8/release/ShareCommonCodeOnDistinctPositionsTest.java
new file mode 100644
index 0000000..353952b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/release/ShareCommonCodeOnDistinctPositionsTest.java
@@ -0,0 +1,18 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.release;
+
+public class ShareCommonCodeOnDistinctPositionsTest {
+
+ public static void main(String[] args) {
+ int x;
+ int len = args.length;
+ if (len > 42) {
+ x = (len - 2) + len * 2;
+ } else {
+ x = (len - 2) + len * 2;
+ }
+ System.out.println(x);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/release/ShareCommonCodeOnDistinctPositionsTestRunner.java b/src/test/java/com/android/tools/r8/release/ShareCommonCodeOnDistinctPositionsTestRunner.java
new file mode 100644
index 0000000..6bfd3d1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/release/ShareCommonCodeOnDistinctPositionsTestRunner.java
@@ -0,0 +1,72 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.release;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.AndroidAppConsumers;
+import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.LineNumberTable;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.Streams;
+import it.unimi.dsi.fastutil.ints.IntCollection;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+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 ShareCommonCodeOnDistinctPositionsTestRunner extends TestBase {
+
+ private static final Class CLASS = ShareCommonCodeOnDistinctPositionsTest.class;
+
+ @Parameters
+ public static Backend[] parameters() {
+ return Backend.values();
+ }
+
+ private final Backend backend;
+
+ public ShareCommonCodeOnDistinctPositionsTestRunner(Backend backend) {
+ this.backend = backend;
+ }
+
+ @Test
+ public void test() throws CompilationFailedException, IOException, ExecutionException {
+ AndroidAppConsumers sink = new AndroidAppConsumers();
+ ToolHelper.runR8(
+ R8Command.builder()
+ .addLibraryFiles(runtimeJar(backend))
+ .addProgramFiles(ToolHelper.getClassFileForTestClass(CLASS))
+ .setProgramConsumer(sink.wrapProgramConsumer(emptyConsumer(backend)))
+ .setDisableMinification(true)
+ .setDisableTreeShaking(true)
+ .build(),
+ options -> options.lineNumberOptimization = LineNumberOptimization.OFF);
+ CodeInspector inspector = new CodeInspector(sink.build());
+ MethodSubject method = inspector.clazz(CLASS).mainMethod();
+ // Check that the two shared lines are not in the output (they have no throwing instructions).
+ LineNumberTable lineNumberTable = method.getLineNumberTable();
+ IntCollection lines = lineNumberTable.getLines();
+ assertFalse(lines.contains(12));
+ assertFalse(lines.contains(14));
+ // Check that the two lines have been shared, e.g., there may be only one multiplication left.
+ assertEquals(
+ "Expected only one multiplcation due to instruction sharing.",
+ // TODO(b/117539423): Implement support for sharing optimizations in the CF backend.
+ backend == Backend.DEX ? 1 : 2,
+ Streams.stream(method.iterateInstructions())
+ .filter(InstructionSubject::isMultiplication)
+ .count());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/KeepAttributesTest.java b/src/test/java/com/android/tools/r8/shaking/KeepAttributesTest.java
index b518683..6992f97 100644
--- a/src/test/java/com/android/tools/r8/shaking/KeepAttributesTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/KeepAttributesTest.java
@@ -3,33 +3,39 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking;
-import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.CompilationMode;
-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.debuginfo.DebugInfoInspector;
-import com.android.tools.r8.naming.MemberNaming.MethodSignature;
-import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.shaking.forceproguardcompatibility.keepattributes.TestKeepAttributes;
-import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.AndroidAppConsumers;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
-import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutionException;
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 KeepAttributesTest extends TestBase {
- public static final Class CLASS = TestKeepAttributes.class;
+ private static final Class CLASS = TestKeepAttributes.class;
+
+ @Parameters(name = "{0}")
+ public static Backend[] parameters() {
+ return new Backend[] { Backend.CF, Backend.DEX};
+ }
+
+ private final Backend backend;
+
+ public KeepAttributesTest(Backend backend) {
+ this.backend = backend;
+ }
@Test
public void keepAllAttributesInDebugMode()
@@ -37,10 +43,9 @@
List<String> keepRules = ImmutableList.of(
"-keep class ** { *; }"
);
- CodeInspector inspector = compile(keepRules, CompilationMode.DEBUG);
- DebugInfoInspector debugInfo = debugInfoForMain(inspector);
- checkLineNumbers(true, debugInfo);
- checkLocals(true, debugInfo);
+ MethodSubject mainMethod = compileRunAndGetMain(keepRules, CompilationMode.DEBUG);
+ assertTrue(mainMethod.hasLineNumberTable());
+ assertTrue(mainMethod.hasLocalVariableTable());
}
@Test
@@ -49,51 +54,45 @@
List<String> keepRules = ImmutableList.of(
"-keep class ** { *; }"
);
- CodeInspector inspector = compile(keepRules, CompilationMode.RELEASE);
- DebugInfoInspector debugInfo = debugInfoForMain(inspector);
- checkLineNumbers(false, debugInfo);
- checkLocals(false, debugInfo);
+ MethodSubject mainMethod = compileRunAndGetMain(keepRules, CompilationMode.RELEASE);
+ assertFalse(mainMethod.hasLineNumberTable());
+ assertFalse(mainMethod.hasLocalVariableTable());
}
@Test
public void keepLineNumberTable()
throws CompilationFailedException, IOException, ExecutionException {
List<String> keepRules = ImmutableList.of(
- "-keep class ** { *; }",
"-keepattributes " + ProguardKeepAttributes.LINE_NUMBER_TABLE
);
- CodeInspector inspector = compile(keepRules, CompilationMode.RELEASE);
- DebugInfoInspector debugInfo = debugInfoForMain(inspector);
- checkLineNumbers(true, debugInfo);
- checkLocals(false, debugInfo);
+ MethodSubject mainMethod = compileRunAndGetMain(keepRules, CompilationMode.RELEASE);
+ assertTrue(mainMethod.hasLineNumberTable());
+ assertFalse(mainMethod.hasLocalVariableTable());
}
@Test
public void keepLineNumberTableAndLocalVariableTable()
throws CompilationFailedException, IOException, ExecutionException {
List<String> keepRules = ImmutableList.of(
- "-keep class ** { *; }",
"-keepattributes "
+ ProguardKeepAttributes.LINE_NUMBER_TABLE
+ ", "
+ ProguardKeepAttributes.LOCAL_VARIABLE_TABLE
);
- CodeInspector inspector = compile(keepRules, CompilationMode.RELEASE);
- DebugInfoInspector debugInfo = debugInfoForMain(inspector);
- checkLineNumbers(true, debugInfo);
+ MethodSubject mainMethod = compileRunAndGetMain(keepRules, CompilationMode.RELEASE);
+ assertTrue(mainMethod.hasLineNumberTable());
// Locals are never included in release builds.
- checkLocals(false, debugInfo);
+ assertFalse(mainMethod.hasLocalVariableTable());
}
@Test
public void keepLocalVariableTable() throws IOException, ExecutionException {
List<String> keepRules = ImmutableList.of(
- "-keep class ** { *; }",
"-keepattributes " + ProguardKeepAttributes.LOCAL_VARIABLE_TABLE
);
// Compiling with a keep rule for locals but no line results in an error in R8.
try {
- compile(keepRules, CompilationMode.RELEASE);
+ compileRunAndGetMain(keepRules, CompilationMode.RELEASE);
} catch (CompilationFailedException e) {
assertTrue(e.getCause().getMessage().contains(ProguardKeepAttributes.LOCAL_VARIABLE_TABLE));
assertTrue(e.getCause().getMessage().contains(ProguardKeepAttributes.LINE_NUMBER_TABLE));
@@ -102,39 +101,16 @@
fail("Expected error");
}
- private CodeInspector compile(List<String> keepRules, CompilationMode mode)
+ private MethodSubject compileRunAndGetMain(List<String> keepRules, CompilationMode mode)
throws CompilationFailedException, IOException, ExecutionException {
- AndroidAppConsumers sink = new AndroidAppConsumers();
- R8.run(
- R8Command.builder()
- .setMode(mode)
- .addProgramFiles(
- ToolHelper.getClassFilesForTestDirectory(
- ToolHelper.getClassFileForTestClass(CLASS).getParent()))
- .addLibraryFiles(ToolHelper.getDefaultAndroidJar())
- .addProguardConfiguration(keepRules, Origin.unknown())
- .setProgramConsumer(sink.wrapProgramConsumer(emptyConsumer(Backend.DEX)))
- .build());
- AndroidApp app = sink.build();
- CodeInspector codeInspector = new CodeInspector(app);
- runOnArt(app, CLASS.getTypeName());
- return codeInspector;
- }
-
- private DebugInfoInspector debugInfoForMain(CodeInspector inspector) {
- return new DebugInfoInspector(
- inspector,
- CLASS.getTypeName(),
- new MethodSignature("main", "void", Collections.singleton("java.lang.String[]")));
- }
-
- private void checkLineNumbers(boolean expected, DebugInfoInspector debugInfo) {
- assertEquals("Expected " + (expected ? "line entries" : "no line entries"),
- expected, debugInfo.getEntries().stream().anyMatch(e -> e.lineEntry));
- }
-
- private void checkLocals(boolean expected, DebugInfoInspector debugInfo) {
- assertEquals("Expected " + (expected ? "locals" : "no locals"),
- expected, debugInfo.getEntries().stream().anyMatch(e -> !e.locals.isEmpty()));
+ return testForR8(backend)
+ .setMode(mode)
+ .addProgramClassesAndInnerClasses(CLASS)
+ .addKeepAllClassesRule()
+ .addKeepRules(keepRules)
+ .run(CLASS)
+ .inspector()
+ .clazz(CLASS)
+ .mainMethod();
}
}
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatibilityTestBase.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatibilityTestBase.java
index cf14d8b..4ee3011 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatibilityTestBase.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatibilityTestBase.java
@@ -74,10 +74,25 @@
protected AndroidApp runShrinker(
Shrinker mode, List<Class> programClasses, Iterable<String> proguardConfigs)
throws Exception {
- return runShrinker(mode, programClasses, String.join(System.lineSeparator(), proguardConfigs));
+ return runShrinker(
+ mode, programClasses, String.join(System.lineSeparator(), proguardConfigs), null);
}
- protected AndroidApp runShrinker(Shrinker mode, List<Class> programClasses, String proguardConfig)
+ protected AndroidApp runShrinker(
+ Shrinker mode,
+ List<Class> programClasses,
+ Iterable<String> proguardConfigs,
+ Consumer<InternalOptions> configure)
+ throws Exception {
+ return runShrinker(
+ mode, programClasses, String.join(System.lineSeparator(), proguardConfigs), configure);
+ }
+
+ protected AndroidApp runShrinker(
+ Shrinker mode,
+ List<Class> programClasses,
+ String proguardConfig,
+ Consumer<InternalOptions> configure)
throws Exception {
proguardMap = File.createTempFile("proguard", ".map", temp.getRoot()).toPath();
switch (mode) {
@@ -86,42 +101,56 @@
case PROGUARD6:
return runProguard6(programClasses, proguardConfig, proguardMap);
case PROGUARD6_THEN_D8:
- return runProguard6AndD8(programClasses, proguardConfig, proguardMap);
+ return runProguard6AndD8(programClasses, proguardConfig, proguardMap, configure);
case R8_COMPAT:
- return runR8Compat(programClasses, proguardConfig, proguardMap, Backend.DEX);
+ return runR8Compat(programClasses, proguardConfig, proguardMap, configure, Backend.DEX);
case R8_COMPAT_CF:
- return runR8Compat(programClasses, proguardConfig, proguardMap, Backend.CF);
+ return runR8Compat(programClasses, proguardConfig, proguardMap, configure, Backend.CF);
case R8:
- return runR8(programClasses, proguardConfig, proguardMap, Backend.DEX);
+ return runR8(programClasses, proguardConfig, proguardMap, configure, Backend.DEX);
case R8_CF:
- return runR8(programClasses, proguardConfig, proguardMap, Backend.CF);
+ return runR8(programClasses, proguardConfig, proguardMap, configure, Backend.CF);
}
throw new IllegalArgumentException("Unknown shrinker: " + mode);
}
protected CodeInspector inspectAfterShrinking(
- Shrinker mode, List<Class> programClasses, List<String> proguardConfigs) throws Exception {
- return inspectAfterShrinking(
- mode, programClasses, String.join(System.lineSeparator(), proguardConfigs));
+ Shrinker mode, List<Class> programClasses, List<String> proguardConfigs)
+ throws Exception {
+ return inspectAfterShrinking(mode, programClasses, proguardConfigs, null);
}
protected CodeInspector inspectAfterShrinking(
- Shrinker mode, List<Class> programClasses, String proguardConfig) throws Exception {
+ Shrinker mode,
+ List<Class> programClasses,
+ List<String> proguardConfigs,
+ Consumer<InternalOptions> configure)
+ throws Exception {
+ return inspectAfterShrinking(
+ mode, programClasses, String.join(System.lineSeparator(), proguardConfigs), configure);
+ }
+
+ protected CodeInspector inspectAfterShrinking(
+ Shrinker mode,
+ List<Class> programClasses,
+ String proguardConfig,
+ Consumer<InternalOptions> configure)
+ throws Exception {
switch (mode) {
case PROGUARD5:
return inspectProguard5Result(programClasses, proguardConfig);
case PROGUARD6:
return inspectProguard6Result(programClasses, proguardConfig);
case PROGUARD6_THEN_D8:
- return inspectProguard6AndD8Result(programClasses, proguardConfig);
+ return inspectProguard6AndD8Result(programClasses, proguardConfig, configure);
case R8_COMPAT:
- return inspectR8CompatResult(programClasses, proguardConfig, Backend.DEX);
+ return inspectR8CompatResult(programClasses, proguardConfig, configure, Backend.DEX);
case R8_COMPAT_CF:
- return inspectR8CompatResult(programClasses, proguardConfig, Backend.CF);
+ return inspectR8CompatResult(programClasses, proguardConfig, configure, Backend.CF);
case R8:
- return inspectR8Result(programClasses, proguardConfig, Backend.DEX);
+ return inspectR8Result(programClasses, proguardConfig, configure, Backend.DEX);
case R8_CF:
- return inspectR8Result(programClasses, proguardConfig, Backend.CF);
+ return inspectR8Result(programClasses, proguardConfig, configure, Backend.CF);
}
throw new IllegalArgumentException("Unknown shrinker: " + mode);
}
@@ -151,14 +180,19 @@
}
protected CodeInspector inspectR8Result(
- List<Class> programClasses, String proguardConfig, Backend backend) throws Exception {
- return new CodeInspector(runR8(programClasses, proguardConfig, null, backend));
+ List<Class> programClasses,
+ String proguardConfig,
+ Consumer<InternalOptions> configure,
+ Backend backend)
+ throws Exception {
+ return new CodeInspector(runR8(programClasses, proguardConfig, null, configure, backend));
}
protected AndroidApp runR8Compat(
List<Class> programClasses,
String proguardConfig,
Path proguardMap,
+ Consumer<InternalOptions> configure,
Backend backend)
throws Exception {
CompatProguardCommandBuilder builder = new CompatProguardCommandBuilder(true);
@@ -175,12 +209,15 @@
builder.addLibraryFiles(ToolHelper.getJava8RuntimeJar());
builder.setProgramConsumer(ClassFileConsumer.emptyConsumer());
}
- return ToolHelper.runR8(builder.build());
+ return ToolHelper.runR8(builder.build(), configure);
}
protected CodeInspector inspectR8CompatResult(
- List<Class> programClasses, String proguardConfig, Backend backend) throws Exception {
- return new CodeInspector(runR8Compat(programClasses, proguardConfig, null, backend));
+ List<Class> programClasses,
+ String proguardConfig,
+ Consumer<InternalOptions> configure,
+ Backend backend) throws Exception {
+ return new CodeInspector(runR8Compat(programClasses, proguardConfig, null, configure, backend));
}
protected AndroidApp runProguard5(
@@ -278,7 +315,11 @@
}
protected AndroidApp runProguard6AndD8(
- List<Class> programClasses, String proguardConfig, Path proguardMap) throws Exception {
+ List<Class> programClasses,
+ String proguardConfig,
+ Path proguardMap,
+ Consumer<InternalOptions> configure)
+ throws Exception {
Path proguardedJar =
File.createTempFile("proguarded", FileUtils.JAR_EXTENSION, temp.getRoot()).toPath();
Path proguardConfigFile = File.createTempFile("proguard", ".config", temp.getRoot()).toPath();
@@ -292,14 +333,15 @@
if (result.exitCode != 0) {
fail("Proguard failed, exit code " + result.exitCode + ", stderr:\n" + result.stderr);
}
- return ToolHelper.runD8(readJar(proguardedJar));
+ return ToolHelper.runD8(readJar(proguardedJar), configure);
}
protected CodeInspector inspectProguard6AndD8Result(
- List<Class> programClasses, String proguardConfig) throws Exception {
+ List<Class> programClasses, String proguardConfig, Consumer<InternalOptions> configure)
+ throws Exception {
proguardMap = File.createTempFile("proguard", ".map", temp.getRoot()).toPath();
return new CodeInspector(
- runProguard6AndD8(programClasses, proguardConfig, proguardMap), proguardMap);
+ runProguard6AndD8(programClasses, proguardConfig, proguardMap, configure), proguardMap);
}
protected void verifyClassesPresent(
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ExternalizableTest.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ExternalizableTest.java
index e20b80a..67417e9 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ExternalizableTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ExternalizableTest.java
@@ -310,14 +310,7 @@
assertThat(init, isPresent());
}
- @Test
- public void testSerializable() throws Exception {
- // TODO(b/116735204): R8 should keep default ctor() of first non-serializable superclass of
- // serializable class.
- if (shrinker.isR8()) {
- return;
- }
-
+ private void testSerializable(boolean enableVerticalClassMerging) throws Exception {
String javaOutput = runOnJava(SerializableTestMain.class);
List<String> config = ImmutableList.of(
@@ -331,7 +324,9 @@
" java.lang.Object readResolve();",
"}");
- AndroidApp processedApp = runShrinker(shrinker, CLASSES_FOR_SERIALIZABLE, config);
+ AndroidApp processedApp =
+ runShrinker(shrinker, CLASSES_FOR_SERIALIZABLE, config,
+ o -> o.enableVerticalClassMerging = enableVerticalClassMerging);
// TODO(b/117302947): Need to update ART binary.
if (shrinker.generatesCf()) {
String output = runOnVM(
@@ -346,9 +341,33 @@
// ...
// * Have access to the no-arg constructor of its first non-serializable superclass
CodeInspector codeInspector = new CodeInspector(processedApp, proguardMap);
- ClassSubject classSubject = codeInspector.clazz(NonSerializableSuperClass.class);
+ ClassSubject classSubject;
+ if (shrinker.isR8() && enableVerticalClassMerging) {
+ // Vertical class merging.
+ classSubject = codeInspector.clazz(SerializableDataClass.class);
+ } else {
+ classSubject = codeInspector.clazz(NonSerializableSuperClass.class);
+ }
assertThat(classSubject, isPresent());
MethodSubject init = classSubject.init(ImmutableList.of());
assertThat(init, isPresent());
}
+
+ @Test
+ public void testSerializable_withVerticalClassMerging() throws Exception {
+ if (!shrinker.isR8()) {
+ // Already covered by the other tests.
+ return;
+ }
+ // TODO(b/117514095): Vertical class merging should preserve non/serializable behavior.
+ if (shrinker.isR8()) {
+ return;
+ }
+ testSerializable(true);
+ }
+
+ @Test
+ public void testSerializable_withoutVerticalClassMerging() throws Exception {
+ testSerializable(false);
+ }
}
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ImplicitlyKeptDefaultConstructorTest.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ImplicitlyKeptDefaultConstructorTest.java
index 777561d..a537d40 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ImplicitlyKeptDefaultConstructorTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ImplicitlyKeptDefaultConstructorTest.java
@@ -147,13 +147,14 @@
Class mainClass, List<Class> programClasses, String proguardConfiguration,
TriConsumer<Class, List<Class>, CodeInspector> r8Checker,
TriConsumer<Class, List<Class>, CodeInspector> proguardChecker) throws Exception {
- CodeInspector inspector = inspectR8CompatResult(programClasses, proguardConfiguration, backend);
+ CodeInspector inspector =
+ inspectR8CompatResult(programClasses, proguardConfiguration, null, backend);
r8Checker.accept(mainClass, programClasses, inspector);
if (isRunProguard()) {
inspector = inspectProguard6Result(programClasses, proguardConfiguration);
proguardChecker.accept(mainClass, programClasses, inspector);
- inspector = inspectProguard6AndD8Result(programClasses, proguardConfiguration);
+ inspector = inspectProguard6AndD8Result(programClasses, proguardConfiguration, null);
proguardChecker.accept(mainClass, programClasses, inspector);
}
}
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java
index 89d5079..e420e1e 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java
@@ -12,6 +12,7 @@
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatibilityTestBase;
import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
@@ -45,13 +46,9 @@
return ImmutableList.of(Shrinker.R8_CF, Shrinker.PROGUARD6, Shrinker.R8);
}
- @Override
- protected AndroidApp runR8(
- List<Class> programClasses, String proguardConfig, Path proguardMap, Backend backend)
- throws Exception {
+ private void configure(InternalOptions options) {
// Disable inlining, otherwise classes can be pruned away if all their methods are inlined.
- return runR8(
- programClasses, proguardConfig, proguardMap, o -> o.enableInlining = false, backend);
+ options.enableInlining = false;
}
@Test
@@ -68,7 +65,7 @@
" public <methods>;",
"}"
);
- CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
+ CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config, this::configure);
ClassSubject classSubject = codeInspector.clazz(ClassForIf.class);
assertThat(classSubject, isPresent());
MethodSubject methodSubject = classSubject.method(publicMethod);
@@ -97,7 +94,7 @@
" public <methods>;",
"}"
);
- CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
+ CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config, this::configure);
ClassSubject classSubject = codeInspector.clazz(ClassForIf.class);
assertThat(classSubject, isPresent());
MethodSubject methodSubject = classSubject.method(publicMethod);
@@ -136,7 +133,7 @@
" !public <methods>;",
"}"
);
- CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
+ CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config, this::configure);
ClassSubject classSubject = codeInspector.clazz(ClassForIf.class);
assertThat(classSubject, isPresent());
MethodSubject methodSubject = classSubject.method(publicMethod);
@@ -176,7 +173,7 @@
" public <methods>;",
"}"
);
- CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
+ CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config, this::configure);
ClassSubject classSubject = codeInspector.clazz(ClassForIf.class);
assertThat(classSubject, isPresent());
MethodSubject methodSubject = classSubject.method(publicMethod);
@@ -208,7 +205,7 @@
" !public <methods>;",
"}"
);
- CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
+ CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config, this::configure);
ClassSubject classSubject = codeInspector.clazz(ClassForIf.class);
assertThat(classSubject, isPresent());
MethodSubject methodSubject = classSubject.method(publicMethod);
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnClassTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnClassTest.java
index 5893782..3a95a6b 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnClassTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnClassTest.java
@@ -11,6 +11,7 @@
import static org.junit.Assert.assertThat;
import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatibilityTestBase;
+import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.FieldSubject;
@@ -19,6 +20,7 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
+import java.util.function.Consumer;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -62,8 +64,13 @@
@Override
protected CodeInspector inspectR8Result(
- List<Class> programClasses, String proguardConfig, Backend backend) throws Exception {
- return super.inspectR8Result(programClasses, adaptConfiguration(proguardConfig), backend);
+ List<Class> programClasses,
+ String proguardConfig,
+ Consumer<InternalOptions> configure,
+ Backend backend)
+ throws Exception {
+ return super.inspectR8Result(
+ programClasses, adaptConfiguration(proguardConfig), configure, backend);
}
@Override
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnFieldTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnFieldTest.java
index 25acb23..3f9c857 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnFieldTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnFieldTest.java
@@ -4,11 +4,13 @@
package com.android.tools.r8.shaking.ifrule;
import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatibilityTestBase;
+import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
+import java.util.function.Consumer;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -43,8 +45,12 @@
@Override
protected CodeInspector inspectR8Result(
- List<Class> programClasses, String proguardConfig, Backend backend) throws Exception {
- return super.inspectR8Result(programClasses, adaptConfiguration(proguardConfig), backend);
+ List<Class> programClasses,
+ String proguardConfig,
+ Consumer<InternalOptions> config,
+ Backend backend) throws Exception {
+ return super.inspectR8Result(
+ programClasses, adaptConfiguration(proguardConfig), config, backend);
}
@Override
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/ExtendsMergedTypeDirectlyTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/ExtendsMergedTypeDirectlyTest.java
new file mode 100644
index 0000000..2523363
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/ExtendsMergedTypeDirectlyTest.java
@@ -0,0 +1,58 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.ifrule.verticalclassmerging;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.core.IsNot.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+
+public class ExtendsMergedTypeDirectlyTest extends MergedTypeBaseTest {
+
+ static class TestClass extends C {
+
+ public static void main(String[] args) {
+ System.out.print("Hello world");
+ }
+ }
+
+ public ExtendsMergedTypeDirectlyTest(Backend backend, boolean enableVerticalClassMerging) {
+ super(backend, enableVerticalClassMerging);
+ }
+
+ @Override
+ public Class<?> getTestClass() {
+ return TestClass.class;
+ }
+
+ @Override
+ public String getConditionForProguardIfRule() {
+ // After class merging, TestClass will no longer extend C, but we should still keep the class
+ // Unused in the output.
+ return "-if class **$TestClass extends **$C";
+ }
+
+ @Override
+ public String getExpectedStdout() {
+ return "Hello world";
+ }
+
+ public void inspect(CodeInspector inspector) {
+ super.inspect(inspector);
+
+ if (enableVerticalClassMerging) {
+ // Check that TestClass no longer extends C.
+ ClassSubject testClassSubject = inspector.clazz(TestClass.class);
+ assertThat(testClassSubject, isPresent());
+ assertEquals("java.lang.Object", testClassSubject.getDexClass().superType.toSourceString());
+
+ // Check that C is no longer present.
+ assertThat(inspector.clazz(C.class), not(isPresent()));
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/ExtendsMergedTypeIndirectlyTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/ExtendsMergedTypeIndirectlyTest.java
new file mode 100644
index 0000000..10cff1e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/ExtendsMergedTypeIndirectlyTest.java
@@ -0,0 +1,50 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.ifrule.verticalclassmerging;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+
+public class ExtendsMergedTypeIndirectlyTest extends MergedTypeBaseTest {
+
+ static class TestClass extends B {
+
+ public static void main(String[] args) {
+ // The instantiation of B prevents it from being merged into TestClass.
+ System.out.print(new B().getClass().getName());
+ }
+ }
+
+ public ExtendsMergedTypeIndirectlyTest(Backend backend, boolean enableVerticalClassMerging) {
+ super(backend, enableVerticalClassMerging);
+ }
+
+ @Override
+ public Class<?> getTestClass() {
+ return TestClass.class;
+ }
+
+ @Override
+ public String getConditionForProguardIfRule() {
+ // After class merging, B will no longer extend A (and therefore, TestClass will no longer
+ // extend A indirectly), but we should still keep the class Unused in the output.
+ return "-if class **$TestClass extends **$A";
+ }
+
+ @Override
+ public String getExpectedStdout() {
+ return B.class.getName();
+ }
+
+ public void inspect(CodeInspector inspector) {
+ super.inspect(inspector);
+
+ // Verify that TestClass still inherits from B.
+ ClassSubject testClassSubject = inspector.clazz(TestClass.class);
+ assertEquals(B.class.getTypeName(), testClassSubject.getDexClass().superType.toSourceString());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/IfRuleWithVerticalClassMerging.java b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/IfRuleWithVerticalClassMerging.java
index 165a68f..b026c31 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/IfRuleWithVerticalClassMerging.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/IfRuleWithVerticalClassMerging.java
@@ -8,17 +8,14 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
-import com.android.tools.r8.OutputMode;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatibilityTestBase;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ir.optimize.Inliner.Reason;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
-import java.io.File;
-import java.nio.file.Path;
+import com.google.common.collect.ImmutableSet;
import java.util.Collection;
import java.util.List;
import org.junit.Test;
@@ -31,7 +28,7 @@
int a() throws ClassNotFoundException {
// Class D is expected to be kept - vertical class merging or not. The -if rule say that if
// the method A.a is in the output, then class D is needed.
- String p =getClass().getPackage().getName();
+ String p = getClass().getPackage().getName();
Class.forName(p + ".D");
return 4;
}
@@ -61,79 +58,64 @@
}
}
+// TODO(b/110141157):
+// - Add tests where fields and methods get renamed due to naming conflicts.
+// - Add tests where the then-clause of an -if rule keeps a class that has been merged into another.
@RunWith(Parameterized.class)
-public class IfRuleWithVerticalClassMerging extends ProguardCompatibilityTestBase {
- private final static List<Class> CLASSES = ImmutableList.of(
- A.class, B.class, C.class, D.class, Main.class);
+public class IfRuleWithVerticalClassMerging extends TestBase {
- private final Shrinker shrinker;
- private final boolean enableClassMerging;
+ private static final List<Class> CLASSES =
+ ImmutableList.of(A.class, B.class, C.class, D.class, Main.class);
- public IfRuleWithVerticalClassMerging(Shrinker shrinker, boolean enableClassMerging) {
- this.shrinker = shrinker;
- this.enableClassMerging = enableClassMerging;
+ private final Backend backend;
+ private final boolean enableVerticalClassMerging;
+
+ public IfRuleWithVerticalClassMerging(Backend backend, boolean enableVerticalClassMerging) {
+ this.backend = backend;
+ this.enableVerticalClassMerging = enableVerticalClassMerging;
}
- @Parameters(name = "shrinker: {0} classMerging: {1}")
+ @Parameters(name = "Backend: {0}, vertical class merging: {1}")
public static Collection<Object[]> data() {
// We don't run this on Proguard, as Proguard does not merge A into B.
return ImmutableList.of(
- new Object[] {Shrinker.R8, true},
- new Object[] {Shrinker.R8, false},
- new Object[] {Shrinker.R8_CF, true},
- new Object[] {Shrinker.R8_CF, false});
+ new Object[] {Backend.DEX, true},
+ new Object[] {Backend.DEX, false},
+ new Object[] {Backend.CF, true},
+ new Object[] {Backend.CF, false});
}
private void configure(InternalOptions options) {
- options.enableVerticalClassMerging = enableClassMerging;
- }
+ options.enableVerticalClassMerging = enableVerticalClassMerging;
- @Override
- protected AndroidApp runR8(
- List<Class> programClasses, String proguardConfig, Path proguardMap, Backend backend)
- throws Exception {
- return super.runR8(programClasses, proguardConfig, proguardMap, this::configure, backend);
+ // TODO(b/110148109): Allow ordinary method inlining when -if rules work with inlining.
+ options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
}
private void check(AndroidApp app) throws Exception {
CodeInspector inspector = new CodeInspector(app);
ClassSubject clazzA = inspector.clazz(A.class);
- assertEquals(!enableClassMerging, clazzA.isPresent());
+ assertEquals(!enableVerticalClassMerging, clazzA.isPresent());
ClassSubject clazzB = inspector.clazz(B.class);
assertThat(clazzB, isPresent());
ClassSubject clazzD = inspector.clazz(D.class);
- // TODO(110141157): Class D should be kept - vertical class merging or not.
- assertEquals(!enableClassMerging, clazzD.isPresent());
-
- ProcessResult result;
- if (shrinker == Shrinker.R8) {
- result = runOnArtRaw(app, Main.class.getName());
- } else {
- assert shrinker == Shrinker.R8_CF;
- Path file = File.createTempFile("junit", ".zip", temp.getRoot()).toPath();
- app.writeToZip(file, OutputMode.ClassFile);
- result = ToolHelper.runJava(file, Main.class.getName());
- }
- // TODO(110141157): The code should run - vertical class merging or not.
- assertEquals(enableClassMerging ? 1 : 0, result.exitCode);
- if (!enableClassMerging) {
- assertEquals("123456", result.stdout);
- }
+ assertThat(clazzD, isPresent());
+ assertEquals("123456", runOnVM(app, Main.class, backend));
}
@Test
public void testMergedClassInIfRule() throws Exception {
// Class C is kept, meaning that it will not be touched.
// Class A will be merged into class B.
- List<String> config = ImmutableList.of(
- "-keep class **.Main { public static void main(java.lang.String[]); }",
- "-keep class **.C",
- "-if class **.A",
- "-keep class **.D",
- "-dontobfuscate"
- );
-
- check(runShrinker(shrinker, CLASSES, config));
+ String config =
+ String.join(
+ System.lineSeparator(),
+ "-keep class **.Main { public static void main(java.lang.String[]); }",
+ "-keep class **.C",
+ "-if class **.A",
+ "-keep class **.D",
+ "-dontobfuscate");
+ check(compileWithR8(readClasses(CLASSES), config, this::configure, backend));
}
@Test
@@ -141,15 +123,15 @@
// Class C is kept, meaning that it will not be touched.
// Class A will be merged into class B.
// Main.main access A.x, so that field exists satisfying the if rule.
- List<String> config = ImmutableList.of(
- "-keep class **.Main { public static void main(java.lang.String[]); }",
- "-keep class **.C",
- "-if class **.A { int x; }",
- "-keep class **.D",
- "-dontobfuscate"
- );
-
- check(runShrinker(shrinker, CLASSES, config));
+ String config =
+ String.join(
+ System.lineSeparator(),
+ "-keep class **.Main { public static void main(java.lang.String[]); }",
+ "-keep class **.C",
+ "-if class **.A { int x; }",
+ "-keep class **.D",
+ "-dontobfuscate");
+ check(compileWithR8(readClasses(CLASSES), config, this::configure, backend));
}
@Test
@@ -157,14 +139,14 @@
// Class C is kept, meaning that it will not be touched.
// Class A will be merged into class B.
// Main.main access A.a(), that method exists satisfying the if rule.
- List<String> config = ImmutableList.of(
- "-keep class **.Main { public static void main(java.lang.String[]); }",
- "-keep class **.C",
- "-if class **.A { int a(); }",
- "-keep class **.D",
- "-dontobfuscate"
- );
-
- check(runShrinker(shrinker, CLASSES, config));
+ String config =
+ String.join(
+ System.lineSeparator(),
+ "-keep class **.Main { public static void main(java.lang.String[]); }",
+ "-keep class **.C",
+ "-if class **.A { int a(); }",
+ "-keep class **.D",
+ "-dontobfuscate");
+ check(compileWithR8(readClasses(CLASSES), config, this::configure, backend));
}
}
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/ImplementsMergedTypeDirectlyTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/ImplementsMergedTypeDirectlyTest.java
new file mode 100644
index 0000000..6e8b394
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/ImplementsMergedTypeDirectlyTest.java
@@ -0,0 +1,58 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.ifrule.verticalclassmerging;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.core.IsNot.not;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+
+public class ImplementsMergedTypeDirectlyTest extends MergedTypeBaseTest {
+
+ static class TestClass implements K {
+
+ public static void main(String[] args) {
+ System.out.print("Hello world");
+ }
+ }
+
+ public ImplementsMergedTypeDirectlyTest(Backend backend, boolean enableVerticalClassMerging) {
+ super(backend, enableVerticalClassMerging);
+ }
+
+ @Override
+ public Class<?> getTestClass() {
+ return TestClass.class;
+ }
+
+ @Override
+ public String getConditionForProguardIfRule() {
+ // After class merging, TestClass will no longer implement K, but we should still keep the
+ // class Unused in the output.
+ return "-if class **$TestClass implements **$K";
+ }
+
+ @Override
+ public String getExpectedStdout() {
+ return "Hello world";
+ }
+
+ public void inspect(CodeInspector inspector) {
+ super.inspect(inspector);
+
+ if (enableVerticalClassMerging) {
+ // Check that TestClass no longer implements K.
+ ClassSubject testClassSubject = inspector.clazz(TestClass.class);
+ assertThat(testClassSubject, isPresent());
+ assertTrue(testClassSubject.getDexClass().interfaces.isEmpty());
+
+ // Check that K is no longer present.
+ assertThat(inspector.clazz(K.class), not(isPresent()));
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/ImplementsMergedTypeIndirectlyTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/ImplementsMergedTypeIndirectlyTest.java
new file mode 100644
index 0000000..4583978
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/ImplementsMergedTypeIndirectlyTest.java
@@ -0,0 +1,55 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.ifrule.verticalclassmerging;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+
+public class ImplementsMergedTypeIndirectlyTest extends MergedTypeBaseTest {
+
+ static class TestClass implements J {
+
+ public static void main(String[] args) {
+ System.out.print("Hello world");
+ }
+ }
+
+ public ImplementsMergedTypeIndirectlyTest(Backend backend, boolean enableVerticalClassMerging) {
+ super(backend, enableVerticalClassMerging);
+ }
+
+ @Override
+ public Class<?> getTestClass() {
+ return TestClass.class;
+ }
+
+ @Override
+ public String getAdditionalKeepRules() {
+ // Keep interface J to prevent it from being merged into TestClass.
+ return "-keep class **$J";
+ }
+
+ @Override
+ public String getConditionForProguardIfRule() {
+ // After class merging, J will no longer extend I (and therefore, TestClass will no longer
+ // implement I indirectly), but we should still keep the class Unused in the output.
+ return "-if class **$TestClass implements **$I";
+ }
+
+ @Override
+ public String getExpectedStdout() {
+ return "Hello world";
+ }
+
+ public void inspect(CodeInspector inspector) {
+ super.inspect(inspector);
+
+ // Verify that TestClass still implements J.
+ ClassSubject testClassSubject = inspector.clazz(TestClass.class);
+ assertEquals(J.class.getTypeName(), testClassSubject.getDexClass().interfaces.toSourceString());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedFieldTypeTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedFieldTypeTest.java
new file mode 100644
index 0000000..a877fec
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedFieldTypeTest.java
@@ -0,0 +1,36 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.ifrule.verticalclassmerging;
+
+public class MergedFieldTypeTest extends MergedTypeBaseTest {
+
+ static class TestClass {
+
+ private static A field = new B();
+
+ public static void main(String[] args) {
+ System.out.print(field.getClass().getName());
+ }
+ }
+
+ public MergedFieldTypeTest(Backend backend, boolean enableVerticalClassMerging) {
+ super(backend, enableVerticalClassMerging);
+ }
+
+ @Override
+ public Class<?> getTestClass() {
+ return TestClass.class;
+ }
+
+ @Override
+ public String getConditionForProguardIfRule() {
+ return "-if class **$TestClass { **$A field; }";
+ }
+
+ @Override
+ public String getExpectedStdout() {
+ return B.class.getName();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedParameterTypeTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedParameterTypeTest.java
new file mode 100644
index 0000000..7447efd1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedParameterTypeTest.java
@@ -0,0 +1,38 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.ifrule.verticalclassmerging;
+
+public class MergedParameterTypeTest extends MergedTypeBaseTest {
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ method(new B());
+ }
+
+ public static void method(A obj) {
+ System.out.print(obj.getClass().getName());
+ }
+ }
+
+ public MergedParameterTypeTest(Backend backend, boolean enableVerticalClassMerging) {
+ super(backend, enableVerticalClassMerging);
+ }
+
+ @Override
+ public Class<?> getTestClass() {
+ return TestClass.class;
+ }
+
+ @Override
+ public String getConditionForProguardIfRule() {
+ return "-if class **$TestClass { void method(**$A); }";
+ }
+
+ @Override
+ public String getExpectedStdout() {
+ return B.class.getName();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedReturnTypeTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedReturnTypeTest.java
new file mode 100644
index 0000000..da4a9f1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedReturnTypeTest.java
@@ -0,0 +1,38 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.ifrule.verticalclassmerging;
+
+public class MergedReturnTypeTest extends MergedTypeBaseTest {
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ System.out.print(method().getClass().getName());
+ }
+
+ public static A method() {
+ return new B();
+ }
+ }
+
+ public MergedReturnTypeTest(Backend backend, boolean enableVerticalClassMerging) {
+ super(backend, enableVerticalClassMerging);
+ }
+
+ @Override
+ public Class<?> getTestClass() {
+ return TestClass.class;
+ }
+
+ @Override
+ public String getConditionForProguardIfRule() {
+ return "-if class **$TestClass { **$A method(); }";
+ }
+
+ @Override
+ public String getExpectedStdout() {
+ return B.class.getName();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedTypeBaseTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedTypeBaseTest.java
new file mode 100644
index 0000000..ac341eb
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedTypeBaseTest.java
@@ -0,0 +1,115 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.ifrule.verticalclassmerging;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ir.optimize.Inliner.Reason;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import java.util.Collection;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public abstract class MergedTypeBaseTest extends TestBase {
+
+ private final List<Class> CLASSES =
+ ImmutableList.of(
+ A.class, B.class, C.class, I.class, J.class, K.class, Unused.class, getTestClass());
+
+ static class A {}
+
+ static class B extends A {}
+
+ static class C {}
+
+ interface I {}
+
+ interface J extends I {}
+
+ interface K {}
+
+ static class Unused {}
+
+ final Backend backend;
+ final boolean enableVerticalClassMerging;
+
+ public MergedTypeBaseTest(Backend backend, boolean enableVerticalClassMerging) {
+ this.backend = backend;
+ this.enableVerticalClassMerging = enableVerticalClassMerging;
+ }
+
+ @Parameters(name = "Backend: {0}, vertical class merging: {1}")
+ public static Collection<Object[]> data() {
+ // We don't run this on Proguard, as Proguard does not merge A into B.
+ return ImmutableList.of(
+ new Object[] {Backend.DEX, true},
+ new Object[] {Backend.DEX, false},
+ new Object[] {Backend.CF, true},
+ new Object[] {Backend.CF, false});
+ }
+
+ public abstract Class<?> getTestClass();
+
+ public String getAdditionalKeepRules() {
+ return "";
+ }
+
+ public abstract String getConditionForProguardIfRule();
+
+ public abstract String getExpectedStdout();
+
+ public void inspect(CodeInspector inspector) {
+ assertThat(inspector.clazz(Unused.class), isPresent());
+
+ // Verify that A and I are no longer present when vertical class merging is enabled.
+ if (enableVerticalClassMerging) {
+ assertThat(inspector.clazz(A.class), not(isPresent()));
+ assertThat(inspector.clazz(I.class), not(isPresent()));
+ }
+ }
+
+ @Test
+ public void testIfRule() throws Exception {
+ String expected = getExpectedStdout();
+ assertEquals(expected, runOnJava(getTestClass()));
+
+ String config =
+ StringUtils.joinLines(
+ "-keep class " + getTestClass().getTypeName() + " {",
+ " public static void main(java.lang.String[]);",
+ "}",
+ getConditionForProguardIfRule(),
+ "-keep class " + Unused.class.getTypeName(),
+ getAdditionalKeepRules());
+ AndroidApp output = compileWithR8(readClasses(CLASSES), config, this::configure, backend);
+ assertEquals(expected, runOnVM(output, getTestClass(), backend));
+ inspect(new CodeInspector(output));
+ }
+
+ private void configure(InternalOptions options) {
+ options.enableMinification = false;
+ options.enableVerticalClassMerging = enableVerticalClassMerging;
+
+ // To ensure that the handling of extends and implements rules work as intended,
+ // and that we don't end up keeping `Unused` only because one of the two implementations work.
+ options.testing.allowProguardRulesThatUseExtendsOrImplementsWrong = false;
+
+ // TODO(b/110148109): Allow ordinary method inlining when -if rules work with inlining.
+ options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/innerclassesenclosingmethod/KeepInnerClassesEnclosingMethodAnnotationsTest.java b/src/test/java/com/android/tools/r8/shaking/innerclassesenclosingmethod/KeepInnerClassesEnclosingMethodAnnotationsTest.java
new file mode 100644
index 0000000..474194e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/innerclassesenclosingmethod/KeepInnerClassesEnclosingMethodAnnotationsTest.java
@@ -0,0 +1,141 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.innerclassesenclosingmethod;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isMemberClass;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.shaking.innerclassesenclosingmethod.testclasses.Outer;
+import com.android.tools.r8.utils.AndroidApp;
+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.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Test;
+
+class Main {
+
+ public static void main(String[] args) {
+ Class<?>[] declaredClasses = Outer.class.getDeclaredClasses();
+ if (declaredClasses.length == 0) {
+ System.out.println("No declared classes");
+ } else {
+ for (int i = 0; i < declaredClasses.length; i++) {
+ System.out.println("Declared class: " + declaredClasses[i].getName());
+ }
+ }
+ if (Outer.Inner.class.getDeclaringClass() == null) {
+ System.out.println("No declaring classes");
+ } else {
+ System.out.println("Declaring class: " + Outer.Inner.class.getDeclaringClass().getName());
+ }
+ }
+}
+
+public class KeepInnerClassesEnclosingMethodAnnotationsTest extends TestBase {
+ private static class TestResult {
+
+ public final AndroidApp app;
+ public final CodeInspector inspector;
+ public final ClassSubject outer;
+ public final ClassSubject inner;
+
+ TestResult(AndroidApp app) throws Throwable {
+ this.app = app;
+ this.inspector = new CodeInspector(app);
+ this.outer = inspector.clazz(Outer.class);
+ this.inner = inspector.clazz(Outer.Inner.class);
+ assertThat(outer, isPresent());
+ assertThat(inner, isPresent());
+ }
+ }
+
+ private TestResult runTest(List<String> proguardConfiguration) throws Throwable {
+ return new TestResult(
+ ToolHelper.runR8(
+ R8Command.builder()
+ .addProgramFiles(ToolHelper.getClassFilesForTestPackage(Outer.class.getPackage()))
+ .addClassProgramData(ToolHelper.getClassAsBytes(Main.class), Origin.unknown())
+ .addProguardConfiguration(proguardConfiguration, Origin.unknown())
+ .setProgramConsumer(emptyConsumer(Backend.DEX))
+ .build()));
+ }
+
+ private void noInnerClassesEnclosingMethodInformation(TestResult result) throws Throwable {
+ List<String> lines = StringUtils.splitLines(runOnArt(result.app, Main.class));
+ assertEquals(2, lines.size());
+ assertEquals("No declared classes", lines.get(0));
+ assertEquals("No declaring classes", lines.get(1));
+ }
+
+ private void fullInnerClassesEnclosingMethodInformation(TestResult result) throws Throwable {
+ List<String> lines = StringUtils.splitLines(runOnArt(result.app, Main.class));
+ assertEquals(2, lines.size());
+ assertEquals("Declared class: " + result.inner.getFinalName(), lines.get(0));
+ assertEquals("Declaring class: " + result.outer.getFinalName(), lines.get(1));
+ }
+
+ @Test
+ public void testKeepAll() throws Throwable {
+ TestResult result =
+ runTest(
+ ImmutableList.of(
+ "-keepattributes InnerClasses,EnclosingMethod",
+ "-keep class **Outer*",
+ keepMainProguardConfiguration(Main.class)));
+ assertThat(result.outer, not(isRenamed()));
+ assertThat(result.inner, not(isRenamed()));
+ assertThat(result.inner, isMemberClass());
+ fullInnerClassesEnclosingMethodInformation(result);
+ }
+
+ @Test
+ public void testKeepAllWithoutAttributes() throws Throwable {
+ TestResult result =
+ runTest(
+ ImmutableList.of("-keep class **Outer*", keepMainProguardConfiguration(Main.class)));
+ assertThat(result.outer, not(isRenamed()));
+ assertThat(result.inner, not(isRenamed()));
+ assertThat(result.inner, not(isMemberClass()));
+ noInnerClassesEnclosingMethodInformation(result);
+ }
+
+ @Test
+ public void testKeepInner() throws Throwable {
+ TestResult result =
+ runTest(
+ ImmutableList.of(
+ "-keepattributes InnerClasses,EnclosingMethod",
+ "-keep class **Outer$Inner",
+ keepMainProguardConfiguration(Main.class)));
+ assertThat(result.outer, isRenamed());
+ assertThat(result.inner, not(isRenamed()));
+ assertThat(result.inner, isMemberClass());
+ fullInnerClassesEnclosingMethodInformation(result);
+ }
+
+ @Test
+ public void testKeepOuter() throws Throwable {
+ TestResult result =
+ runTest(
+ ImmutableList.of(
+ "-keepattributes InnerClasses,EnclosingMethod",
+ "-keep class **Outer",
+ keepMainProguardConfiguration(Main.class)));
+ assertThat(result.outer, not(isRenamed()));
+ assertThat(result.inner, isRenamed());
+ assertThat(result.inner, isMemberClass());
+ fullInnerClassesEnclosingMethodInformation(result);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/innerclassesenclosingmethod/testclasses/Outer.java b/src/test/java/com/android/tools/r8/shaking/innerclassesenclosingmethod/testclasses/Outer.java
new file mode 100644
index 0000000..064583f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/innerclassesenclosingmethod/testclasses/Outer.java
@@ -0,0 +1,10 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.innerclassesenclosingmethod.testclasses;
+
+public class Outer {
+
+ public static class Inner {}
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/keepclassmembers/b115867670/B115867670.java b/src/test/java/com/android/tools/r8/shaking/keepclassmembers/b115867670/B115867670.java
index bdd8e1a..a2ab372 100644
--- a/src/test/java/com/android/tools/r8/shaking/keepclassmembers/b115867670/B115867670.java
+++ b/src/test/java/com/android/tools/r8/shaking/keepclassmembers/b115867670/B115867670.java
@@ -8,7 +8,6 @@
import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertThat;
import com.android.tools.r8.graph.invokesuper.Consumer;
@@ -85,8 +84,7 @@
.add(" public static void main(java.lang.String[]);")
.add("}")
.add(additionalKeepRules);
- String config = String.join(System.lineSeparator(), builder.build());
- CodeInspector inspector = inspectAfterShrinking(shrinker, CLASSES, config);
+ CodeInspector inspector = inspectAfterShrinking(shrinker, CLASSES, builder.build());
inspection.accept(inspector);
}
@@ -103,8 +101,7 @@
ClassSubject cls = inspector.clazz(clazz);
assertThat(cls, isPresent());
assertEquals(1, cls.asFoundClassSubject().allFields().size());
- // TODD(116079696): This is a hack!
- cls.forAllFields(field -> assertNotEquals(1, field.getFinalName().length()));
+ cls.forAllFields(field -> assertThat(field, not(isRenamed())));
}
}
@@ -115,8 +112,7 @@
assertThat(cls, isPresent());
assertThat(cls, isRenamed());
assertEquals(1, cls.asFoundClassSubject().allFields().size());
- // TODD(116079696): This is a hack!
- cls.forAllFields(field -> assertEquals(1, field.getFinalName().length()));
+ cls.forAllFields(field -> assertThat(field, isRenamed()));
}
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java
index 1396be9..15e8803 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java
@@ -78,4 +78,14 @@
public String getFinalSignatureAttribute() {
return null;
}
+
+ @Override
+ public LineNumberTable getLineNumberTable() {
+ return null;
+ }
+
+ @Override
+ public boolean hasLocalVariableTable() {
+ return false;
+ }
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
index dedb839..067280a 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.utils.codeinspector;
+import com.android.tools.r8.cf.code.CfArithmeticBinop;
import com.android.tools.r8.cf.code.CfCheckCast;
import com.android.tools.r8.cf.code.CfConstNull;
import com.android.tools.r8.cf.code.CfConstString;
@@ -212,4 +213,13 @@
public boolean isLoad() {
return instruction instanceof CfLoad;
}
+
+ @Override
+ public boolean isMultiplication() {
+ if (!(instruction instanceof CfArithmeticBinop)) {
+ return false;
+ }
+ int opcode = ((CfArithmeticBinop) instruction).getAsmOpcode();
+ return Opcodes.IMUL <= opcode && opcode <= Opcodes.DMUL;
+ }
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
index bc31615..28404f0 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
@@ -39,6 +39,7 @@
import java.nio.file.Paths;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
@@ -136,6 +137,23 @@
return dexItemFactory.createType(DescriptorUtils.javaTypeToDescriptorIgnorePrimitives(string));
}
+ String mapType(Map<String, String> mapping, String typeName) {
+ final String ARRAY_POSTFIX = "[]";
+ int arrayCount = 0;
+ while (typeName.endsWith(ARRAY_POSTFIX)) {
+ arrayCount++;
+ typeName = typeName.substring(0, typeName.length() - 2);
+ }
+ String mappedType = mapping.get(typeName);
+ if (mappedType == null) {
+ return null;
+ }
+ for (int i = 0; i < arrayCount; i++) {
+ mappedType += ARRAY_POSTFIX;
+ }
+ return mappedType;
+ }
+
static <S, T extends Subject> void forAll(
S[] items,
BiFunction<S, FoundClassSubject, ? extends T> constructor,
@@ -248,12 +266,11 @@
}
String getObfuscatedTypeName(String originalTypeName) {
- String obfuscatedType = null;
+ String obfuscatedTypeName = null;
if (mapping != null) {
- obfuscatedType = originalToObfuscatedMapping.get(originalTypeName);
+ obfuscatedTypeName = mapType(originalToObfuscatedMapping, originalTypeName);
}
- obfuscatedType = obfuscatedType == null ? originalTypeName : obfuscatedType;
- return obfuscatedType;
+ return obfuscatedTypeName != null ? obfuscatedTypeName : originalTypeName;
}
InstructionSubject createInstructionSubject(Instruction instruction) {
@@ -321,7 +338,7 @@
public String parsedTypeName(String name) {
String type = name;
if (originalToObfuscatedMapping != null) {
- String original = originalToObfuscatedMapping.inverse().get(name);
+ String original = mapType(originalToObfuscatedMapping.inverse(), name);
type = original != null ? original : name;
}
signature.append(type);
@@ -330,14 +347,17 @@
@Override
public String parsedInnerTypeName(String enclosingType, String name) {
- String type;
+ String type = null;
if (originalToObfuscatedMapping != null) {
// The enclosingType has already been mapped if a mapping is present.
String minifiedEnclosing = originalToObfuscatedMapping.get(enclosingType);
- type = originalToObfuscatedMapping.inverse().get(minifiedEnclosing + "$" + name);
- if (type != null) {
- assert type.startsWith(enclosingType + "$");
- name = type.substring(enclosingType.length() + 1);
+ if (minifiedEnclosing != null) {
+ assert !minifiedEnclosing.contains("[");
+ type = mapType(originalToObfuscatedMapping.inverse(), minifiedEnclosing + "$" + name);
+ if (type != null) {
+ assert type.startsWith(enclosingType + "$");
+ name = type.substring(enclosingType.length() + 1);
+ }
}
} else {
type = enclosingType + "$" + name;
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
index e1d74c1..ca9ce6a 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
@@ -46,6 +46,16 @@
import com.android.tools.r8.code.IputObject;
import com.android.tools.r8.code.IputShort;
import com.android.tools.r8.code.IputWide;
+import com.android.tools.r8.code.MulDouble;
+import com.android.tools.r8.code.MulDouble2Addr;
+import com.android.tools.r8.code.MulFloat;
+import com.android.tools.r8.code.MulFloat2Addr;
+import com.android.tools.r8.code.MulInt;
+import com.android.tools.r8.code.MulInt2Addr;
+import com.android.tools.r8.code.MulIntLit16;
+import com.android.tools.r8.code.MulIntLit8;
+import com.android.tools.r8.code.MulLong;
+import com.android.tools.r8.code.MulLong2Addr;
import com.android.tools.r8.code.NewInstance;
import com.android.tools.r8.code.Nop;
import com.android.tools.r8.code.PackedSwitch;
@@ -265,4 +275,18 @@
public boolean isSparseSwitch() {
return instruction instanceof SparseSwitch;
}
+
+ @Override
+ public boolean isMultiplication() {
+ return instruction instanceof MulInt
+ || instruction instanceof MulIntLit8
+ || instruction instanceof MulIntLit16
+ || instruction instanceof MulInt2Addr
+ || instruction instanceof MulFloat
+ || instruction instanceof MulFloat2Addr
+ || instruction instanceof MulLong
+ || instruction instanceof MulLong2Addr
+ || instruction instanceof MulDouble
+ || instruction instanceof MulDouble2Addr;
+ }
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java
index ae42b14..e65a8b0 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java
@@ -67,9 +67,9 @@
//
// whereas the final signature is for X.a is "a a"
String obfuscatedType = signature.type;
- String originalType = codeInspector.originalToObfuscatedMapping.inverse().get(obfuscatedType);
+ String originalType =
+ codeInspector.mapType(codeInspector.originalToObfuscatedMapping.inverse(), obfuscatedType);
String fieldType = originalType != null ? originalType : obfuscatedType;
-
FieldSignature lookupSignature = new FieldSignature(signature.name, fieldType);
MemberNaming memberNaming = clazz.naming.lookup(lookupSignature);
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
index 3fe8186..f4f1660 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
@@ -4,10 +4,26 @@
package com.android.tools.r8.utils.codeinspector;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfPosition;
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.graph.DexDebugEvent;
+import com.android.tools.r8.graph.DexDebugInfo;
+import com.android.tools.r8.graph.DexDebugPositionState;
import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.JarCode;
import com.android.tools.r8.naming.MemberNaming;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.naming.signature.GenericSignatureParser;
+import it.unimi.dsi.fastutil.objects.Reference2IntMap;
+import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
+import java.util.Arrays;
import java.util.Iterator;
import java.util.function.Predicate;
@@ -134,6 +150,89 @@
}
@Override
+ public boolean hasLocalVariableTable() {
+ Code code = getMethod().getCode();
+ if (code.isDexCode()) {
+ DexCode dexCode = code.asDexCode();
+ if (dexCode.getDebugInfo() != null) {
+ for (DexString parameter : dexCode.getDebugInfo().parameters) {
+ if (parameter != null) {
+ return true;
+ }
+ }
+ for (DexDebugEvent event : dexCode.getDebugInfo().events) {
+ if (event instanceof DexDebugEvent.StartLocal) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+ if (code.isCfCode()) {
+ return !code.asCfCode().getLocalVariables().isEmpty();
+ }
+ if (code.isJarCode()) {
+ return code.asJarCode().getNode().localVariables != null
+ && !code.asJarCode().getNode().localVariables.isEmpty();
+ }
+ throw new Unreachable("Unexpected code type: " + code.getClass().getSimpleName());
+ }
+
+ @Override
+ public LineNumberTable getLineNumberTable() {
+ Code code = getMethod().getCode();
+ if (code.isDexCode()) {
+ return getDexLineNumberTable(code.asDexCode());
+ }
+ if (code.isCfCode()) {
+ return getCfLineNumberTable(code.asCfCode());
+ }
+ if (code.isJarCode()) {
+ return getJarLineNumberTable(code.asJarCode());
+ }
+ throw new Unreachable("Unexpected code type: " + code.getClass().getSimpleName());
+ }
+
+ private LineNumberTable getJarLineNumberTable(JarCode code) {
+ throw new Unimplemented("No support for inspecting the line number table for JarCode");
+ }
+
+ private LineNumberTable getCfLineNumberTable(CfCode code) {
+ int currentLine = -1;
+ Reference2IntMap<InstructionSubject> lineNumberTable =
+ new Reference2IntOpenHashMap<>(code.getInstructions().size());
+ for (CfInstruction insn : code.getInstructions()) {
+ if (insn instanceof CfPosition) {
+ currentLine = ((CfPosition) insn).getPosition().line;
+ }
+ if (currentLine != -1) {
+ lineNumberTable.put(new CfInstructionSubject(insn), currentLine);
+ }
+ }
+ return currentLine == -1 ? null : new LineNumberTable(lineNumberTable);
+ }
+
+ private LineNumberTable getDexLineNumberTable(DexCode code) {
+ DexDebugInfo debugInfo = code.getDebugInfo();
+ if (debugInfo == null) {
+ return null;
+ }
+ Reference2IntMap<InstructionSubject> lineNumberTable =
+ new Reference2IntOpenHashMap<>(code.instructions.length);
+ DexDebugPositionState state =
+ new DexDebugPositionState(debugInfo.startLine, getMethod().method);
+ Iterator<DexDebugEvent> iterator = Arrays.asList(debugInfo.events).iterator();
+ for (Instruction insn : code.instructions) {
+ int offset = insn.getOffset();
+ while (state.getCurrentPc() < offset && iterator.hasNext()) {
+ iterator.next().accept(state);
+ }
+ lineNumberTable.put(new DexInstructionSubject(insn), state.getCurrentLine());
+ }
+ return new LineNumberTable(lineNumberTable);
+ }
+
+ @Override
public String toString() {
return dexMethod.toSourceString();
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
index fb0e0d3..6890684 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
@@ -65,4 +65,6 @@
boolean isPackedSwitch();
boolean isSparseSwitch();
+
+ boolean isMultiplication();
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/LineNumberTable.java b/src/test/java/com/android/tools/r8/utils/codeinspector/LineNumberTable.java
new file mode 100644
index 0000000..7c9c30f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/LineNumberTable.java
@@ -0,0 +1,19 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.utils.codeinspector;
+
+import it.unimi.dsi.fastutil.ints.IntCollection;
+import it.unimi.dsi.fastutil.objects.Reference2IntMap;
+
+public class LineNumberTable {
+ private final Reference2IntMap<InstructionSubject> lineNumberTable;
+
+ public LineNumberTable(Reference2IntMap<InstructionSubject> lineNumberTable) {
+ this.lineNumberTable = lineNumberTable;
+ }
+
+ public IntCollection getLines() {
+ return lineNumberTable.values();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
index 3b718a4..7f310ed 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
@@ -115,6 +115,25 @@
};
}
+ public static Matcher<ClassSubject> isMemberClass() {
+ return new TypeSafeMatcher<ClassSubject>() {
+ @Override
+ public boolean matchesSafely(final ClassSubject clazz) {
+ return clazz.isMemberClass();
+ }
+
+ @Override
+ public void describeTo(final Description description) {
+ description.appendText("is member class");
+ }
+
+ @Override
+ public void describeMismatchSafely(final ClassSubject clazz, Description description) {
+ description.appendText("class ").appendValue(clazz.getOriginalName()).appendText(" is not");
+ }
+ };
+ }
+
public static Matcher<MethodSubject> isAbstract() {
return new TypeSafeMatcher<MethodSubject>() {
@Override
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
index 6eaaf5f..1e21552 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
@@ -32,4 +32,12 @@
Predicate<InstructionSubject> filter) {
return null;
}
+
+ public boolean hasLineNumberTable() {
+ return getLineNumberTable() != null;
+ }
+
+ public abstract LineNumberTable getLineNumberTable();
+
+ public abstract boolean hasLocalVariableTable();
}
diff --git a/third_party/dart-sdk.tar.gz.sha1 b/third_party/dart-sdk.tar.gz.sha1
new file mode 100644
index 0000000..f8b4a60
--- /dev/null
+++ b/third_party/dart-sdk.tar.gz.sha1
@@ -0,0 +1 @@
+0f15c6a81827ce4979b6fcf6183ef35df335d9dc
\ No newline at end of file
diff --git a/tools/disasm.py b/tools/disasm.py
index 0d2599a..a9a5a0a 100755
--- a/tools/disasm.py
+++ b/tools/disasm.py
@@ -7,4 +7,4 @@
import toolhelper
if __name__ == '__main__':
- sys.exit(toolhelper.run('disasm', sys.argv[1:]))
+ sys.exit(toolhelper.run('disasm', sys.argv[1:]), debug=False)
diff --git a/tools/performance_try.py b/tools/performance_try.py
new file mode 100755
index 0000000..5e6404d
--- /dev/null
+++ b/tools/performance_try.py
@@ -0,0 +1,21 @@
+#!/usr/bin/env python
+# Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+import os
+import subprocess
+import sys
+import utils
+
+SCRIPT = '/google/data/ro/teams/dart/golem/bin/golem4.dart'
+DART = os.path.join(utils.THIRD_PARTY, 'dart-sdk', 'bin', 'dart')
+
+def Main():
+ args = sys.argv[1:]
+ if len(args) != 1:
+ print('Performance tracking takes exactly one argument, the name for display')
+ subprocess.check_call([DART, SCRIPT, args[0]])
+
+if __name__ == '__main__':
+ sys.exit(Main())
diff --git a/tools/test.py b/tools/test.py
index b57d00f..cd9b70f 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -52,14 +52,14 @@
help='Print a line before a tests starts and after it ends to stdout.',
default=False, action='store_true')
result.add_option('--tool',
- help='Tool to run ART tests with: "r8" (default) or "d8". Ignored if'
- ' "--all_tests" enabled.',
- default=None, choices=["r8", "d8"])
+ help='Tool to run ART tests with: "r8" (default) or "d8" or "r8cf"'
+ ' (r8 w/CF-backend). Ignored if "--all_tests" enabled.',
+ default=None, choices=["r8", "d8", "r8cf"])
result.add_option('--jctf',
- help='Run JCTF tests with: "r8" (default) or "d8".',
+ help='Run JCTF tests with: "r8" (default) or "d8" or "r8cf".',
default=False, action='store_true')
result.add_option('--only-jctf', '--only_jctf',
- help='Run only JCTF tests with: "r8" (default) or "d8".',
+ help='Run only JCTF tests with: "r8" (default) or "d8" or "r8cf".',
default=False, action='store_true')
result.add_option('--jctf-compile-only', '--jctf_compile_only',
help="Don't run, only compile JCTF tests.",
@@ -156,6 +156,8 @@
gradle_args.append('-PHEAD_sha1=' + utils.get_HEAD_sha1())
# Add Gradle tasks
gradle_args.append('cleanTest')
+ # Build R8lib with dependencies for bootstrapping tests.
+ gradle_args.append('r8libWithDeps')
gradle_args.append('test')
# Test filtering. Must always follow the 'test' task.
for testFilter in args:
diff --git a/tools/test_gradle_benchmarks.py b/tools/test_gradle_benchmarks.py
index c451424..b7c53b8 100755
--- a/tools/test_gradle_benchmarks.py
+++ b/tools/test_gradle_benchmarks.py
@@ -8,6 +8,7 @@
import gradle
import golem
import os
+import subprocess
import sys
import utils
from enum import Enum
@@ -82,7 +83,9 @@
args.append('-Dandroid.enableDesugar=true')
else:
raise AssertionError("Unknown desugar mode: " + repr(desugarMode))
-
+ # Running with a daemon will give inconsistent results based on previous runs
+ # and if the golem runners restarted.
+ args.append('--no-daemon')
args.extend(command)
return gradle.RunGradleWrapperInGetOutput(args, self.appPath, env=self.env)
@@ -158,6 +161,9 @@
def Main():
args = parse_arguments()
if args.golem:
+ # Ensure that we don't have a running daemon
+ exitcode = subprocess.call(['pkill', 'java'])
+ assert exitcode == 0 or exitcode == 1
golem.link_third_party()
if args.tool == 'd8':