Merge "Fix not setting offset on DexItemBasedConstString replacement"
diff --git a/build.gradle b/build.gradle
index c205b2d..33d967f 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1443,8 +1443,13 @@
def out = new StringBuffer()
def err = new StringBuffer()
def command = "tools/retrace.py"
+ def header = "RETRACED STACKTRACE";
+ if (System.getenv('BUILDBOT_BUILDERNAME') != null
+ && !System.getenv('BUILDBOT_BUILDERNAME').endsWith("_release")) {
+ header += ": (${command} --commit_hash ${System.getenv('BUILDBOT_REVISION')})";
+ }
out.append("\n--------------------------------------\n")
- out.append("RETRACED STACKTRACE\n")
+ out.append("${header}\n")
out.append("--------------------------------------\n")
Process process = command.execute()
def processIn = new PrintStream(process.getOut())
diff --git a/src/main/java/com/android/tools/r8/graph/AccessFlags.java b/src/main/java/com/android/tools/r8/graph/AccessFlags.java
index ebeae02..63c1cde 100644
--- a/src/main/java/com/android/tools/r8/graph/AccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/AccessFlags.java
@@ -45,11 +45,12 @@
return NAMES;
}
- protected int flags;
- protected boolean isPublicized = false;
+ protected int originalFlags;
+ protected int modifiedFlags;
- protected AccessFlags(int flags) {
- this.flags = flags;
+ protected AccessFlags(int originalFlags, int modifiedFlags) {
+ this.originalFlags = originalFlags;
+ this.modifiedFlags = modifiedFlags;
}
public abstract T copy();
@@ -57,10 +58,7 @@
public abstract T self();
public int materialize() {
- if (isPromotedToPublic()) {
- return ((flags | Constants.ACC_PUBLIC) & ~Constants.ACC_PROTECTED) & ~Constants.ACC_PRIVATE;
- }
- return flags;
+ return modifiedFlags;
}
public abstract int getAsCfAccessFlags();
@@ -68,21 +66,21 @@
public abstract int getAsDexAccessFlags();
public final int getOriginalCfAccessFlags() {
- return flags;
+ return originalFlags;
}
@Override
public boolean equals(Object object) {
if (object instanceof AccessFlags) {
AccessFlags other = (AccessFlags) object;
- return flags == other.flags && isPublicized == other.isPublicized;
+ return originalFlags == other.originalFlags && modifiedFlags == other.modifiedFlags;
}
return false;
}
@Override
public int hashCode() {
- return (flags << 1) | (isPublicized ? 1 : 0);
+ return originalFlags | modifiedFlags;
}
public boolean isMoreVisibleThan(AccessFlags other) {
@@ -109,7 +107,7 @@
}
public boolean isPublic() {
- return isSet(Constants.ACC_PUBLIC) || isPromotedToPublic();
+ return isSet(Constants.ACC_PUBLIC);
}
public void setPublic() {
@@ -122,7 +120,7 @@
}
public boolean isPrivate() {
- return isSet(Constants.ACC_PRIVATE) && !isPromotedToPublic();
+ return isSet(Constants.ACC_PRIVATE);
}
public void setPrivate() {
@@ -135,7 +133,7 @@
}
public boolean isProtected() {
- return isSet(Constants.ACC_PROTECTED) && !isPromotedToPublic();
+ return isSet(Constants.ACC_PROTECTED);
}
public void setProtected() {
@@ -179,35 +177,56 @@
unset(Constants.ACC_SYNTHETIC);
}
- public boolean isPromotedToPublic() {
- return isPublicized;
+ public void promoteToFinal() {
+ promote(Constants.ACC_FINAL);
}
- public T setPromotedToPublic(boolean isPublicized) {
- this.isPublicized = isPublicized;
- return self();
+ public void demoteFromFinal() {
+ demote(Constants.ACC_FINAL);
+ }
+
+ public boolean isPromotedToPublic() {
+ return isPromoted(Constants.ACC_PUBLIC);
}
public void promoteToPublic() {
- isPublicized = true;
+ demote(Constants.ACC_PRIVATE | Constants.ACC_PROTECTED);
+ promote(Constants.ACC_PUBLIC);
}
- public void unsetPromotedToPublic() {
- isPublicized = false;
+ public void promoteToStatic() {
+ promote(Constants.ACC_STATIC);
+ }
+
+ private boolean wasSet(int flag) {
+ return (originalFlags & flag) != 0;
}
protected boolean isSet(int flag) {
- return (flags & flag) != 0;
+ return (modifiedFlags & flag) != 0;
}
protected void set(int flag) {
- flags |= flag;
+ originalFlags |= flag;
+ modifiedFlags |= flag;
}
protected void unset(int flag) {
- flags &= ~flag;
+ originalFlags &= ~flag;
+ modifiedFlags &= ~flag;
}
+ protected boolean isPromoted(int flag) {
+ return !wasSet(flag) && isSet(flag);
+ }
+
+ protected void promote(int flag) {
+ modifiedFlags |= flag;
+ }
+
+ protected void demote(int flag) {
+ modifiedFlags &= ~flag;
+ }
public String toSmaliString() {
return toStringInternal(true);
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
index a45064e..e415b0f 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
@@ -119,8 +119,10 @@
// For mapping invoke virtual instruction to target methods.
public Set<DexEncodedMethod> lookupVirtualTargets(DexMethod method) {
- Set<DexEncodedMethod> result = new HashSet<>();
- // First add the target for receiver type method.type.
+ if (method.holder.isArrayType()) {
+ assert method.name == dexItemFactory.cloneMethodName;
+ return null;
+ }
DexClass root = definitionFor(method.holder);
if (root == null) {
// type specified in method does not have a materialized class.
@@ -131,6 +133,8 @@
// This will fail at runtime.
return null;
}
+ // First add the target for receiver type method.type.
+ Set<DexEncodedMethod> result = new HashSet<>();
topTargets.forEachTarget(result::add);
// Add all matching targets from the subclass hierarchy.
for (DexType type : subtypes(method.holder)) {
diff --git a/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java b/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java
index 6c7caf9..0c96120 100644
--- a/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/ClassAccessFlags.java
@@ -50,7 +50,11 @@
}
private ClassAccessFlags(int flags) {
- super(flags);
+ this(flags, flags);
+ }
+
+ private ClassAccessFlags(int originalFlags, int modifiedFlags) {
+ super(originalFlags, modifiedFlags);
}
public static ClassAccessFlags fromSharedAccessFlags(int access) {
@@ -70,7 +74,7 @@
@Override
public ClassAccessFlags copy() {
- return new ClassAccessFlags(flags).setPromotedToPublic(isPromotedToPublic());
+ return new ClassAccessFlags(originalFlags, modifiedFlags);
}
@Override
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 c76865e..d9940f3 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -448,6 +448,13 @@
}
public boolean classInitializationMayHaveSideEffects(AppInfo appInfo) {
+ return classInitializationMayHaveSideEffects(appInfo, Predicates.alwaysFalse());
+ }
+
+ public boolean classInitializationMayHaveSideEffects(AppInfo appInfo, Predicate<DexType> ignore) {
+ if (ignore.test(type)) {
+ return false;
+ }
if (hasNonTrivialClassInitializer()) {
return true;
}
@@ -455,11 +462,11 @@
return true;
}
for (DexType iface : interfaces.values) {
- if (iface.classInitializationMayHaveSideEffects(appInfo)) {
+ if (iface.classInitializationMayHaveSideEffects(appInfo, ignore)) {
return true;
}
}
- if (superType != null && superType.classInitializationMayHaveSideEffects(appInfo)) {
+ if (superType != null && superType.classInitializationMayHaveSideEffects(appInfo, ignore)) {
return true;
}
return 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 5aa2ee4..44294db 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -645,7 +645,7 @@
// for the forwarding method, as the forwarding method will copy the access flags from this,
// and if different forwarding methods are created in different subclasses the first could be
// final.
- accessFlags.unsetFinal();
+ accessFlags.demoteFromFinal();
DexMethod newMethod = itemFactory.createMethod(holder.type, method.proto, method.name);
Invoke.Type type = accessFlags.isStatic() ? Invoke.Type.STATIC : Invoke.Type.SUPER;
Builder builder = builder(this);
@@ -685,7 +685,8 @@
public DexEncodedMethod toStaticMethodWithoutThis() {
checkIfObsolete();
assert !accessFlags.isStatic();
- Builder builder = builder(this).setStatic().unsetOptimizationInfo().withoutThisParameter();
+ Builder builder =
+ builder(this).promoteToStatic().unsetOptimizationInfo().withoutThisParameter();
setObsolete();
return builder.build();
}
@@ -1255,8 +1256,8 @@
this.method = method;
}
- public Builder setStatic() {
- this.accessFlags.setStatic();
+ public Builder promoteToStatic() {
+ this.accessFlags.promoteToStatic();
return this;
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index 31edee9..0c48ea9 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -23,6 +23,7 @@
import java.util.TreeSet;
import java.util.function.Consumer;
import java.util.function.Function;
+import java.util.function.Predicate;
public class DexType extends DexReference implements PresortedComparable<DexType> {
@@ -119,9 +120,9 @@
return implementedInterfaces(appInfo).contains(appInfo.dexItemFactory.serializableType);
}
- public boolean classInitializationMayHaveSideEffects(AppInfo appInfo) {
+ public boolean classInitializationMayHaveSideEffects(AppInfo appInfo, Predicate<DexType> ignore) {
DexClass clazz = appInfo.definitionFor(this);
- return clazz == null || clazz.classInitializationMayHaveSideEffects(appInfo);
+ return clazz == null || clazz.classInitializationMayHaveSideEffects(appInfo, ignore);
}
public boolean isUnknown() {
diff --git a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
index 012c4a4..1e125d9 100644
--- a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
@@ -45,6 +45,7 @@
@Override
public DexClass definitionFor(DexType type) {
+ assert type.isClassType() : "Cannot lookup definition for type: " + type;
DexClass result = programClasses.get(type);
if (result == null) {
result = libraryClasses.get(type);
@@ -73,22 +74,24 @@
}
public DirectMappedDexApplication rewrittenWithLense(GraphLense graphLense) {
- assert mappingIsValid(graphLense, programClasses.getAllTypes());
- assert mappingIsValid(graphLense, libraryClasses.keySet());
// As a side effect, this will rebuild the program classes and library classes maps.
- return this.builder().build().asDirect();
+ DirectMappedDexApplication rewrittenApplication = this.builder().build().asDirect();
+ assert rewrittenApplication.mappingIsValid(graphLense, programClasses.getAllTypes());
+ assert rewrittenApplication.mappingIsValid(graphLense, libraryClasses.keySet());
+ return rewrittenApplication;
}
private boolean mappingIsValid(GraphLense graphLense, Iterable<DexType> types) {
- // The lense might either map to a different type that is already present in the application
+ // The lens might either map to a different type that is already present in the application
// (e.g. relinking a type) or it might encode a type that was renamed, in which case the
// original type will point to a definition that was renamed.
for (DexType type : types) {
DexType renamed = graphLense.lookupType(type);
if (renamed != type) {
- if (definitionFor(type).type != renamed && definitionFor(renamed) == null) {
- return false;
+ if (definitionFor(type) == null && definitionFor(renamed) != null) {
+ continue;
}
+ assert definitionFor(type).type == renamed || definitionFor(renamed) != null;
}
}
return true;
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java b/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java
index e940269..bb39765 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java
@@ -37,12 +37,16 @@
}
private FieldAccessFlags(int flags) {
- super(flags);
+ this(flags, flags);
+ }
+
+ private FieldAccessFlags(int originalFlags, int modifiedFlags) {
+ super(originalFlags, modifiedFlags);
}
@Override
public FieldAccessFlags copy() {
- return new FieldAccessFlags(flags).setPromotedToPublic(isPromotedToPublic());
+ return new FieldAccessFlags(originalFlags, modifiedFlags);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLense.java b/src/main/java/com/android/tools/r8/graph/GraphLense.java
index 898afed..b93c867 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLense.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLense.java
@@ -359,6 +359,8 @@
return new Builder();
}
+ public abstract DexType getOriginalType(DexType type);
+
public abstract DexField getOriginalFieldSignature(DexField field);
public abstract DexMethod getOriginalMethodSignature(DexMethod method);
@@ -370,16 +372,16 @@
public DexEncodedMethod mapDexEncodedMethod(
AppInfo appInfo, DexEncodedMethod originalEncodedMethod) {
DexMethod newMethod = getRenamedMethodSignature(originalEncodedMethod.method);
- if (newMethod != originalEncodedMethod.method) {
- // We can't directly use AppInfo#definitionFor(DexMethod) since definitions may not be
- // updated either yet.
- DexClass newHolder = appInfo.definitionFor(newMethod.holder);
- assert newHolder != null;
- DexEncodedMethod newEncodedMethod = newHolder.lookupMethod(newMethod);
- assert newEncodedMethod != null;
- return newEncodedMethod;
- }
- return originalEncodedMethod;
+ // Note that:
+ // * Even if `newMethod` is the same as `originalEncodedMethod.method`, we still need to look it
+ // up, since `originalEncodedMethod` may be obsolete.
+ // * We can't directly use AppInfo#definitionFor(DexMethod) since definitions may not be
+ // updated either yet.
+ DexClass newHolder = appInfo.definitionFor(newMethod.holder);
+ assert newHolder != null;
+ DexEncodedMethod newEncodedMethod = newHolder.lookupMethod(newMethod);
+ assert newEncodedMethod != null;
+ return newEncodedMethod;
}
public abstract DexType lookupType(DexType type);
@@ -391,7 +393,7 @@
}
public abstract GraphLenseLookupResult lookupMethod(
- DexMethod method, DexEncodedMethod context, Type type);
+ DexMethod method, DexMethod context, Type type);
public abstract RewrittenPrototypeDescription lookupPrototypeChanges(DexMethod method);
@@ -626,6 +628,11 @@
}
@Override
+ public DexType getOriginalType(DexType type) {
+ return type;
+ }
+
+ @Override
public DexField getOriginalFieldSignature(DexField field) {
return field;
}
@@ -651,8 +658,7 @@
}
@Override
- public GraphLenseLookupResult lookupMethod(
- DexMethod method, DexEncodedMethod context, Type type) {
+ public GraphLenseLookupResult lookupMethod(DexMethod method, DexMethod context, Type type) {
return new GraphLenseLookupResult(method, type);
}
@@ -673,14 +679,14 @@
}
/**
- * GraphLense implementation with a parent lense using a simple mapping for type, method and
- * field mapping.
+ * GraphLense implementation with a parent lense using a simple mapping for type, method and field
+ * mapping.
*
- * Subclasses can override the lookup methods.
+ * <p>Subclasses can override the lookup methods.
*
- * For method mapping where invocation type can change just override
- * {@link #mapInvocationType(DexMethod, DexMethod, DexEncodedMethod, Type)} if
- * the default name mapping applies, and only invocation type might need to change.
+ * <p>For method mapping where invocation type can change just override {@link
+ * #mapInvocationType(DexMethod, DexMethod, DexMethod, Type)} if the default name mapping applies,
+ * and only invocation type might need to change.
*/
public static class NestedGraphLense extends GraphLense {
@@ -715,6 +721,11 @@
}
@Override
+ public DexType getOriginalType(DexType type) {
+ return previousLense.getOriginalType(type);
+ }
+
+ @Override
public DexField getOriginalFieldSignature(DexField field) {
DexField originalField =
originalFieldSignatures != null
@@ -772,9 +783,12 @@
}
@Override
- public GraphLenseLookupResult lookupMethod(
- DexMethod method, DexEncodedMethod context, Type type) {
- GraphLenseLookupResult previous = previousLense.lookupMethod(method, context, type);
+ public GraphLenseLookupResult lookupMethod(DexMethod method, DexMethod context, Type type) {
+ DexMethod previousContext =
+ originalMethodSignatures != null
+ ? originalMethodSignatures.getOrDefault(context, context)
+ : context;
+ GraphLenseLookupResult previous = previousLense.lookupMethod(method, previousContext, type);
DexMethod newMethod = methodMap.get(previous.getMethod());
if (newMethod == null) {
return previous;
@@ -782,7 +796,7 @@
// TODO(sgjesse): Should we always do interface to virtual mapping? Is it a performance win
// that only subclasses which are known to need it actually do it?
return new GraphLenseLookupResult(
- newMethod, mapInvocationType(newMethod, method, context, previous.getType()));
+ newMethod, mapInvocationType(newMethod, method, previous.getType()));
}
@Override
@@ -793,22 +807,20 @@
/**
* Default invocation type mapping.
*
- * This is an identity mapping. If a subclass need invocation type mapping either override
- * this method or {@link #lookupMethod(DexMethod, DexEncodedMethod, Type)}
+ * <p>This is an identity mapping. If a subclass need invocation type mapping either override
+ * this method or {@link #lookupMethod(DexMethod, DexMethod, Type)}
*/
- protected Type mapInvocationType(
- DexMethod newMethod, DexMethod originalMethod, DexEncodedMethod context, Type type) {
+ protected Type mapInvocationType(DexMethod newMethod, DexMethod originalMethod, Type type) {
return type;
}
/**
* Standard mapping between interface and virtual invoke type.
*
- * Handle methods moved from interface to class or class to interface.
+ * <p>Handle methods moved from interface to class or class to interface.
*/
- final protected Type mapVirtualInterfaceInvocationTypes(
- AppInfo appInfo, DexMethod newMethod, DexMethod originalMethod,
- DexEncodedMethod context, Type type) {
+ protected final Type mapVirtualInterfaceInvocationTypes(
+ AppInfo appInfo, DexMethod newMethod, DexMethod originalMethod, Type type) {
if (type == Type.VIRTUAL || type == Type.INTERFACE) {
// Get the invoke type of the actual definition.
DexClass newTargetClass = appInfo.definitionFor(newMethod.holder);
diff --git a/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java b/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
index 6dea825..b236428 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
@@ -42,9 +42,7 @@
@Override
public DexClass definitionFor(DexType type) {
- if (type == null) {
- return null;
- }
+ assert type.isClassType() : "Cannot lookup definition for type: " + type;
DexClass clazz = programClasses.get(type);
if (clazz == null && classpathClasses != null) {
clazz = classpathClasses.get(type);
diff --git a/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java b/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java
index ca507b7..2173ed0 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodAccessFlags.java
@@ -54,12 +54,16 @@
}
private MethodAccessFlags(int flags) {
- super(flags);
+ this(flags, flags);
+ }
+
+ private MethodAccessFlags(int originalFlags, int modifiedFlags) {
+ super(originalFlags, modifiedFlags);
}
@Override
public MethodAccessFlags copy() {
- return new MethodAccessFlags(flags).setPromotedToPublic(isPromotedToPublic());
+ return new MethodAccessFlags(originalFlags, modifiedFlags);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/TypeChecker.java b/src/main/java/com/android/tools/r8/ir/analysis/TypeChecker.java
index 53a6245..75a9726 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/TypeChecker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/TypeChecker.java
@@ -78,7 +78,7 @@
return true;
}
- if (valueType.isReference()) {
+ if (fieldType.isClassType() && valueType.isReference()) {
// Interface types are treated like Object according to the JVM spec.
// https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.10.1.2-100
DexClass clazz = appInfo.definitionFor(instruction.getField().type);
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 9c3bcc3..3ef167c 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
@@ -330,8 +330,7 @@
if (type.isPrimitiveType()) {
return PrimitiveTypeLatticeElement.fromDexType(type, asArrayElementType);
}
- return appInfo.dexItemFactory.createReferenceTypeLatticeElement(
- type, nullability, appInfo);
+ return appInfo.dexItemFactory.createReferenceTypeLatticeElement(type, nullability, appInfo);
}
public boolean isValueTypeCompatible(TypeLatticeElement other) {
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 bd57f29..7610f9c 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
@@ -91,6 +91,9 @@
// A const-class instruction can be dead code only if the resulting program is known to contain
// the class mentioned.
DexType baseType = clazz.toBaseType(appInfo.dexItemFactory);
+ if (baseType.isPrimitiveType()) {
+ return true;
+ }
DexClass holder = appInfo.definitionFor(baseType);
return holder != null && holder.isProgramClass();
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index cb93a5e..773a648 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -662,9 +662,12 @@
}
// After the throwing instruction only debug instructions and the final jump
// instruction is allowed.
+ // TODO(ager): For now allow const instructions due to the way consts are pushed
+ // towards their use
if (seenThrowing) {
assert instruction.isDebugInstruction()
|| instruction.isJumpInstruction()
+ || instruction.isConstInstruction()
|| instruction.isStore()
|| instruction.isPop();
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
index d9040c5..c12ea40 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
@@ -532,7 +532,7 @@
private void processInvoke(Type type, DexMethod method) {
DexEncodedMethod source = caller.method;
- GraphLenseLookupResult result = graphLense.lookupMethod(method, source, type);
+ GraphLenseLookupResult result = graphLense.lookupMethod(method, source.method, type);
method = result.getMethod();
type = result.getType();
DexEncodedMethod definition = appInfo.lookup(type, method, source.method.holder);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 7b55543..f068a6a 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -1290,6 +1290,7 @@
CodeRewriter.collapseTrivialGotos(method, code);
PeepholeOptimizer.optimize(code, registerAllocator);
}
+ CodeRewriter.removeUnneededMovesOnExitingPaths(code, registerAllocator);
CodeRewriter.collapseTrivialGotos(method, code);
if (Log.ENABLED) {
Log.debug(getClass(), "Final (non-SSA) flow graph for %s:\n%s",
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index cf88f0f..7c22bbf 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -156,7 +156,7 @@
checkInvokeDirect(method.method, invoke.asInvokeDirect());
}
GraphLenseLookupResult lenseLookup =
- graphLense.lookupMethod(invokedMethod, method, invoke.getType());
+ graphLense.lookupMethod(invokedMethod, method.method, invoke.getType());
DexMethod actualTarget = lenseLookup.getMethod();
Invoke.Type actualInvokeType = lenseLookup.getType();
if (actualInvokeType == Type.VIRTUAL) {
@@ -210,15 +210,7 @@
iterator.replaceCurrentInstruction(newInvoke);
if (constantReturnMaterializingInstruction != null) {
- if (block.hasCatchHandlers()) {
- // Split the block to ensure no instructions after throwing instructions.
- iterator
- .split(code, blocks)
- .listIterator()
- .add(constantReturnMaterializingInstruction);
- } else {
- iterator.add(constantReturnMaterializingInstruction);
- }
+ iterator.add(constantReturnMaterializingInstruction);
}
DexType actualReturnType = actualTarget.proto.returnType;
@@ -314,10 +306,11 @@
}
} else if (current.isMoveException()) {
MoveException moveException = current.asMoveException();
- if (moveException.hasOutValue()) {
- // Conservatively add the out-value to `newSSAValues` since the catch handler guards
- // may have been renamed as a result of class merging.
- newSSAValues.add(moveException.outValue());
+ DexType newExceptionType = graphLense.lookupType(moveException.getExceptionType());
+ if (newExceptionType != moveException.getExceptionType()) {
+ iterator.replaceCurrentInstruction(
+ new MoveException(
+ makeOutValue(moveException, code, newSSAValues), newExceptionType, options));
}
} else if (current.isNewArrayEmpty()) {
NewArrayEmpty newArrayEmpty = current.asNewArrayEmpty();
@@ -328,7 +321,7 @@
iterator.replaceCurrentInstruction(newNewArray);
}
} else if (current.isNewInstance()) {
- NewInstance newInstance= current.asNewInstance();
+ NewInstance newInstance = current.asNewInstance();
DexType newClazz = graphLense.lookupType(newInstance.clazz);
if (newClazz != newInstance.clazz) {
NewInstance newNewInstance = new NewInstance(
@@ -464,7 +457,7 @@
DexMethod invokedMethod = methodHandle.asMethod();
MethodHandleType oldType = methodHandle.type;
GraphLenseLookupResult lenseLookup =
- graphLense.lookupMethod(invokedMethod, context, oldType.toInvokeType());
+ graphLense.lookupMethod(invokedMethod, context.method, oldType.toInvokeType());
DexMethod rewrittenTarget = lenseLookup.getMethod();
DexMethod actualTarget;
MethodHandleType newType;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodDesugaringLense.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodDesugaringLense.java
index a8616fb..b708ae9 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodDesugaringLense.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodDesugaringLense.java
@@ -4,8 +4,6 @@
package com.android.tools.r8.ir.desugar;
-import com.android.tools.r8.graph.AppInfo;
-import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.GraphLense;
@@ -13,14 +11,11 @@
import com.google.common.collect.BiMap;
import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.ImmutableMap;
-import java.util.Map;
class InterfaceMethodDesugaringLense extends NestedGraphLense {
- private final Map<DexEncodedMethod, DexEncodedMethod> methodsWithMovedCode;
InterfaceMethodDesugaringLense(
BiMap<DexMethod, DexMethod> methodMapping,
- Map<DexEncodedMethod, DexEncodedMethod> methodsWithMovedCode,
GraphLense previous, DexItemFactory factory) {
super(
ImmutableMap.of(),
@@ -30,12 +25,5 @@
methodMapping.inverse(),
previous,
factory);
- this.methodsWithMovedCode = methodsWithMovedCode;
- }
-
- @Override
- public DexEncodedMethod mapDexEncodedMethod(AppInfo appInfo, DexEncodedMethod original) {
- return super.mapDexEncodedMethod(
- appInfo, methodsWithMovedCode.getOrDefault(original, original));
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
index 7a19fb8..10cbabb 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
@@ -428,7 +428,6 @@
converter.appView.setGraphLense(
new InterfaceMethodDesugaringLense(
processor.movedMethods,
- processor.methodsWithMovedCode,
converter.appView.graphLense(),
factory));
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
index 6ba9b6a..4f335a6 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
@@ -85,7 +85,7 @@
MethodAccessFlags newFlags = virtual.accessFlags.copy();
newFlags.unsetBridge();
- newFlags.setStatic();
+ newFlags.promoteToStatic();
DexCode dexCode = code.asDexCode();
// We cannot name the parameter "this" because the debugger may omit it due to the method
// actually being static. Instead we prepend it with a special character.
@@ -124,8 +124,7 @@
MethodAccessFlags originalFlags = direct.accessFlags;
MethodAccessFlags newFlags = originalFlags.copy();
if (originalFlags.isPrivate()) {
- newFlags.unsetPrivate();
- newFlags.setPublic();
+ newFlags.promoteToPublic();
}
DexMethod oldMethod = direct.method;
@@ -143,7 +142,7 @@
assert !rewriter.factory.isClassConstructor(oldMethod)
: "Unexpected private constructor " + direct.toSourceString()
+ " in " + iface.origin;
- newFlags.setStatic();
+ newFlags.promoteToStatic();
DexMethod companionMethod = rewriter.privateAsMethodOfCompanionClass(oldMethod);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriterGraphLense.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriterGraphLense.java
index 26f890e..d5ceeb1 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriterGraphLense.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriterGraphLense.java
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.desugar;
-import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.GraphLense;
@@ -28,13 +27,12 @@
}
@Override
- protected Type mapInvocationType(
- DexMethod newMethod, DexMethod originalMethod, DexEncodedMethod context, Type type) {
+ protected Type mapInvocationType(DexMethod newMethod, DexMethod originalMethod, Type type) {
if (methodMap.get(originalMethod) == newMethod) {
assert type == Type.VIRTUAL || type == Type.DIRECT;
return Type.STATIC;
}
- return super.mapInvocationType(newMethod, originalMethod, context, type);
+ return super.mapInvocationType(newMethod, originalMethod, type);
}
}
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 3227d45..1eae24e 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
@@ -61,6 +61,7 @@
import com.android.tools.r8.ir.code.ConstNumber;
import com.android.tools.r8.ir.code.ConstString;
import com.android.tools.r8.ir.code.DebugLocalWrite;
+import com.android.tools.r8.ir.code.DebugLocalsChange;
import com.android.tools.r8.ir.code.DexItemBasedConstString;
import com.android.tools.r8.ir.code.DominatorTree;
import com.android.tools.r8.ir.code.Goto;
@@ -78,6 +79,7 @@
import com.android.tools.r8.ir.code.InvokeNewArray;
import com.android.tools.r8.ir.code.InvokeStatic;
import com.android.tools.r8.ir.code.InvokeVirtual;
+import com.android.tools.r8.ir.code.Move;
import com.android.tools.r8.ir.code.NewArrayEmpty;
import com.android.tools.r8.ir.code.NewArrayFilledData;
import com.android.tools.r8.ir.code.NewInstance;
@@ -97,6 +99,7 @@
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.ReflectionOptimizer.ClassNameComputationInfo;
import com.android.tools.r8.ir.optimize.SwitchUtils.EnumSwitchInfo;
+import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
import com.android.tools.r8.kotlin.Kotlin;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
@@ -115,12 +118,18 @@
import com.google.common.collect.Sets;
import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
+import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectSortedMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap.Entry;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntIterator;
import it.unimi.dsi.fastutil.ints.IntList;
+import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
+import it.unimi.dsi.fastutil.ints.IntSet;
import it.unimi.dsi.fastutil.longs.Long2ReferenceMap;
import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenHashMap;
@@ -2147,6 +2156,9 @@
private boolean isTypeInaccessibleInCurrentContext(DexType type, DexEncodedMethod context) {
DexType baseType = type.toBaseType(appInfo.dexItemFactory);
+ if (baseType.isPrimitiveType()) {
+ return false;
+ }
DexClass clazz = definitionFor(baseType);
if (clazz == null) {
// Conservatively say yes.
@@ -3640,6 +3652,132 @@
assert code.isConsistentSSA();
}
+ /**
+ * Remove moves that are not actually used by instructions in exiting paths. These moves can arise
+ * due to debug local info needing a particular value and the live-interval for it then moves it
+ * back into the properly assigned register. If the register is only used for debug purposes, it
+ * is safe to just remove the move and update the local information accordingly.
+ */
+ public static void removeUnneededMovesOnExitingPaths(
+ IRCode code, LinearScanRegisterAllocator allocator) {
+ if (!code.options.debug) {
+ return;
+ }
+ for (BasicBlock block : code.blocks) {
+ // Skip non-exit blocks.
+ if (!block.getSuccessors().isEmpty()) {
+ continue;
+ }
+ // Skip blocks with no locals at entry.
+ Int2ReferenceMap<DebugLocalInfo> localsAtEntry = block.getLocalsAtEntry();
+ if (localsAtEntry == null || localsAtEntry.isEmpty()) {
+ continue;
+ }
+ // Find the locals state after spill moves.
+ DebugLocalsChange postSpillLocalsChange = null;
+ for (Instruction instruction : block.getInstructions()) {
+ if (instruction.getNumber() != -1 || postSpillLocalsChange != null) {
+ break;
+ }
+ postSpillLocalsChange = instruction.asDebugLocalsChange();
+ }
+ // Skip if the locals state did not change.
+ if (postSpillLocalsChange == null
+ || !postSpillLocalsChange.apply(new Int2ReferenceOpenHashMap<>(localsAtEntry))) {
+ continue;
+ }
+ // Collect the moves that can safely be removed.
+ Set<Move> unneededMoves = computeUnneededMoves(block, postSpillLocalsChange, allocator);
+ if (unneededMoves.isEmpty()) {
+ continue;
+ }
+ Int2IntMap previousMapping = new Int2IntOpenHashMap();
+ Int2IntMap mapping = new Int2IntOpenHashMap();
+ ListIterator<Instruction> it = block.getInstructions().listIterator();
+ while (it.hasNext()) {
+ Instruction instruction = it.next();
+ if (instruction.isMove()) {
+ Move move = instruction.asMove();
+ if (unneededMoves.contains(move)) {
+ int dst = allocator.getRegisterForValue(move.dest(), move.getNumber());
+ int src = allocator.getRegisterForValue(move.src(), move.getNumber());
+ int mappedSrc = mapping.getOrDefault(src, src);
+ mapping.put(dst, mappedSrc);
+ it.remove();
+ }
+ } else if (instruction.isDebugLocalsChange()) {
+ DebugLocalsChange change = instruction.asDebugLocalsChange();
+ updateDebugLocalsRegisterMap(previousMapping, change.getEnding());
+ updateDebugLocalsRegisterMap(mapping, change.getStarting());
+ previousMapping = mapping;
+ mapping = new Int2IntOpenHashMap(previousMapping);
+ }
+ }
+ }
+ }
+
+ private static Set<Move> computeUnneededMoves(
+ BasicBlock block,
+ DebugLocalsChange postSpillLocalsChange,
+ LinearScanRegisterAllocator allocator) {
+ Set<Move> unneededMoves = Sets.newIdentityHashSet();
+ IntSet usedRegisters = new IntOpenHashSet();
+ IntSet clobberedRegisters = new IntOpenHashSet();
+ // Backwards instruction scan collecting the registers used by actual instructions.
+ boolean inEntrySpillMoves = false;
+ InstructionListIterator it = block.listIterator(block.getInstructions().size());
+ while (it.hasPrevious()) {
+ Instruction instruction = it.previous();
+ if (instruction == postSpillLocalsChange) {
+ inEntrySpillMoves = true;
+ }
+ // If this is a move in the block-entry spill moves check if it is unneeded.
+ if (inEntrySpillMoves && instruction.isMove()) {
+ Move move = instruction.asMove();
+ int dst = allocator.getRegisterForValue(move.dest(), move.getNumber());
+ int src = allocator.getRegisterForValue(move.src(), move.getNumber());
+ if (!usedRegisters.contains(dst) && !clobberedRegisters.contains(src)) {
+ unneededMoves.add(move);
+ continue;
+ }
+ }
+ if (instruction.outValue() != null && instruction.outValue().needsRegister()) {
+ int register =
+ allocator.getRegisterForValue(instruction.outValue(), instruction.getNumber());
+ // The register is defined anew, so uses before this are on distinct values.
+ usedRegisters.remove(register);
+ // Mark it clobbered to avoid any uses in locals after this point to become invalid.
+ clobberedRegisters.add(register);
+ }
+ if (!instruction.inValues().isEmpty()) {
+ for (Value inValue : instruction.inValues()) {
+ if (inValue.needsRegister()) {
+ int register = allocator.getRegisterForValue(inValue, instruction.getNumber());
+ // Record the register as being used.
+ usedRegisters.add(register);
+ }
+ }
+ }
+ }
+ return unneededMoves;
+ }
+
+ private static void updateDebugLocalsRegisterMap(
+ Int2IntMap mapping, Int2ReferenceMap<DebugLocalInfo> locals) {
+ // If nothing is mapped nothing needs to be changed.
+ if (mapping.isEmpty()) {
+ return;
+ }
+ // Locals is final, so we copy and clear it during update.
+ Int2ReferenceMap<DebugLocalInfo> copy = new Int2ReferenceOpenHashMap<>(locals);
+ locals.clear();
+ for (Entry<DebugLocalInfo> entry : copy.int2ReferenceEntrySet()) {
+ int oldRegister = entry.getIntKey();
+ int newRegister = mapping.getOrDefault(oldRegister, oldRegister);
+ locals.put(newRegister, entry.getValue());
+ }
+ }
+
// Removes calls to Throwable.addSuppressed(Throwable) and rewrites
// Throwable.getSuppressed() into new Throwable[0].
//
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
index 99f9731..4da9f8d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
@@ -17,13 +17,11 @@
import com.android.tools.r8.ir.analysis.type.Nullability;
import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
-import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.ConstNumber;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.InstancePut;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionIterator;
-import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.StaticGet;
import com.android.tools.r8.ir.code.StaticPut;
@@ -31,7 +29,6 @@
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.android.tools.r8.shaking.ProguardMemberRule;
import com.google.common.collect.Sets;
-import java.util.ListIterator;
import java.util.Set;
import java.util.function.Predicate;
@@ -136,160 +133,151 @@
public void rewriteWithConstantValues(
IRCode code, DexType callingContext, Predicate<DexEncodedMethod> isProcessedConcurrently) {
Set<Value> affectedValues = Sets.newIdentityHashSet();
- ListIterator<BasicBlock> blocks = code.blocks.listIterator();
- while (blocks.hasNext()) {
- BasicBlock block = blocks.next();
- InstructionListIterator iterator = block.listIterator();
- while (iterator.hasNext()) {
- Instruction current = iterator.next();
- if (current.isInvokeMethod()) {
- InvokeMethod invoke = current.asInvokeMethod();
- DexMethod invokedMethod = invoke.getInvokedMethod();
- DexType invokedHolder = invokedMethod.getHolder();
- if (!invokedHolder.isClassType()) {
- continue;
- }
- // TODO(70550443): Maybe check all methods here.
- DexEncodedMethod definition = appInfo
- .lookup(invoke.getType(), invokedMethod, callingContext);
+ InstructionIterator iterator = code.instructionIterator();
+ while (iterator.hasNext()) {
+ Instruction current = iterator.next();
+ if (current.isInvokeMethod()) {
+ InvokeMethod invoke = current.asInvokeMethod();
+ DexMethod invokedMethod = invoke.getInvokedMethod();
+ DexType invokedHolder = invokedMethod.getHolder();
+ if (!invokedHolder.isClassType()) {
+ continue;
+ }
+ // TODO(70550443): Maybe check all methods here.
+ DexEncodedMethod definition = appInfo
+ .lookup(invoke.getType(), invokedMethod, callingContext);
- boolean invokeReplaced = false;
- ProguardMemberRuleLookup lookup = lookupMemberRule(definition);
- if (lookup != null) {
- if (lookup.type == RuleType.ASSUME_NO_SIDE_EFFECTS
- && (invoke.outValue() == null || !invoke.outValue().isUsed())) {
- // Remove invoke if marked as having no side effects and the return value is not used.
- iterator.remove();
- invokeReplaced = true;
- } else if (invoke.outValue() != null && invoke.outValue().isUsed()) {
- // Check to see if a constant value can be assumed.
- Instruction replacement =
- constantReplacementFromProguardRule(lookup.rule, code, invoke);
- if (replacement != null) {
- affectedValues.add(replacement.outValue());
- replaceInstructionFromProguardRule(lookup.type, iterator, current, replacement);
- invokeReplaced = true;
- } else {
- // Check to see if a value range can be assumed.
- setValueRangeFromProguardRule(lookup.rule, current.outValue());
- }
- }
- }
-
- // If no Proguard rule could replace the instruction check for knowledge about the
- // return value.
- if (!invokeReplaced && invoke.outValue() != null) {
- DexEncodedMethod target = invoke.lookupSingleTarget(appInfo, callingContext);
- if (target != null) {
- if (target.getOptimizationInfo().neverReturnsNull() && invoke.outValue()
- .canBeNull()) {
- Value knownToBeNonNullValue = invoke.outValue();
- knownToBeNonNullValue.markNeverNull();
- TypeLatticeElement typeLattice = knownToBeNonNullValue.getTypeLattice();
- assert typeLattice.isNullable() && typeLattice.isReference();
- knownToBeNonNullValue.narrowing(appInfo, typeLattice.asNonNullable());
- affectedValues.addAll(knownToBeNonNullValue.affectedValues());
- }
- if (target.getOptimizationInfo().returnsConstant()) {
- long constant = target.getOptimizationInfo().getReturnedConstant();
- ConstNumber replacement = createConstNumberReplacement(
- code, constant, invoke.outValue().getTypeLattice(), invoke.getLocalInfo());
- affectedValues.add(replacement.outValue());
- invoke.outValue().replaceUsers(replacement.outValue());
- invoke.setOutValue(null);
- replacement.setPosition(invoke.getPosition());
- invoke.moveDebugValues(replacement);
- if (current.getBlock().hasCatchHandlers()) {
- iterator.split(code, blocks).listIterator().add(replacement);
- } else {
- iterator.add(replacement);
- }
- }
- }
- }
- } else if (current.isInstancePut()) {
- InstancePut instancePut = current.asInstancePut();
- DexField field = instancePut.getField();
- DexEncodedField target = appInfo.lookupInstanceTarget(field.getHolder(), field);
- if (target != null) {
- // Remove writes to dead (i.e. never read) fields.
- if (!isFieldRead(target, false) && instancePut.object().isNeverNull()) {
- iterator.remove();
- }
- }
- } else if (current.isStaticGet()) {
- StaticGet staticGet = current.asStaticGet();
- DexField field = staticGet.getField();
- DexEncodedField target = appInfo.lookupStaticTarget(field.getHolder(), field);
- ProguardMemberRuleLookup lookup = null;
- if (target != null) {
- // Check if a this value is known const.
- Instruction replacement = target.valueAsConstInstruction(appInfo, staticGet.dest());
- if (replacement == null) {
- lookup = lookupMemberRule(target);
- if (lookup != null) {
- replacement = constantReplacementFromProguardRule(lookup.rule, code, staticGet);
- }
- }
- if (replacement == null) {
- // If no const replacement was found, at least store the range information.
- if (lookup != null) {
- setValueRangeFromProguardRule(lookup.rule, staticGet.dest());
- }
- }
+ boolean invokeReplaced = false;
+ ProguardMemberRuleLookup lookup = lookupMemberRule(definition);
+ if (lookup != null) {
+ if (lookup.type == RuleType.ASSUME_NO_SIDE_EFFECTS
+ && (invoke.outValue() == null || !invoke.outValue().isUsed())) {
+ // Remove invoke if marked as having no side effects and the return value is not used.
+ iterator.remove();
+ invokeReplaced = true;
+ } else if (invoke.outValue() != null && invoke.outValue().isUsed()) {
+ // Check to see if a constant value can be assumed.
+ Instruction replacement =
+ constantReplacementFromProguardRule(lookup.rule, code, invoke);
if (replacement != null) {
affectedValues.add(replacement.outValue());
- // Ignore assumenosideeffects for fields.
- if (lookup != null && lookup.type == RuleType.ASSUME_VALUES) {
- replaceInstructionFromProguardRule(lookup.type, iterator, current, replacement);
- } else {
- iterator.replaceCurrentInstruction(replacement);
- }
- } else if (staticGet.dest() != null) {
- // In case the class holder of this static field satisfying following criteria:
- // -- cannot trigger other static initializer except for its own
- // -- is final
- // -- has a class initializer which is classified as trivial
- // (see CodeRewriter::computeClassInitializerInfo) and
- // initializes the field being accessed
- //
- // ... and the field itself is not pinned by keep rules (in which case it might
- // be updated outside the class constructor, e.g. via reflections), it is safe
- // to assume that the static-get instruction reads the value it initialized value
- // in class initializer and is never null.
- //
- DexClass holderDefinition = appInfo.definitionFor(field.getHolder());
- if (holderDefinition != null
- && holderDefinition.accessFlags.isFinal()
- && !appInfo.canTriggerStaticInitializer(field.getHolder(), true)) {
- Value outValue = staticGet.dest();
- DexEncodedMethod classInitializer = holderDefinition.getClassInitializer();
- if (classInitializer != null && !isProcessedConcurrently.test(classInitializer)) {
- TrivialInitializer info =
- classInitializer.getOptimizationInfo().getTrivialInitializerInfo();
- if (info != null
- && ((TrivialClassInitializer) info).field == field
- && !appInfo.isPinned(field)
- && outValue.canBeNull()) {
- outValue.markNeverNull();
- TypeLatticeElement typeLattice = outValue.getTypeLattice();
- assert typeLattice.isNullable() && typeLattice.isReference();
- outValue.narrowing(appInfo, typeLattice.asNonNullable());
- affectedValues.addAll(outValue.affectedValues());
- }
+ replaceInstructionFromProguardRule(lookup.type, iterator, current, replacement);
+ invokeReplaced = true;
+ } else {
+ // Check to see if a value range can be assumed.
+ setValueRangeFromProguardRule(lookup.rule, current.outValue());
+ }
+ }
+ }
+
+ // If no Proguard rule could replace the instruction check for knowledge about the
+ // return value.
+ if (!invokeReplaced && invoke.outValue() != null) {
+ DexEncodedMethod target = invoke.lookupSingleTarget(appInfo, callingContext);
+ if (target != null) {
+ if (target.getOptimizationInfo().neverReturnsNull() && invoke.outValue().canBeNull()) {
+ Value knownToBeNonNullValue = invoke.outValue();
+ knownToBeNonNullValue.markNeverNull();
+ TypeLatticeElement typeLattice = knownToBeNonNullValue.getTypeLattice();
+ assert typeLattice.isNullable() && typeLattice.isReference();
+ knownToBeNonNullValue.narrowing(appInfo, typeLattice.asNonNullable());
+ affectedValues.addAll(knownToBeNonNullValue.affectedValues());
+ }
+ if (target.getOptimizationInfo().returnsConstant()) {
+ long constant = target.getOptimizationInfo().getReturnedConstant();
+ ConstNumber replacement = createConstNumberReplacement(
+ code, constant, invoke.outValue().getTypeLattice(), invoke.getLocalInfo());
+ affectedValues.add(replacement.outValue());
+ invoke.outValue().replaceUsers(replacement.outValue());
+ invoke.setOutValue(null);
+ replacement.setPosition(invoke.getPosition());
+ invoke.moveDebugValues(replacement);
+ iterator.add(replacement);
+ }
+ }
+ }
+ } else if (current.isInstancePut()) {
+ InstancePut instancePut = current.asInstancePut();
+ DexField field = instancePut.getField();
+ DexEncodedField target = appInfo.lookupInstanceTarget(field.getHolder(), field);
+ if (target != null) {
+ // Remove writes to dead (i.e. never read) fields.
+ if (!isFieldRead(target, false) && instancePut.object().isNeverNull()) {
+ iterator.remove();
+ }
+ }
+ } else if (current.isStaticGet()) {
+ StaticGet staticGet = current.asStaticGet();
+ DexField field = staticGet.getField();
+ DexEncodedField target = appInfo.lookupStaticTarget(field.getHolder(), field);
+ ProguardMemberRuleLookup lookup = null;
+ if (target != null) {
+ // Check if a this value is known const.
+ Instruction replacement = target.valueAsConstInstruction(appInfo, staticGet.dest());
+ if (replacement == null) {
+ lookup = lookupMemberRule(target);
+ if (lookup != null) {
+ replacement = constantReplacementFromProguardRule(lookup.rule, code, staticGet);
+ }
+ }
+ if (replacement == null) {
+ // If no const replacement was found, at least store the range information.
+ if (lookup != null) {
+ setValueRangeFromProguardRule(lookup.rule, staticGet.dest());
+ }
+ }
+ if (replacement != null) {
+ affectedValues.add(replacement.outValue());
+ // Ignore assumenosideeffects for fields.
+ if (lookup != null && lookup.type == RuleType.ASSUME_VALUES) {
+ replaceInstructionFromProguardRule(lookup.type, iterator, current, replacement);
+ } else {
+ iterator.replaceCurrentInstruction(replacement);
+ }
+ } else if (staticGet.dest() != null) {
+ // In case the class holder of this static field satisfying following criteria:
+ // -- cannot trigger other static initializer except for its own
+ // -- is final
+ // -- has a class initializer which is classified as trivial
+ // (see CodeRewriter::computeClassInitializerInfo) and
+ // initializes the field being accessed
+ //
+ // ... and the field itself is not pinned by keep rules (in which case it might
+ // be updated outside the class constructor, e.g. via reflections), it is safe
+ // to assume that the static-get instruction reads the value it initialized value
+ // in class initializer and is never null.
+ //
+ DexClass holderDefinition = appInfo.definitionFor(field.getHolder());
+ if (holderDefinition != null
+ && holderDefinition.accessFlags.isFinal()
+ && !appInfo.canTriggerStaticInitializer(field.getHolder(), true)) {
+ Value outValue = staticGet.dest();
+ DexEncodedMethod classInitializer = holderDefinition.getClassInitializer();
+ if (classInitializer != null && !isProcessedConcurrently.test(classInitializer)) {
+ TrivialInitializer info =
+ classInitializer.getOptimizationInfo().getTrivialInitializerInfo();
+ if (info != null
+ && ((TrivialClassInitializer) info).field == field
+ && !appInfo.isPinned(field)
+ && outValue.canBeNull()) {
+ outValue.markNeverNull();
+ TypeLatticeElement typeLattice = outValue.getTypeLattice();
+ assert typeLattice.isNullable() && typeLattice.isReference();
+ outValue.narrowing(appInfo, typeLattice.asNonNullable());
+ affectedValues.addAll(outValue.affectedValues());
}
}
}
}
- } else if (current.isStaticPut()) {
- StaticPut staticPut = current.asStaticPut();
- DexField field = staticPut.getField();
- DexEncodedField target = appInfo.lookupStaticTarget(field.getHolder(), field);
- if (target != null) {
- // Remove writes to dead (i.e. never read) fields.
- if (!isFieldRead(target, true)) {
- iterator.removeOrReplaceByDebugLocalRead();
- }
+ }
+ } else if (current.isStaticPut()) {
+ StaticPut staticPut = current.asStaticPut();
+ DexField field = staticPut.getField();
+ DexEncodedField target = appInfo.lookupStaticTarget(field.getHolder(), field);
+ if (target != null) {
+ // Remove writes to dead (i.e. never read) fields.
+ if (!isFieldRead(target, true)) {
+ iterator.removeOrReplaceByDebugLocalRead();
}
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index 7375303..cdf37c4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -113,6 +113,9 @@
eligibleClass =
root.isNewInstance() ? root.asNewInstance().clazz : root.asStaticGet().getField().type;
+ if (!eligibleClass.isClassType()) {
+ return false;
+ }
eligibleClassDefinition = appInfo.definitionFor(eligibleClass);
if (eligibleClassDefinition == null && lambdaRewriter != null) {
// Check if the class is synthesized for a desugared lambda
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerGraphLense.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerGraphLense.java
index 9bdd55e..6613534 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerGraphLense.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerGraphLense.java
@@ -36,14 +36,12 @@
}
@Override
- protected Type mapInvocationType(
- DexMethod newMethod, DexMethod originalMethod,
- DexEncodedMethod context, Type type) {
+ protected Type mapInvocationType(DexMethod newMethod, DexMethod originalMethod, Type type) {
if (methodMap.get(originalMethod) == newMethod) {
assert type == Type.VIRTUAL || type == Type.DIRECT;
return Type.STATIC;
}
- return super.mapInvocationType(newMethod, originalMethod, context, type);
+ return super.mapInvocationType(newMethod, originalMethod, type);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/jar/JarRegisterEffectsVisitor.java b/src/main/java/com/android/tools/r8/jar/JarRegisterEffectsVisitor.java
index 3395753..b445efc 100644
--- a/src/main/java/com/android/tools/r8/jar/JarRegisterEffectsVisitor.java
+++ b/src/main/java/com/android/tools/r8/jar/JarRegisterEffectsVisitor.java
@@ -39,7 +39,7 @@
if (opcode == org.objectweb.asm.Opcodes.NEW) {
registry.registerNewInstance(type);
} else if (opcode == Opcodes.CHECKCAST) {
- registry.registerCheckCast(type);
+ registry.registerCheckCast(type);
} else {
registry.registerTypeReference(type);
}
diff --git a/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java b/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
index b59e3e9..72fc987 100644
--- a/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
+++ b/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
@@ -128,7 +128,7 @@
return false;
}
lenseBuilder.add(encodedMethod.method);
- accessFlags.setFinal();
+ accessFlags.promoteToFinal();
accessFlags.promoteToPublic();
// Although the current method became public, it surely has the single virtual target.
encodedMethod.method.setSingleVirtualMethodCache(
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingLense.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingLense.java
index 98a0da4..5e19d48 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingLense.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingLense.java
@@ -5,7 +5,6 @@
package com.android.tools.r8.optimize;
import com.android.tools.r8.graph.AppInfo;
-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.GraphLense;
@@ -47,11 +46,8 @@
return new Builder(appInfo);
}
-
@Override
- protected Type mapInvocationType(
- DexMethod newMethod, DexMethod originalMethod, DexEncodedMethod context, Type type) {
- return super.mapVirtualInterfaceInvocationTypes(
- appInfo, newMethod, originalMethod, context, type);
+ protected Type mapInvocationType(DexMethod newMethod, DexMethod originalMethod, Type type) {
+ return super.mapVirtualInterfaceInvocationTypes(appInfo, newMethod, originalMethod, type);
}
}
diff --git a/src/main/java/com/android/tools/r8/optimize/PublicizerLense.java b/src/main/java/com/android/tools/r8/optimize/PublicizerLense.java
index 2db2dce..c685d84 100644
--- a/src/main/java/com/android/tools/r8/optimize/PublicizerLense.java
+++ b/src/main/java/com/android/tools/r8/optimize/PublicizerLense.java
@@ -15,6 +15,7 @@
import java.util.Set;
final class PublicizerLense extends NestedGraphLense {
+
private final AppView appView;
private final Set<DexMethod> publicizedMethods;
@@ -34,8 +35,7 @@
}
@Override
- public GraphLenseLookupResult lookupMethod(
- DexMethod method, DexEncodedMethod context, Type type) {
+ public GraphLenseLookupResult lookupMethod(DexMethod method, DexMethod context, Type type) {
GraphLenseLookupResult previous = previousLense.lookupMethod(method, context, type);
method = previous.getMethod();
type = previous.getType();
@@ -46,7 +46,7 @@
return super.lookupMethod(method, context, type);
}
- private boolean publicizedMethodIsPresentOnHolder(DexMethod method, DexEncodedMethod context) {
+ private boolean publicizedMethodIsPresentOnHolder(DexMethod method, DexMethod context) {
GraphLenseLookupResult lookup =
appView.graphLense().lookupMethod(method, context, Type.VIRTUAL);
DexMethod signatureInCurrentWorld = lookup.getMethod();
diff --git a/src/main/java/com/android/tools/r8/references/ArrayReference.java b/src/main/java/com/android/tools/r8/references/ArrayReference.java
index 10d2a31..96cb926 100644
--- a/src/main/java/com/android/tools/r8/references/ArrayReference.java
+++ b/src/main/java/com/android/tools/r8/references/ArrayReference.java
@@ -10,12 +10,13 @@
@Keep
public final class ArrayReference implements TypeReference {
- private final int dimentions;
+ private final int dimensions;
private final TypeReference baseType;
private String descriptor;
- private ArrayReference(int dimentions, TypeReference baseType, String descriptor) {
- this.dimentions = dimentions;
+ private ArrayReference(int dimensions, TypeReference baseType, String descriptor) {
+ assert dimensions > 0;
+ this.dimensions = dimensions;
this.baseType = baseType;
this.descriptor = descriptor;
}
@@ -23,15 +24,18 @@
static ArrayReference fromDescriptor(String descriptor) {
for (int i = 0; i < descriptor.length(); i++) {
if (descriptor.charAt(i) != '[') {
- return new ArrayReference(
- i, Reference.typeFromDescriptor(descriptor.substring(i)), descriptor);
+ if (i > 0) {
+ return new ArrayReference(
+ i, Reference.typeFromDescriptor(descriptor.substring(i)), descriptor);
+ }
+ break;
}
}
throw new Unreachable("Invalid array type descriptor: " + descriptor);
}
- public int getDimentions() {
- return dimentions;
+ public int getDimensions() {
+ return dimensions;
}
public TypeReference getMemberType() {
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 adc6979..8560ba7 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -2414,6 +2414,10 @@
// https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-6.html#jvms-6.5.invokevirtual
assert method != null;
assert refinedReceiverType.isSubtypeOf(method.holder, this);
+ if (method.holder.isArrayType()) {
+ assert method.name == dexItemFactory.cloneMethodName;
+ return null;
+ }
DexClass holder = definitionFor(method.holder);
if (holder == null || holder.isLibraryClass() || holder.isInterface()) {
return null;
diff --git a/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java b/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java
index 543dcc4..71b3d53 100644
--- a/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java
+++ b/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java
@@ -19,7 +19,6 @@
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.UseRegistry;
-import com.google.common.collect.ImmutableSet;
import java.util.Set;
import java.util.function.Consumer;
@@ -61,35 +60,23 @@
boolean value = false;
}
- public static boolean hasReferencesOutside(
- AppInfoWithSubtyping appInfo, DexProgramClass clazz, Set<DexType> types) {
- BooleanBox result = new BooleanBox();
-
- new MainDexDirectReferenceTracer(appInfo, type -> {
- if (!types.contains(type)) {
- DexClass cls = appInfo.definitionFor(type);
- if (cls != null && !cls.isLibraryClass()) {
- result.value = true;
- }
- }
- }).run(ImmutableSet.of(clazz.type));
-
- return result.value;
- }
-
public static boolean hasReferencesOutsideFromCode(
AppInfoWithSubtyping appInfo, DexEncodedMethod method, Set<DexType> classes) {
BooleanBox result = new BooleanBox();
- new MainDexDirectReferenceTracer(appInfo, type -> {
- if (!classes.contains(type)) {
- DexClass cls = appInfo.definitionFor(type);
- if (cls != null && !cls.isLibraryClass()) {
- result.value = true;
- }
- }
- }).runOnCode(method);
+ new MainDexDirectReferenceTracer(
+ appInfo,
+ type -> {
+ DexType baseType = type.toBaseType(appInfo.dexItemFactory);
+ if (baseType.isClassType() && !classes.contains(baseType)) {
+ DexClass cls = appInfo.definitionFor(baseType);
+ if (cls != null && !cls.isLibraryClass()) {
+ result.value = true;
+ }
+ }
+ })
+ .runOnCode(method);
return result.value;
}
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 898d06c..ff196e3 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -843,6 +843,9 @@
}
private void includeDescriptor(DexDefinition item, DexType type, ProguardKeepRule context) {
+ if (type.isVoidType()) {
+ return;
+ }
if (type.isArrayType()) {
type = type.toBaseType(application.dexItemFactory);
}
diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
index 6cf0da0..f502692 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -92,7 +92,7 @@
// corresponding methods in this class. This might happen if we only keep this
// class around for its constants.
// For now, we remove the final flag to still be able to mark it abstract.
- clazz.accessFlags.unsetFinal();
+ clazz.accessFlags.demoteFromFinal();
}
clazz.accessFlags.setAbstract();
}
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 45ca1af..15dcafa 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -6,7 +6,6 @@
import static com.android.tools.r8.ir.code.Invoke.Type.DIRECT;
import static com.android.tools.r8.ir.code.Invoke.Type.STATIC;
-import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.AppInfo.ResolutionResult;
@@ -53,6 +52,7 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
import com.google.common.collect.Streams;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
@@ -211,13 +211,13 @@
private final Set<DexProgramClass> mergeCandidates = new LinkedHashSet<>();
// Map from source class to target class.
- private final Map<DexType, DexType> mergedClasses = new HashMap<>();
+ private final Map<DexType, DexType> mergedClasses = new IdentityHashMap<>();
// Map from target class to the super classes that have been merged into the target class.
- private final Map<DexType, Set<DexType>> mergedClassesInverse = new HashMap<>();
+ private final Map<DexType, Set<DexType>> mergedClassesInverse = new IdentityHashMap<>();
// Set of types that must not be merged into their subtype.
- private final Set<DexType> pinnedTypes = new HashSet<>();
+ private final Set<DexType> pinnedTypes = Sets.newIdentityHashSet();
// The resulting graph lense that should be used after class merging.
private final VerticalClassMergerGraphLense.Builder renamedMembersLense;
@@ -336,15 +336,16 @@
}
private void markTypeAsPinned(DexType type, AbortReason reason) {
- if (appInfo.isPinned(type)) {
+ DexType baseType = type.toBaseType(appInfo.dexItemFactory);
+ if (!baseType.isClassType() || appInfo.isPinned(baseType)) {
// We check for the case where the type is pinned according to appInfo.isPinned,
// so we only need to add it here if it is not the case.
return;
}
- DexClass clazz = appInfo.definitionFor(type);
+ DexClass clazz = appInfo.definitionFor(baseType);
if (clazz != null && clazz.isProgramClass()) {
- boolean changed = pinnedTypes.add(type);
+ boolean changed = pinnedTypes.add(baseType);
if (Log.ENABLED) {
if (changed && isMergeCandidate(clazz.asProgramClass(), ImmutableSet.of())) {
@@ -423,7 +424,8 @@
return false;
}
DexClass targetClass = appInfo.definitionFor(clazz.type.getSingleSubtype());
- if (clazz.hasClassInitializer() && targetClass.hasClassInitializer()) {
+ if ((clazz.hasClassInitializer() && targetClass.hasClassInitializer())
+ || targetClass.classInitializationMayHaveSideEffects(appInfo, type -> type == clazz.type)) {
// TODO(herhut): Handle class initializers.
if (Log.ENABLED) {
AbortReason.STATIC_INITIALIZERS.printLogMessageForClass(clazz);
@@ -685,7 +687,7 @@
if (Log.ENABLED) {
Log.debug(getClass(), "Merged %d classes.", mergedClasses.size());
}
- return renamedMembersLense.build(graphLense, mergedClasses, synthesizedBridges, appInfo);
+ return renamedMembersLense.build(graphLense, mergedClasses, appInfo);
}
private boolean methodResolutionMayChange(DexClass source, DexClass target) {
@@ -742,10 +744,6 @@
return false;
}
- private boolean hasReferencesOutside(DexProgramClass clazz, Set<DexType> types) {
- return MainDexDirectReferenceTracer.hasReferencesOutside(appInfo, clazz, types);
- }
-
private void mergeClassIfPossible(DexProgramClass clazz) {
if (!mergeCandidates.contains(clazz)) {
return;
@@ -1214,7 +1212,6 @@
// The bridge is now the public method serving the role of the original method, and should
// reflect that this method was publicized.
assert bridge.accessFlags.isPromotedToPublic();
- method.accessFlags.unsetPromotedToPublic();
}
return bridge;
}
@@ -1409,7 +1406,6 @@
private static void makePrivate(DexEncodedMethod method) {
assert !method.accessFlags.isAbstract();
- method.accessFlags.unsetPromotedToPublic();
method.accessFlags.unsetPublic();
method.accessFlags.unsetProtected();
method.accessFlags.setPrivate();
@@ -1432,42 +1428,19 @@
for (DexProgramClass clazz : appInfo.classes()) {
clazz.setDirectMethods(substituteTypesIn(clazz.directMethods()));
clazz.setVirtualMethods(substituteTypesIn(clazz.virtualMethods()));
- clazz.setVirtualMethods(removeDupes(clazz.virtualMethods()));
clazz.setStaticFields(substituteTypesIn(clazz.staticFields()));
clazz.setInstanceFields(substituteTypesIn(clazz.instanceFields()));
}
+ for (SynthesizedBridgeCode synthesizedBridge : synthesizedBridges) {
+ synthesizedBridge.updateMethodSignatures(this::fixupMethod);
+ }
// Record type renamings so check-cast and instance-of checks are also fixed.
for (DexType type : mergedClasses.keySet()) {
- DexType fixed = fixupType(type);
- lense.map(type, fixed);
+ lense.map(type, fixupType(type));
}
return lense.build(application.dexItemFactory, graphLense);
}
- private DexEncodedMethod[] removeDupes(DexEncodedMethod[] methods) {
- if (methods == null) {
- return null;
- }
- Map<DexMethod, DexEncodedMethod> filtered = new IdentityHashMap<>();
- for (DexEncodedMethod method : methods) {
- DexEncodedMethod previous = filtered.put(method.method, method);
- if (previous != null) {
- if (!previous.accessFlags.isBridge()) {
- if (!method.accessFlags.isBridge()) {
- throw new CompilationError(
- "Class merging produced invalid result on: " + previous.toSourceString());
- } else {
- filtered.put(previous.method, previous);
- }
- }
- }
- }
- if (filtered.size() == methods.length) {
- return methods;
- }
- return filtered.values().toArray(DexEncodedMethod.EMPTY_ARRAY);
- }
-
private DexEncodedMethod[] substituteTypesIn(DexEncodedMethod[] methods) {
if (methods == null) {
return null;
@@ -1475,12 +1448,9 @@
for (int i = 0; i < methods.length; i++) {
DexEncodedMethod encodedMethod = methods[i];
DexMethod method = encodedMethod.method;
- DexProto newProto = getUpdatedProto(method.proto);
- DexType newHolder = fixupType(method.holder);
- DexMethod newMethod = application.dexItemFactory.createMethod(newHolder, newProto,
- method.name);
- if (newMethod != encodedMethod.method) {
- lense.move(encodedMethod.method, newMethod);
+ DexMethod newMethod = fixupMethod(method);
+ if (newMethod != method) {
+ lense.move(method, newMethod);
methods[i] = encodedMethod.toTypeSubstitutedMethod(newMethod);
}
}
@@ -1505,7 +1475,12 @@
return fields;
}
- private DexProto getUpdatedProto(DexProto proto) {
+ private DexMethod fixupMethod(DexMethod method) {
+ return application.dexItemFactory.createMethod(
+ fixupType(method.holder), fixupProto(method.proto), method.name);
+ }
+
+ private DexProto fixupProto(DexProto proto) {
DexProto result = protoFixupCache.get(proto);
if (result == null) {
DexType returnType = fixupType(proto.returnType);
@@ -1522,12 +1497,13 @@
DexType fixed = fixupType(base);
if (base == fixed) {
return type;
- } else {
- return type.replaceBaseType(fixed, application.dexItemFactory);
}
+ return type.replaceBaseType(fixed, application.dexItemFactory);
}
- while (mergedClasses.containsKey(type)) {
- type = mergedClasses.get(type);
+ if (type.isClassType()) {
+ while (mergedClasses.containsKey(type)) {
+ type = mergedClasses.get(type);
+ }
}
return type;
}
@@ -1680,6 +1656,11 @@
}
@Override
+ public DexType getOriginalType(DexType type) {
+ throw new Unreachable();
+ }
+
+ @Override
public DexField getOriginalFieldSignature(DexField field) {
throw new Unreachable();
}
@@ -1705,8 +1686,7 @@
}
@Override
- public GraphLenseLookupResult lookupMethod(
- DexMethod method, DexEncodedMethod context, Type type) {
+ public GraphLenseLookupResult lookupMethod(DexMethod method, DexMethod context, Type type) {
// First look up the method using the existing graph lense (for example, the type will have
// changed if the method was publicized by ClassAndMemberPublicizer).
GraphLenseLookupResult lookup = graphLense.lookupMethod(method, context, type);
@@ -1750,7 +1730,7 @@
public static class IllegalAccessDetector extends UseRegistry {
private boolean foundIllegalAccess = false;
- private DexEncodedMethod context = null;
+ private DexMethod context = null;
private final AppView<? extends AppInfo> appView;
private final DexClass source;
@@ -1766,7 +1746,7 @@
}
public void setContext(DexEncodedMethod context) {
- this.context = context;
+ this.context = context.method;
}
private boolean checkFieldReference(DexField field) {
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
index 20a05ab..6576433 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
@@ -5,7 +5,6 @@
package com.android.tools.r8.shaking;
import com.android.tools.r8.graph.AppInfo;
-import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
@@ -14,17 +13,14 @@
import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
import com.android.tools.r8.ir.code.Invoke.Type;
-import com.android.tools.r8.shaking.VerticalClassMerger.SynthesizedBridgeCode;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import java.util.HashMap;
import java.util.IdentityHashMap;
-import java.util.List;
import java.util.Map;
import java.util.Set;
-import java.util.function.Function;
// This graph lense is instantiated during vertical class merging. The graph lense is context
// sensitive in the enclosing class of a given invoke *and* the type of the invoke (e.g., invoke-
@@ -55,7 +51,7 @@
private final Map<DexType, Map<DexMethod, GraphLenseLookupResult>>
contextualVirtualToDirectMethodMaps;
- private final Set<DexMethod> mergedMethods;
+ private Set<DexMethod> mergedMethods;
private final Map<DexMethod, DexMethod> originalMethodSignaturesForBridges;
public VerticalClassMergerGraphLense(
@@ -83,19 +79,29 @@
}
@Override
+ public DexType getOriginalType(DexType type) {
+ return previousLense.getOriginalType(type);
+ }
+
+ @Override
public DexMethod getOriginalMethodSignature(DexMethod method) {
return super.getOriginalMethodSignature(
originalMethodSignaturesForBridges.getOrDefault(method, method));
}
@Override
- public GraphLenseLookupResult lookupMethod(
- DexMethod method, DexEncodedMethod context, Type type) {
+ public GraphLenseLookupResult lookupMethod(DexMethod method, DexMethod context, Type type) {
assert isContextFreeForMethod(method) || (context != null && type != null);
- GraphLenseLookupResult previous = previousLense.lookupMethod(method, context, type);
- if (previous.getType() == Type.SUPER && !mergedMethods.contains(context.method)) {
+ DexMethod previousContext =
+ originalMethodSignaturesForBridges.containsKey(context)
+ ? originalMethodSignaturesForBridges.get(context)
+ : originalMethodSignatures != null
+ ? originalMethodSignatures.getOrDefault(context, context)
+ : context;
+ GraphLenseLookupResult previous = previousLense.lookupMethod(method, previousContext, type);
+ if (previous.getType() == Type.SUPER && !mergedMethods.contains(context)) {
Map<DexMethod, GraphLenseLookupResult> virtualToDirectMethodMap =
- contextualVirtualToDirectMethodMaps.get(context.method.holder);
+ contextualVirtualToDirectMethodMaps.get(context.holder);
if (virtualToDirectMethodMap != null) {
GraphLenseLookupResult lookup = virtualToDirectMethodMap.get(previous.getMethod());
if (lookup != null) {
@@ -113,10 +119,8 @@
}
@Override
- protected Type mapInvocationType(
- DexMethod newMethod, DexMethod originalMethod, DexEncodedMethod context, Type type) {
- return super.mapVirtualInterfaceInvocationTypes(
- appInfo, newMethod, originalMethod, context, type);
+ protected Type mapInvocationType(DexMethod newMethod, DexMethod originalMethod, Type type) {
+ return super.mapVirtualInterfaceInvocationTypes(appInfo, newMethod, originalMethod, type);
}
@Override
@@ -170,7 +174,6 @@
public GraphLense build(
GraphLense previousLense,
Map<DexType, DexType> mergedClasses,
- List<SynthesizedBridgeCode> synthesizedBridges,
AppInfo appInfo) {
if (fieldMap.isEmpty()
&& methodMap.isEmpty()
@@ -179,14 +182,6 @@
}
Map<DexProto, DexProto> cache = new HashMap<>();
BiMap<DexField, DexField> originalFieldSignatures = fieldMap.inverse();
- // Update all synthesized bridges.
- Function<DexMethod, DexMethod> synthesizedBridgeTransformer =
- method ->
- getMethodSignatureAfterClassMerging(
- method, mergedClasses, appInfo.dexItemFactory, cache);
- for (SynthesizedBridgeCode synthesizedBridge : synthesizedBridges) {
- synthesizedBridge.updateMethodSignatures(synthesizedBridgeTransformer);
- }
// Build new graph lense.
return new VerticalClassMergerGraphLense(
appInfo,
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 f34e059..4e66abf 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -825,7 +825,7 @@
//
// We used to insert a empty loop at the end, however, mediatek has an optimizer
// on lollipop devices that cannot deal with an unreachable infinite loop, so we
- // couldn't do that. See b/119895393.2
+ // couldn't do that. See b/119895393.
public boolean canHaveTracingPastInstructionsStreamBug() {
return minApiLevel < AndroidApiLevel.L.getLevel();
}
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 ec12f82..c9be1b8 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -28,6 +28,7 @@
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.naming.ClassNameMapper;
@@ -150,16 +151,17 @@
// At this point we don't know if we really need to add this class to the builder.
// It depends on whether any methods/fields are renamed or some methods contain positions.
// Create a supplier which creates a new, cached ClassNaming.Builder on-demand.
+ DexType originalType = graphLense.getOriginalType(clazz.type);
DexString renamedClassName = namingLens.lookupDescriptor(clazz.getType());
Supplier<ClassNaming.Builder> onDemandClassNamingBuilder =
Suppliers.memoize(
() ->
classNameMapperBuilder.classNamingBuilder(
DescriptorUtils.descriptorToJavaType(renamedClassName.toString()),
- clazz.toString()));
+ originalType.toSourceString()));
// If the class is renamed add it to the classNamingBuilder.
- addClassToClassNaming(clazz, renamedClassName, onDemandClassNamingBuilder);
+ addClassToClassNaming(originalType, renamedClassName, onDemandClassNamingBuilder);
// First transfer renamed fields to classNamingBuilder.
addFieldsToClassNaming(graphLense, namingLens, clazz, onDemandClassNamingBuilder);
@@ -306,10 +308,12 @@
}
@SuppressWarnings("ReturnValueIgnored")
- private static void addClassToClassNaming(DexProgramClass clazz, DexString renamedClassName,
+ private static void addClassToClassNaming(
+ DexType originalType,
+ DexString renamedClassName,
Supplier<Builder> onDemandClassNamingBuilder) {
// We do know we need to create a ClassNaming.Builder if the class itself had been renamed.
- if (!clazz.toString().equals(renamedClassName.toString())) {
+ if (originalType.descriptor != renamedClassName) {
// Not using return value, it's registered in classNameMapperBuilder
onDemandClassNamingBuilder.get();
}
diff --git a/src/main/keep-compatdx.txt b/src/main/keep-compatdx.txt
index 75a8012..7275f7f 100644
--- a/src/main/keep-compatdx.txt
+++ b/src/main/keep-compatdx.txt
@@ -4,3 +4,13 @@
-keep public class com.android.tools.r8.compatdx.CompatDx { public static void main(java.lang.String[]); }
-keepattributes LineNumberTable
+
+# JvmMetadataExtensions must be kept because it'll be used indirectly through java.util.ServiceLoader.
+-keep, allowobfuscation public class com.android.tools.r8.jetbrains.kotlinx.metadata.jvm.impl.JvmMetadataExtensions { public <init>(); }
+
+# For now we need to keep MetadataExtensions to avoid minifying (obfuscating) the name, because
+# '-adaptresourcefilenames' doesn't rename it (the package name is not encoded in directories).
+-keep public class com.android.tools.r8.jetbrains.kotlinx.metadata.impl.extensions.MetadataExtensions { }
+
+# The contents of this file is ...JvmMetadataExtensions, which is renamed.
+-adaptresourcefilecontents META-INF/services/com.android.tools.r8.jetbrains.kotlinx.metadata.impl.extensions.MetadataExtensions
diff --git a/src/main/keep-compatproguard.txt b/src/main/keep-compatproguard.txt
index e29d13e..7944c1a 100644
--- a/src/main/keep-compatproguard.txt
+++ b/src/main/keep-compatproguard.txt
@@ -4,3 +4,13 @@
-keep public class com.android.tools.r8.compatproguard.CompatProguard { public static void main(java.lang.String[]); }
-keepattributes LineNumberTable
+
+# JvmMetadataExtensions must be kept because it'll be used indirectly through java.util.ServiceLoader.
+-keep, allowobfuscation public class com.android.tools.r8.jetbrains.kotlinx.metadata.jvm.impl.JvmMetadataExtensions { public <init>(); }
+
+# For now we need to keep MetadataExtensions to avoid minifying (obfuscating) the name, because
+# '-adaptresourcefilenames' doesn't rename it (the package name is not encoded in directories).
+-keep public class com.android.tools.r8.jetbrains.kotlinx.metadata.impl.extensions.MetadataExtensions { }
+
+# The contents of this file is ...JvmMetadataExtensions, which is renamed.
+-adaptresourcefilecontents META-INF/services/com.android.tools.r8.jetbrains.kotlinx.metadata.impl.extensions.MetadataExtensions
diff --git a/src/test/examples/classmerging/PinnedArrayParameterTypesTest.java b/src/test/examples/classmerging/PinnedArrayParameterTypesTest.java
new file mode 100644
index 0000000..4a8b5be
--- /dev/null
+++ b/src/test/examples/classmerging/PinnedArrayParameterTypesTest.java
@@ -0,0 +1,47 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package classmerging;
+
+import java.lang.reflect.Method;
+
+public class PinnedArrayParameterTypesTest {
+
+ public static void main(String[] args) throws Exception {
+ for (Method method : TestClass.class.getMethods()) {
+ if (method.getName().equals("method")) {
+ Class<?> parameterType = method.getParameterTypes()[0];
+
+ // Should print classmerging.PinnedArrayParameterTypesTest$Interface when
+ // -keepparameternames is used.
+ System.out.println(parameterType.getName());
+
+ method.invoke(null, new Object[]{ new InterfaceImpl[]{ new InterfaceImpl() } });
+ break;
+ }
+ }
+ }
+
+ public interface Interface {
+
+ void foo();
+ }
+
+ public static class InterfaceImpl implements Interface {
+
+ @Override
+ public void foo() {
+ System.out.println("In InterfaceImpl.foo()");
+ }
+ }
+
+ public static class TestClass {
+
+ // This method has been kept explicitly by a keep rule. Therefore, since -keepparameternames is
+ // used, Interface must not be merged into InterfaceImpl.
+ public static void method(Interface[] obj) {
+ obj[0].foo();
+ }
+ }
+}
diff --git a/src/test/examples/classmerging/keep-rules.txt b/src/test/examples/classmerging/keep-rules.txt
index e3e0006..c75058d 100644
--- a/src/test/examples/classmerging/keep-rules.txt
+++ b/src/test/examples/classmerging/keep-rules.txt
@@ -43,6 +43,12 @@
-keep public class classmerging.PinnedParameterTypesTest$TestClass {
public static void method(...);
}
+-keep public class classmerging.PinnedArrayParameterTypesTest {
+ public static void main(...);
+}
+-keep public class classmerging.PinnedArrayParameterTypesTest$TestClass {
+ public static void method(...);
+}
-keep public class classmerging.ProguardFieldMapTest {
public static void main(...);
}
diff --git a/src/test/examples/multidex005/ArrayReference.java b/src/test/examples/multidex005/ArrayReference.java
new file mode 100644
index 0000000..b877486
--- /dev/null
+++ b/src/test/examples/multidex005/ArrayReference.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package multidex005;
+
+public class ArrayReference {
+
+ public Object directReference() {
+ return new DirectlyReferenced[1];
+ }
+}
diff --git a/src/test/examples/multidex005/CheckCastReference.java b/src/test/examples/multidex005/CheckCastReference.java
new file mode 100644
index 0000000..3e04911
--- /dev/null
+++ b/src/test/examples/multidex005/CheckCastReference.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package multidex005;
+
+public class CheckCastReference {
+
+ public Object directReference(Object obj) {
+ return (DirectlyReferenced) obj;
+ }
+}
diff --git a/src/test/examples/multidex005/InstanceOfReference.java b/src/test/examples/multidex005/InstanceOfReference.java
new file mode 100644
index 0000000..136deb8
--- /dev/null
+++ b/src/test/examples/multidex005/InstanceOfReference.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package multidex005;
+
+public class InstanceOfReference {
+
+ public boolean directReference(Object obj) {
+ return obj instanceof DirectlyReferenced;
+ }
+}
diff --git a/src/test/examples/multidex005/main-dex-rules-10.txt b/src/test/examples/multidex005/main-dex-rules-10.txt
new file mode 100644
index 0000000..1c4629f
--- /dev/null
+++ b/src/test/examples/multidex005/main-dex-rules-10.txt
@@ -0,0 +1,7 @@
+# Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+-keep public class *.CheckCastReference {
+ <init>();
+}
diff --git a/src/test/examples/multidex005/main-dex-rules-8.txt b/src/test/examples/multidex005/main-dex-rules-8.txt
new file mode 100644
index 0000000..6e86e48
--- /dev/null
+++ b/src/test/examples/multidex005/main-dex-rules-8.txt
@@ -0,0 +1,7 @@
+# Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+-keep public class *.ArrayReference {
+ <init>();
+}
diff --git a/src/test/examples/multidex005/main-dex-rules-9.txt b/src/test/examples/multidex005/main-dex-rules-9.txt
new file mode 100644
index 0000000..d27beec
--- /dev/null
+++ b/src/test/examples/multidex005/main-dex-rules-9.txt
@@ -0,0 +1,7 @@
+# Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+-keep public class *.InstanceOfReference {
+ <init>();
+}
diff --git a/src/test/examples/multidex005/ref-list-10.txt b/src/test/examples/multidex005/ref-list-10.txt
new file mode 100644
index 0000000..d110f05
--- /dev/null
+++ b/src/test/examples/multidex005/ref-list-10.txt
@@ -0,0 +1,8 @@
+Lmultidex005/CheckCastReference;
+Lmultidex005/DirectlyReferenced;
+Lmultidex005/Interface1;
+Lmultidex005/Interface2;
+Lmultidex005/Interface3;
+Lmultidex005/SuperClass;
+Lmultidex005/SuperInterface;
+Lmultidex005/SuperSuperClass;
diff --git a/src/test/examples/multidex005/ref-list-8.txt b/src/test/examples/multidex005/ref-list-8.txt
new file mode 100644
index 0000000..3937484
--- /dev/null
+++ b/src/test/examples/multidex005/ref-list-8.txt
@@ -0,0 +1,8 @@
+Lmultidex005/ArrayReference;
+Lmultidex005/DirectlyReferenced;
+Lmultidex005/Interface1;
+Lmultidex005/Interface2;
+Lmultidex005/Interface3;
+Lmultidex005/SuperClass;
+Lmultidex005/SuperInterface;
+Lmultidex005/SuperSuperClass;
diff --git a/src/test/examples/multidex005/ref-list-9.txt b/src/test/examples/multidex005/ref-list-9.txt
new file mode 100644
index 0000000..c36015f
--- /dev/null
+++ b/src/test/examples/multidex005/ref-list-9.txt
@@ -0,0 +1,8 @@
+Lmultidex005/DirectlyReferenced;
+Lmultidex005/InstanceOfReference;
+Lmultidex005/Interface1;
+Lmultidex005/Interface2;
+Lmultidex005/Interface3;
+Lmultidex005/SuperClass;
+Lmultidex005/SuperInterface;
+Lmultidex005/SuperSuperClass;
diff --git a/src/test/java/com/android/tools/r8/D8TestBuilder.java b/src/test/java/com/android/tools/r8/D8TestBuilder.java
index 88d077b..37e4dec 100644
--- a/src/test/java/com/android/tools/r8/D8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/D8TestBuilder.java
@@ -3,13 +3,18 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8;
+import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
import com.android.tools.r8.D8Command.Builder;
import com.android.tools.r8.TestBase.Backend;
import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.InternalOptions;
+import java.io.IOException;
import java.nio.file.Path;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.List;
import java.util.function.Consumer;
import java.util.function.Supplier;
@@ -17,6 +22,9 @@
extends TestCompilerBuilder<
D8Command, Builder, D8TestCompileResult, D8TestRunResult, D8TestBuilder> {
+ // Consider an in-order collection of both class and files on the classpath.
+ private List<Class<?>> classpathClasses = new ArrayList<>();
+
private D8TestBuilder(TestState state, Builder builder) {
super(state, builder, Backend.DEX);
}
@@ -34,6 +42,29 @@
D8TestCompileResult internalCompile(
Builder builder, Consumer<InternalOptions> optionsConsumer, Supplier<AndroidApp> app)
throws CompilationFailedException {
+ if (!classpathClasses.isEmpty()) {
+ Path cp;
+ try {
+ cp = getState().getNewTempFolder().resolve("cp.jar");
+ } catch (IOException e) {
+ throw builder.getReporter().fatalError("Failed to create temp file for classpath archive");
+ }
+ ArchiveConsumer archiveConsumer = new ArchiveConsumer(cp);
+ for (Class<?> classpathClass : classpathClasses) {
+ try {
+ archiveConsumer.accept(
+ ByteDataView.of(ToolHelper.getClassAsBytes(classpathClass)),
+ DescriptorUtils.javaTypeToDescriptor(classpathClass.getTypeName()),
+ builder.getReporter());
+ } catch (IOException e) {
+ builder
+ .getReporter()
+ .error("Failed to read bytes for classpath class: " + classpathClass.getTypeName());
+ }
+ }
+ archiveConsumer.finished(builder.getReporter());
+ builder.addClasspathFiles(cp);
+ }
ToolHelper.runD8(builder, optionsConsumer);
return new D8TestCompileResult(getState(), app.get());
}
@@ -43,7 +74,8 @@
}
public D8TestBuilder addClasspathClasses(Collection<Class<?>> classes) {
- return addClasspathFiles(getFilesForClasses(classes));
+ classpathClasses.addAll(classes);
+ return self();
}
public D8TestBuilder addClasspathFiles(Path... files) {
diff --git a/src/test/java/com/android/tools/r8/Dex2OatTestRunResult.java b/src/test/java/com/android/tools/r8/Dex2OatTestRunResult.java
index c4425b1..1a4e0ec 100644
--- a/src/test/java/com/android/tools/r8/Dex2OatTestRunResult.java
+++ b/src/test/java/com/android/tools/r8/Dex2OatTestRunResult.java
@@ -4,8 +4,12 @@
package com.android.tools.r8;
+import static org.hamcrest.MatcherAssert.assertThat;
+
import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.utils.AndroidApp;
+import org.hamcrest.CoreMatchers;
+import org.hamcrest.Matcher;
public class Dex2OatTestRunResult extends TestRunResult<Dex2OatTestRunResult> {
@@ -17,4 +21,15 @@
protected Dex2OatTestRunResult self() {
return this;
}
+
+ public Dex2OatTestRunResult assertNoVerificationErrors() {
+ assertSuccess();
+ Matcher<? super String> matcher =
+ CoreMatchers.not(CoreMatchers.containsString("Verification error"));
+ assertThat(
+ errorMessage("Run dex2oat produced verification errors.", matcher.toString()),
+ getStdErr(),
+ matcher);
+ return self();
+ }
}
diff --git a/src/test/java/com/android/tools/r8/RegressionForPrimitiveDefinitionForLookup.java b/src/test/java/com/android/tools/r8/RegressionForPrimitiveDefinitionForLookup.java
new file mode 100644
index 0000000..b43bb8b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/RegressionForPrimitiveDefinitionForLookup.java
@@ -0,0 +1,43 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+
+class Tester {
+ public int foo() {
+ float[][][] fs = new float[1][2][3];
+ return fs.length;
+ }
+
+ public static void main(String[] args) {
+ System.out.println(new Tester().foo());
+ }
+}
+
+// The DirectoryClasspathProvider asserts lookups are reference types which witnessed the issue.
+public class RegressionForPrimitiveDefinitionForLookup extends TestBase {
+
+ public final Class<Tester> CLASS = Tester.class;
+ public String EXPECTED = StringUtils.lines("1");
+
+ @Test
+ public void testWithArchiveClasspath() throws Exception {
+ testForD8()
+ .addClasspathClasses(CLASS)
+ .addProgramClasses(CLASS)
+ .run(CLASS)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testWithDirectoryClasspath() throws Exception {
+ testForD8()
+ .addClasspathFiles(ToolHelper.getClassPathForTests())
+ .addProgramClasses(CLASS)
+ .run(CLASS)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/TestRunResult.java b/src/test/java/com/android/tools/r8/TestRunResult.java
index a032fd6..89d0fff 100644
--- a/src/test/java/com/android/tools/r8/TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/TestRunResult.java
@@ -108,11 +108,11 @@
return builder.toString();
}
- private String errorMessage(String message) {
+ String errorMessage(String message) {
return errorMessage(message, null);
}
- private String errorMessage(String message, String expected) {
+ String errorMessage(String message, String expected) {
StringBuilder builder = new StringBuilder(message).append('\n');
if (expected != null) {
if (expected.contains(System.lineSeparator())) {
diff --git a/src/test/java/com/android/tools/r8/classmerging/StaticInitializerTest.java b/src/test/java/com/android/tools/r8/classmerging/StaticInitializerTest.java
new file mode 100644
index 0000000..ce89fe1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/StaticInitializerTest.java
@@ -0,0 +1,73 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.classmerging;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.StringUtils;
+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 StaticInitializerTest extends TestBase {
+
+ private final Backend backend;
+
+ @Parameters(name = "Backend: {0}")
+ public static Backend[] data() {
+ return Backend.values();
+ }
+
+ public StaticInitializerTest(Backend backend) {
+ this.backend = backend;
+ }
+
+ @Test
+ public void test() throws Exception {
+ String expectedOutput = StringUtils.lines("In A.m()", "In B.<clinit>()", "In B.m()");
+
+ if (backend == Backend.CF) {
+ testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expectedOutput);
+ }
+
+ testForR8(backend)
+ .addInnerClasses(StaticInitializerTest.class)
+ .addKeepMainRule(TestClass.class)
+ .enableInliningAnnotations()
+ .run(TestClass.class)
+ .assertSuccessWithOutput(expectedOutput);
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ A.m();
+ B.m();
+ }
+ }
+
+ // Cannot be merged into B because that would change the semantics due to <clinit>.
+ static class A {
+
+ @NeverInline
+ public static void m() {
+ System.out.println("In A.m()");
+ }
+ }
+
+ static class B extends A {
+
+ static {
+ System.out.println("In B.<clinit>()");
+ }
+
+ @NeverInline
+ public static void m() {
+ System.out.println("In B.m()");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
index 2284fa5..1836a29 100644
--- a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
@@ -86,16 +86,13 @@
private void runR8(Path proguardConfig, Consumer<InternalOptions> optionsConsumer)
throws IOException, ExecutionException, CompilationFailedException {
- ToolHelper.runR8(
- R8Command.builder()
- .setOutput(Paths.get(temp.getRoot().getCanonicalPath()), OutputMode.DexIndexed)
+ inspector =
+ testForR8(Backend.DEX)
.addProgramFiles(EXAMPLE_JAR)
- .addProguardConfigurationFiles(proguardConfig)
- .setDisableMinification(true)
- .build(),
- optionsConsumer);
- inspector = new CodeInspector(
- Paths.get(temp.getRoot().getCanonicalPath()).resolve("classes.dex"));
+ .addKeepRuleFiles(proguardConfig)
+ .addOptionsModification(optionsConsumer)
+ .compile()
+ .inspector();
}
private CodeInspector inspector;
@@ -466,6 +463,29 @@
}
@Test
+ public void testPinnedArrayParameterTypes() throws Throwable {
+ String main = "classmerging.PinnedArrayParameterTypesTest";
+ Path[] programFiles =
+ new Path[] {
+ CF_DIR.resolve("PinnedArrayParameterTypesTest.class"),
+ CF_DIR.resolve("PinnedArrayParameterTypesTest$Interface.class"),
+ CF_DIR.resolve("PinnedArrayParameterTypesTest$InterfaceImpl.class"),
+ CF_DIR.resolve("PinnedArrayParameterTypesTest$TestClass.class")
+ };
+ Set<String> preservedClassNames =
+ ImmutableSet.of(
+ "classmerging.PinnedArrayParameterTypesTest",
+ "classmerging.PinnedArrayParameterTypesTest$Interface",
+ "classmerging.PinnedArrayParameterTypesTest$InterfaceImpl",
+ "classmerging.PinnedArrayParameterTypesTest$TestClass");
+ runTest(
+ main,
+ programFiles,
+ preservedClassNames::contains,
+ getProguardConfig(EXAMPLE_KEEP, "-keepparameternames"));
+ }
+
+ @Test
public void testProguardFieldMap() throws Throwable {
String main = "classmerging.ProguardFieldMapTest";
Path[] programFiles =
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
index 31170d3..3e5a03a 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
@@ -219,6 +219,21 @@
}
@Test
+ public void traceMainDexList005_8() throws Throwable {
+ doTest5(8);
+ }
+
+ @Test
+ public void traceMainDexList005_9() throws Throwable {
+ doTest5(9);
+ }
+
+ @Test
+ public void traceMainDexList005_10() throws Throwable {
+ doTest5(10);
+ }
+
+ @Test
public void traceMainDexList006() throws Throwable {
doTest(
"traceMainDexList006",
diff --git a/src/test/java/com/android/tools/r8/proguard/printmapping/PrintMappingTest.java b/src/test/java/com/android/tools/r8/proguard/printmapping/PrintMappingTest.java
index e9bf581..378cf0e 100644
--- a/src/test/java/com/android/tools/r8/proguard/printmapping/PrintMappingTest.java
+++ b/src/test/java/com/android/tools/r8/proguard/printmapping/PrintMappingTest.java
@@ -27,14 +27,11 @@
private void test(Path mapping) throws Exception {
testForR8(Backend.DEX)
.addInnerClasses(PrintMappingTest.class)
- .addKeepMainRule(TestClass.class)
+ .addKeepRules("-keep,allowobfuscation class " + TestClass.class.getTypeName())
.addKeepRules("-printmapping " + mapping)
.compile();
assertTrue(mapping.toFile().exists());
}
- static class TestClass {
-
- public static void main(String[] args) {}
- }
+ static class TestClass {}
}
diff --git a/src/test/java/com/android/tools/r8/resource/KeepDirectoriesTest.java b/src/test/java/com/android/tools/r8/resource/KeepDirectoriesTest.java
index 1c28054..5b7a11e 100644
--- a/src/test/java/com/android/tools/r8/resource/KeepDirectoriesTest.java
+++ b/src/test/java/com/android/tools/r8/resource/KeepDirectoriesTest.java
@@ -15,6 +15,7 @@
import com.android.tools.r8.DataResourceConsumer;
import com.android.tools.r8.DiagnosticsHandler;
import com.android.tools.r8.R8Command;
+import com.android.tools.r8.StringResource;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.naming.ClassNameMapper;
@@ -65,10 +66,15 @@
// Return the package name in the app for this package.
private String pathForThisPackage(AndroidApp app) throws Exception {
- ClassNameMapper mapper =
- ClassNameMapper.mapperFromString(app.getProguardMapOutputData().getString());
- String x = mapper.getObfuscatedToOriginalMapping().inverse.get(Main.class.getCanonicalName());
- return x.substring(0, x.lastIndexOf('.')).replace('.', '/');
+ String name;
+ if (app.getProguardMapOutputData() != null) {
+ ClassNameMapper mapper =
+ ClassNameMapper.mapperFromString(app.getProguardMapOutputData().getString());
+ name = mapper.getObfuscatedToOriginalMapping().inverse.get(Main.class.getCanonicalName());
+ } else {
+ name = Main.class.getTypeName();
+ }
+ return name.substring(0, name.lastIndexOf('.')).replace('.', '/');
}
private void checkResourceNames(Set<String> resourceNames, String expectedPackageName) {
diff --git a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
index b302615..0b246a5 100644
--- a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
@@ -260,9 +260,8 @@
}
if (inspection != null) {
- CodeInspector inspector = new CodeInspector(out,
- minify.isMinify() ? proguardMap.toString()
- : null);
+ CodeInspector inspector =
+ new CodeInspector(out, minify.isMinify() ? proguardMap.toString() : null);
inspection.accept(inspector);
}
}
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking11Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking11Test.java
index f65c1d4..3833645 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking11Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking11Test.java
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking.examples;
-import com.android.tools.r8.TestBase.MinifyMode;
import com.android.tools.r8.shaking.TreeShakingTest;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/accessrelaxation/IfRuleWithAccessRelaxation.java b/src/test/java/com/android/tools/r8/shaking/ifrule/accessrelaxation/IfRuleWithAccessRelaxation.java
deleted file mode 100644
index cf6d7c0..0000000
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/accessrelaxation/IfRuleWithAccessRelaxation.java
+++ /dev/null
@@ -1,65 +0,0 @@
-// 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.accessrelaxation;
-
-import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
-
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
-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 IfRuleWithAccessRelaxation extends TestBase {
-
- private final Backend backend;
-
- public IfRuleWithAccessRelaxation(Backend backend) {
- this.backend = backend;
- }
-
- @Parameters(name = "Backend: {0}")
- public static Backend[] data() {
- return Backend.values();
- }
-
- @Test
- public void r8Test() throws Exception {
- CodeInspector inspector =
- testForR8(backend)
- .addInnerClasses(IfRuleWithAccessRelaxation.class)
- .addKeepRules(
- "-keep class " + TestClass.class.getTypeName() + " { int field; void method(); }",
- "-if class " + TestClass.class.getTypeName() + " { protected int field; }",
- "-keep class " + Unused1.class.getTypeName(),
- "-if class " + TestClass.class.getTypeName() + " { protected void method(); }",
- "-keep class " + Unused2.class.getTypeName(),
- "-allowaccessmodification")
- .compile()
- .inspector();
-
- assertTrue(inspector.clazz(TestClass.class).isPublic());
- assertTrue(inspector.clazz(TestClass.class).uniqueMethodWithName("method").isPublic());
- assertTrue(inspector.clazz(TestClass.class).uniqueFieldWithName("field").isPublic());
-
- assertThat(inspector.clazz(Unused1.class), isPresent());
- assertThat(inspector.clazz(Unused2.class), isPresent());
- }
-
- protected static class TestClass {
-
- protected int field = 42;
-
- protected void method() {}
- }
-
- static class Unused1 {}
-
- static class Unused2 {}
-}
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/accessrelaxation/IfRuleWithAccessRelaxationTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/accessrelaxation/IfRuleWithAccessRelaxationTest.java
new file mode 100644
index 0000000..560ebd4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/accessrelaxation/IfRuleWithAccessRelaxationTest.java
@@ -0,0 +1,97 @@
+// 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.accessrelaxation;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isFinal;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPublic;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+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 IfRuleWithAccessRelaxationTest extends TestBase {
+
+ private final Backend backend;
+
+ public IfRuleWithAccessRelaxationTest(Backend backend) {
+ this.backend = backend;
+ }
+
+ @Parameters(name = "Backend: {0}")
+ public static Backend[] data() {
+ return Backend.values();
+ }
+
+ @Test
+ public void r8Test() throws Exception {
+ CodeInspector inspector =
+ testForR8(backend)
+ .addInnerClasses(IfRuleWithAccessRelaxationTest.class)
+ .addKeepMainRule(TestClass.class)
+ .addKeepRules(
+ "-keep class " + TestClass.class.getTypeName() + " { int field; }",
+ "-if class " + TestClass.class.getTypeName() + " { protected int field; }",
+ "-keep class " + Unused1.class.getTypeName(),
+ "-if class " + TestClass.class.getTypeName() + " {",
+ " private !final void privateMethod();",
+ "}",
+ "-keep class " + Unused2.class.getTypeName(),
+ "-if class " + TestClass.class.getTypeName() + " {",
+ " protected void virtualMethod();",
+ "}",
+ "-keep class " + Unused3.class.getTypeName(),
+ "-allowaccessmodification")
+ .enableInliningAnnotations()
+ .compile()
+ .inspector();
+
+ assertTrue(inspector.clazz(TestClass.class).isPublic());
+ assertThat(inspector.clazz(TestClass.class).uniqueFieldWithName("field"), isPublic());
+ assertThat(
+ inspector.clazz(TestClass.class).uniqueMethodWithName("privateMethod"),
+ allOf(isPublic(), isFinal()));
+ assertThat(inspector.clazz(TestClass.class).uniqueMethodWithName("virtualMethod"), isPublic());
+
+ assertThat(inspector.clazz(Unused1.class), isPresent());
+ assertThat(inspector.clazz(Unused2.class), isPresent());
+ assertThat(inspector.clazz(Unused3.class), isPresent());
+ }
+
+ protected static class TestClass {
+
+ public static void main(String[] args) {
+ TestClass obj = new TestClass();
+ obj.privateMethod();
+ obj.virtualMethod();
+ }
+
+ protected int field = 42;
+
+ @NeverInline
+ private void privateMethod() {
+ System.out.println("In privateMethod()");
+ }
+
+ @NeverInline
+ protected void virtualMethod() {
+ System.out.println("In virtualMethod()");
+ }
+ }
+
+ static class Unused1 {}
+
+ static class Unused2 {}
+
+ static class Unused3 {}
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/classstaticizer/IfRuleWithClassStaticizerTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/classstaticizer/IfRuleWithClassStaticizerTest.java
new file mode 100644
index 0000000..a12620d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/classstaticizer/IfRuleWithClassStaticizerTest.java
@@ -0,0 +1,113 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.ifrule.classstaticizer;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+import java.util.List;
+import java.util.stream.Collectors;
+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 IfRuleWithClassStaticizerTest extends TestBase {
+
+ private final Backend backend;
+
+ @Parameters(name = "Backend: {0}")
+ public static Backend[] data() {
+ return Backend.values();
+ }
+
+ public IfRuleWithClassStaticizerTest(Backend backend) {
+ this.backend = backend;
+ }
+
+ @Test
+ public void test() throws Exception {
+ String expectedOutput = StringUtils.lines("In method()");
+
+ if (backend == Backend.CF) {
+ testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expectedOutput);
+ }
+
+ CodeInspector inspector =
+ testForR8(backend)
+ .addInnerClasses(IfRuleWithClassStaticizerTest.class)
+ .addKeepMainRule(TestClass.class)
+ .addKeepRules(
+ "-if class " + StaticizerCandidate.Companion.class.getTypeName() + " {",
+ " public !static void method();",
+ "}",
+ "-keep class " + Unused.class.getTypeName())
+ .enableInliningAnnotations()
+ .enableClassInliningAnnotations()
+ .run(TestClass.class)
+ .assertSuccessWithOutput(expectedOutput)
+ .inspector();
+
+ ClassSubject classSubject = inspector.clazz(StaticizerCandidate.class);
+ assertThat(classSubject, isPresent());
+
+ if (backend == Backend.CF) {
+ // The class staticizer is not enabled for CF.
+ assertThat(inspector.clazz(Unused.class), isPresent());
+ } else {
+ assert backend == Backend.DEX;
+
+ // There should be a static method on StaticizerCandidate after staticizing.
+ List<FoundMethodSubject> staticMethods =
+ classSubject.allMethods().stream()
+ .filter(method -> method.isStatic() && !method.isClassInitializer())
+ .collect(Collectors.toList());
+ assertEquals(1, staticMethods.size());
+ assertEquals(
+ "void " + StaticizerCandidate.Companion.class.getTypeName() + ".method()",
+ staticMethods.get(0).getOriginalSignature().toString());
+
+ // The Companion class should not be present after staticizing.
+ assertThat(inspector.clazz(StaticizerCandidate.Companion.class), not(isPresent()));
+
+ // TODO(b/122867080): The Unused class should be present due to the -if rule.
+ assertThat(inspector.clazz(Unused.class), not(isPresent()));
+ }
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ StaticizerCandidate.companion.method();
+ }
+ }
+
+ @NeverClassInline
+ static class StaticizerCandidate {
+
+ static final Companion companion = new Companion();
+
+ @NeverClassInline
+ static class Companion {
+
+ @NeverInline
+ public void method() {
+ System.out.println("In method()");
+ }
+ }
+ }
+
+ static class Unused {}
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/interfacemethoddesugaring/IfRuleWithInterfaceMethodDesugaringTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/interfacemethoddesugaring/IfRuleWithInterfaceMethodDesugaringTest.java
new file mode 100644
index 0000000..d86bc68
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/interfacemethoddesugaring/IfRuleWithInterfaceMethodDesugaringTest.java
@@ -0,0 +1,105 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.ifrule.interfacemethoddesugaring;
+
+import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.COMPANION_CLASS_NAME_SUFFIX;
+import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.DEFAULT_METHOD_PREFIX;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPublic;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isStatic;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+
+public class IfRuleWithInterfaceMethodDesugaringTest extends TestBase {
+
+ @Test
+ public void test() throws Exception {
+ String expectedOutput =
+ StringUtils.lines("In Interface.staticMethod()", "In Interface.virtualMethod()");
+
+ testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expectedOutput);
+
+ CodeInspector inspector =
+ testForR8(Backend.DEX)
+ .addInnerClasses(IfRuleWithInterfaceMethodDesugaringTest.class)
+ .addKeepMainRule(TestClass.class)
+ .addKeepRules(
+ "-if class " + Interface.class.getTypeName() + " {",
+ " !public static void staticMethod();",
+ "}",
+ "-keep class " + Unused1.class.getTypeName(),
+ "-if class " + Interface.class.getTypeName() + " {",
+ " !public !static void virtualMethod();",
+ "}",
+ "-keep class " + Unused2.class.getTypeName())
+ .enableInliningAnnotations()
+ .enableClassInliningAnnotations()
+ .enableMergeAnnotations()
+ .setMinApi(AndroidApiLevel.M)
+ .run(TestClass.class)
+ .assertSuccessWithOutput(expectedOutput)
+ .inspector();
+
+ ClassSubject classSubject =
+ inspector.clazz(Interface.class.getTypeName() + COMPANION_CLASS_NAME_SUFFIX);
+ assertThat(classSubject, isPresent());
+ assertEquals(2, classSubject.allMethods().size());
+
+ MethodSubject staticMethodSubject = classSubject.uniqueMethodWithName("staticMethod");
+ assertThat(staticMethodSubject, allOf(isPresent(), isPublic(), isStatic()));
+
+ // TODO(b/122867087): Should not be necessary to use `DEFAULT_METHOD_PREFIX`.
+ MethodSubject virtualMethodSubject =
+ classSubject.uniqueMethodWithName(DEFAULT_METHOD_PREFIX + "virtualMethod");
+ assertThat(virtualMethodSubject, allOf(isPresent(), isPublic(), isStatic()));
+
+ // TODO(b/122875545): The Unused class should be present due to the -if rule.
+ assertThat(inspector.clazz(Unused1.class), not(isPresent()));
+ assertThat(inspector.clazz(Unused2.class), not(isPresent()));
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ Interface.staticMethod();
+ new InterfaceImpl().virtualMethod();
+ }
+ }
+
+ @NeverClassInline
+ @NeverMerge
+ interface Interface {
+
+ @NeverInline
+ static void staticMethod() {
+ System.out.println("In Interface.staticMethod()");
+ }
+
+ @NeverInline
+ default void virtualMethod() {
+ System.out.println("In Interface.virtualMethod()");
+ }
+ }
+
+ @NeverClassInline
+ static class InterfaceImpl implements Interface {}
+
+ static class Unused1 {}
+
+ static class Unused2 {}
+}
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 9983320..a9058ff 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
@@ -45,6 +45,7 @@
import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.lang.reflect.Method;
+import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
@@ -82,13 +83,14 @@
public CodeInspector(
List<Path> files, String mappingFile, Consumer<InternalOptions> optionsConsumer)
throws IOException, ExecutionException {
- if (mappingFile != null) {
- this.mapping = ClassNameMapper.mapperFromFile(Paths.get(mappingFile));
- BiMapContainer<String, String> nameMapping = this.mapping.getObfuscatedToOriginalMapping();
+ Path mappingPath = mappingFile != null ? Paths.get(mappingFile) : null;
+ if (mappingPath != null && Files.exists(mappingPath)) {
+ mapping = ClassNameMapper.mapperFromFile(mappingPath);
+ BiMapContainer<String, String> nameMapping = mapping.getObfuscatedToOriginalMapping();
obfuscatedToOriginalMapping = nameMapping.original;
originalToObfuscatedMapping = nameMapping.inverse;
} else {
- this.mapping = null;
+ mapping = null;
originalToObfuscatedMapping = null;
obfuscatedToOriginalMapping = null;
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
index 8cd5cc7..11ee3af 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
@@ -102,7 +102,7 @@
public MethodSubject uniqueMethodWithName(String name) {
MethodSubject methodSubject = null;
for (FoundMethodSubject candidate : allMethods()) {
- if (candidate.getOriginalName().equals(name)) {
+ if (candidate.getOriginalName(false).equals(name)) {
assert methodSubject == null;
methodSubject = candidate;
}
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 b2dff57..103ffb9 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
@@ -146,6 +146,28 @@
};
}
+ public static Matcher<MemberSubject> isStatic() {
+ return new TypeSafeMatcher<MemberSubject>() {
+ @Override
+ public boolean matchesSafely(final MemberSubject subject) {
+ return subject.isPresent() && subject.isStatic();
+ }
+
+ @Override
+ public void describeTo(final Description description) {
+ description.appendText(" present");
+ }
+
+ @Override
+ public void describeMismatchSafely(final MemberSubject subject, Description description) {
+ description
+ .appendText(type(subject) + " ")
+ .appendValue(name(subject))
+ .appendText(" was not");
+ }
+ };
+ }
+
public static Matcher<Subject> isSynthetic() {
return new TypeSafeMatcher<Subject>() {
@Override
@@ -227,6 +249,28 @@
};
}
+ public static Matcher<MethodSubject> isFinal() {
+ return new TypeSafeMatcher<MethodSubject>() {
+ @Override
+ public boolean matchesSafely(final MethodSubject method) {
+ return method.isPresent() && method.isFinal();
+ }
+
+ @Override
+ public void describeTo(final Description description) {
+ description.appendText("is final");
+ }
+
+ @Override
+ public void describeMismatchSafely(final MethodSubject method, Description description) {
+ description
+ .appendText("method ")
+ .appendValue(method.getOriginalName())
+ .appendText(" was not");
+ }
+ };
+ }
+
public static <T extends MemberSubject> Matcher<T> isPrivate() {
return hasVisibility(Visibility.PRIVATE);
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/MemberSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/MemberSubject.java
index a95b6e6..af1fe14 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/MemberSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/MemberSubject.java
@@ -25,8 +25,22 @@
public abstract Signature getFinalSignature();
public String getOriginalName() {
+ return getOriginalName(true);
+ }
+
+ public String getOriginalName(boolean qualified) {
Signature originalSignature = getOriginalSignature();
- return originalSignature == null ? null : originalSignature.name;
+ if (originalSignature != null) {
+ String name = originalSignature.name;
+ if (!qualified) {
+ int index = name.lastIndexOf(".");
+ if (index >= 0) {
+ return name.substring(index + 1);
+ }
+ }
+ return name;
+ }
+ return null;
}
public String getFinalName() {
diff --git a/tools/run_on_as_app.py b/tools/run_on_as_app.py
index 770a651..5dc42ec 100755
--- a/tools/run_on_as_app.py
+++ b/tools/run_on_as_app.py
@@ -79,6 +79,14 @@
'flavor': 'standard',
'releaseTarget': 'app:assembleRelease',
},
+ 'tivi': {
+ 'app_id': 'app.tivi',
+ # Forked from https://github.com/chrisbanes/tivi.git removing
+ # signingConfigs.
+ 'git_repo': 'https://github.com/sgjesse/tivi.git',
+ # TODO(123047413): Fails with R8.
+ 'skip': True,
+ },
# This does not build yet.
'muzei': {
'git_repo': 'https://github.com/sgjesse/muzei.git',
@@ -267,9 +275,6 @@
result_per_shrinker[shrinker] = result
- if IsTrackedByGit('gradle.properties'):
- GitCheckout('gradle.properties')
-
return result_per_shrinker
def BuildAppWithShrinker(app, config, shrinker, checkout_dir, options):
@@ -286,19 +291,6 @@
archives_base_name = config.get(' archives_base_name', app_module)
flavor = config.get('flavor')
- # Ensure that gradle.properties is not modified before modifying it to
- # select shrinker.
- if IsTrackedByGit('gradle.properties'):
- GitCheckout('gradle.properties')
- with open("gradle.properties", "a") as gradle_properties:
- if 'r8' in shrinker:
- gradle_properties.write('\nandroid.enableR8=true\n')
- if shrinker == 'r8full' or shrinker == 'r8full-minified':
- gradle_properties.write('android.enableR8.fullMode=true\n')
- else:
- assert shrinker == 'proguard'
- gradle_properties.write('\nandroid.enableR8=false\n')
-
out = os.path.join(checkout_dir, 'out', shrinker)
if not os.path.exists(out):
os.makedirs(out)
@@ -317,8 +309,17 @@
releaseTarget = app_module + ':' + 'assemble' + (
flavor.capitalize() if flavor else '') + 'Release'
- cmd = ['./gradlew', '--no-daemon', 'clean', releaseTarget, '--profile',
- '--stacktrace']
+ # Value for property android.enableR8.
+ enableR8 = 'r8' in shrinker
+ # Value for property android.enableR8.fullMode.
+ enableR8FullMode = shrinker == 'r8full' or shrinker == 'r8full-minified'
+ # Build gradlew command line.
+ cmd = ['./gradlew', '--no-daemon', 'clean', releaseTarget,
+ '--profile', '--stacktrace',
+ '-Pandroid.enableR8=' + str(enableR8).lower(),
+ '-Pandroid.enableR8.fullMode=' + str(enableR8FullMode).lower()]
+ if options.gradle_flags:
+ cmd.extend(options.gradle_flags.split(' '))
utils.PrintCmd(cmd)
build_process = subprocess.Popen(cmd, env=env, stdout=subprocess.PIPE)
@@ -377,7 +378,7 @@
def RebuildAppWithShrinker(apk, apk_dest, proguard_config_file, shrinker):
assert 'r8' in shrinker
assert apk_dest.endswith('.apk')
-
+
# Compile given APK with shrinker to temporary zip file.
api = 28 # TODO(christofferqa): Should be the one from build.gradle
android_jar = os.path.join(utils.REPO_ROOT, utils.ANDROID_JAR.format(api=api))
@@ -523,6 +524,8 @@
help='Run without building ToT first (only when using ToT)',
default=False,
action='store_true')
+ result.add_option('--gradle-flags', '--gradle_flags',
+ help='Flags to pass in to gradle')
(options, args) = result.parse_args(argv)
if options.disable_tot:
# r8.jar is required for recompiling the generated APK