Merge commit '7ba844fa7c6f67fcd9a990d375df8deb05ab9137' into dev-release
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java
index 139a285..6c8d4cf 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -12,11 +12,14 @@
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMap.Builder;
-import com.google.common.collect.ImmutableSet;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -402,59 +405,48 @@
* <a href="https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.4.3.3">
* Section 5.4.3.3 of the JVM Spec</a>. As this is the same for interfaces and classes, we share
* one implementation.
- * <p>
- * This method will return all maximally specific default methods if there is more than one. If
- * there is no default method, any of the found methods is returned.
*/
private ResolutionResult resolveMethodStep3(DexClass clazz, DexMethod method) {
- MultiResultBuilder builder = new MultiResultBuilder();
- DexEncodedMethod anyTarget = resolveMethodStep3Helper(clazz, method, builder);
- ResolutionResult result = builder.build();
- if (result != null) {
- // We have found default methods, return them.
- return result;
- }
- // Return any of the non-default methods.
- return anyTarget == null ? NoSuchMethodResult.INSTANCE : new SingleResolutionResult(anyTarget);
+ MaximallySpecificMethodsBuilder builder = new MaximallySpecificMethodsBuilder();
+ resolveMethodStep3Helper(clazz, method, builder);
+ return builder.resolve();
}
- /**
- * Helper method that performs the actual search and adds all maximally specific default methods
- * to the builder. Additionally, one of the maximally specific default methods or, if none exist,
- * any of the found methods, is returned.
- */
- private DexEncodedMethod resolveMethodStep3Helper(DexClass clazz, DexMethod method,
- MultiResultBuilder builder) {
- // We are looking for the maximally-specific superinterfaces that have a
- // non-abstract method or any of the abstract methods.
- DexEncodedMethod result = null;
+ /** Helper method that builds the set of maximally specific methods. */
+ private void resolveMethodStep3Helper(
+ DexClass clazz, DexMethod method, MaximallySpecificMethodsBuilder builder) {
for (DexType iface : clazz.interfaces.values) {
DexClass definiton = definitionFor(iface);
if (definiton == null) {
// Ignore missing interface definitions.
continue;
}
- DexEncodedMethod localResult = definiton.lookupMethod(method);
- // Remember the result, if any, as local result.
- result = selectCandidate(result, localResult);
- if (localResult != null && localResult.isNonAbstractVirtualMethod()) {
- // We have found a default method in this class. Remember it and stop the search.
- builder.add(localResult);
+ assert definiton.isInterface();
+ DexEncodedMethod result = definiton.lookupMethod(method);
+ if (isMaximallySpecificCandidate(result)) {
+ // The candidate is added and doing so will prohibit shadowed methods from being in the set.
+ builder.addCandidate(definiton, result, this);
} else {
// Look at the super-interfaces of this class and keep searching.
- localResult = resolveMethodStep3Helper(definiton, method, builder);
- result = selectCandidate(result, localResult);
+ resolveMethodStep3Helper(definiton, method, builder);
}
}
// Now look at indirect super interfaces.
if (clazz.superType != null) {
DexClass superClass = definitionFor(clazz.superType);
if (superClass != null) {
- DexEncodedMethod superResult = resolveMethodStep3Helper(superClass, method, builder);
- result = selectCandidate(result, superResult);
+ resolveMethodStep3Helper(superClass, method, builder);
}
}
- return result;
+ }
+
+ /**
+ * A candidate for being a maximally specific method must have neither its private, nor its static
+ * flag set. A candidate may still not be maximally specific, which entails that no subinterfaces
+ * from also contribute with a candidate to the type. That is not determined by this method.
+ */
+ private boolean isMaximallySpecificCandidate(DexEncodedMethod method) {
+ return method != null && !method.accessFlags.isPrivate() && !method.accessFlags.isStatic();
}
/**
@@ -605,22 +597,6 @@
return null;
}
- /**
- * If previous is non-null, selects previous. If current is non-null and a non-private,
- * non-static method, current is selected. Otherwise null is returned.
- */
- private DexEncodedMethod selectCandidate(DexEncodedMethod previous, DexEncodedMethod current) {
- if (previous != null) {
- assert !previous.accessFlags.isPrivate();
- assert !previous.accessFlags.isStatic();
- return previous;
- }
- if (current != null && !current.accessFlags.isPrivate() && !current.accessFlags.isStatic()) {
- return current;
- }
- return null;
- }
-
public boolean hasSubtyping() {
assert checkIfObsolete();
return false;
@@ -646,31 +622,85 @@
return app.mainDexList.contains(type);
}
- private static class MultiResultBuilder {
+ private static class MaximallySpecificMethodsBuilder {
- private ImmutableSet.Builder<DexEncodedMethod> builder;
- private DexEncodedMethod singleResult;
+ // The set of actual maximally specific methods.
+ // This set is linked map so that in the case where a number of methods remain a deterministic
+ // choice can be made. The map is from definition classes to their maximally specific method, or
+ // in the case that a type has a candidate which is shadowed by a subinterface, the map will
+ // map the class to a null entry, thus any addition to the map must check for key containment
+ // prior to writing.
+ LinkedHashMap<DexClass, DexEncodedMethod> maximallySpecificMethods = new LinkedHashMap<>();
- void add(DexEncodedMethod result) {
- if (builder != null) {
- builder.add(result);
- } else if (singleResult != null && !singleResult.equals(result)) {
- builder = ImmutableSet.builder();
- builder.add(singleResult, result);
- singleResult = null;
- } else {
- singleResult = result;
+ void addCandidate(DexClass holder, DexEncodedMethod method, AppInfo appInfo) {
+ // If this candidate is already a candidate or it is shadowed, then no need to continue.
+ if (maximallySpecificMethods.containsKey(holder)) {
+ return;
+ }
+ maximallySpecificMethods.put(holder, method);
+ // Prune exiting candidates and prohibit future candidates in the super hierarchy.
+ assert holder.isInterface();
+ assert holder.superType == appInfo.dexItemFactory.objectType;
+ for (DexType iface : holder.interfaces.values) {
+ markShadowed(iface, appInfo);
}
}
- ResolutionResult build() {
- if (builder != null) {
- return new MultiResolutionResult(builder.build().asList());
+ private void markShadowed(DexType type, AppInfo appInfo) {
+ if (type == null) {
+ return;
}
- if (singleResult != null) {
- return new SingleResolutionResult(singleResult);
+ DexClass clazz = appInfo.definitionFor(type);
+ if (clazz == null) {
+ return;
}
- return null;
+ assert clazz.isInterface();
+ assert clazz.superType == appInfo.dexItemFactory.objectType;
+ // A null entry signifies that the candidate is shadowed blocking future candidates.
+ // If the candidate is already shadowed at this type there is no need to shadow further up.
+ if (maximallySpecificMethods.containsKey(clazz)
+ && maximallySpecificMethods.get(clazz) == null) {
+ return;
+ }
+ maximallySpecificMethods.put(clazz, null);
+ for (DexType iface : clazz.interfaces.values) {
+ markShadowed(iface, appInfo);
+ }
+ }
+
+ ResolutionResult resolve() {
+ if (maximallySpecificMethods.isEmpty()) {
+ return NoSuchMethodResult.INSTANCE;
+ }
+ // Fast path in the common case of a single method.
+ if (false && maximallySpecificMethods.size() == 1) {
+ return new SingleResolutionResult(maximallySpecificMethods.values().iterator().next());
+ }
+ DexEncodedMethod firstMaximallySpecificMethod = null;
+ List<DexEncodedMethod> nonAbstractMethods = new ArrayList<>(maximallySpecificMethods.size());
+ for (DexEncodedMethod method : maximallySpecificMethods.values()) {
+ if (method == null) {
+ // Ignore shadowed candidates.
+ continue;
+ }
+ if (firstMaximallySpecificMethod == null) {
+ firstMaximallySpecificMethod = method;
+ }
+ if (method.isNonAbstractVirtualMethod()) {
+ nonAbstractMethods.add(method);
+ }
+ }
+ // If there are no non-abstract methods, then any candidate will suffice as a target.
+ // For deterministic resolution, we return the first mapped method (of the linked map).
+ if (nonAbstractMethods.isEmpty()) {
+ return new SingleResolutionResult(firstMaximallySpecificMethod);
+ }
+ // If there is exactly one non-abstract method (a default method) it is the resolution target.
+ if (nonAbstractMethods.size() == 1) {
+ return new SingleResolutionResult(nonAbstractMethods.get(0));
+ }
+ // TODO(b/144085169): In the case of multiple non-abstract methods resolution should fail.
+ return new MultiResolutionResult(ImmutableList.copyOf(nonAbstractMethods));
}
}
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 6308a16..f8aaa42 100644
--- a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
@@ -19,7 +19,7 @@
import java.util.List;
import java.util.Map;
-public class DirectMappedDexApplication extends DexApplication {
+public class DirectMappedDexApplication extends DexApplication implements DexDefinitionSupplier {
// Mapping from code objects to their encoded-method owner. Used for asserting unique ownership
// and debugging purposes.
@@ -70,12 +70,46 @@
}
@Override
+ public DexDefinition definitionFor(DexReference reference) {
+ if (reference.isDexType()) {
+ return definitionFor(reference.asDexType());
+ }
+ if (reference.isDexMethod()) {
+ return definitionFor(reference.asDexMethod());
+ }
+ assert reference.isDexField();
+ return definitionFor(reference.asDexField());
+ }
+
+ @Override
+ public DexEncodedField definitionFor(DexField field) {
+ DexClass clazz = definitionFor(field.holder);
+ return clazz != null ? clazz.lookupField(field) : null;
+ }
+
+ @Override
+ public DexEncodedMethod definitionFor(DexMethod method) {
+ DexClass clazz = definitionFor(method.holder);
+ return clazz != null ? clazz.lookupMethod(method) : null;
+ }
+
+ @Override
public DexClass definitionFor(DexType type) {
assert type.isClassType() : "Cannot lookup definition for type: " + type;
return allClasses.get(type);
}
@Override
+ public DexProgramClass definitionForProgramType(DexType type) {
+ return programDefinitionFor(type);
+ }
+
+ @Override
+ public DexItemFactory dexItemFactory() {
+ return dexItemFactory;
+ }
+
+ @Override
public DexProgramClass programDefinitionFor(DexType type) {
DexClass clazz = definitionFor(type);
return clazz instanceof DexProgramClass ? clazz.asProgramClass() : null;
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollectionImpl.java b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollectionImpl.java
index 1478f2d..3a307cf 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollectionImpl.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoCollectionImpl.java
@@ -36,11 +36,13 @@
infos.entrySet().removeIf(entry -> predicate.test(entry.getKey(), entry.getValue()));
}
- public FieldAccessInfoCollectionImpl rewrittenWithLens(GraphLense lens) {
+ public FieldAccessInfoCollectionImpl rewrittenWithLens(
+ DexDefinitionSupplier definitions, GraphLense lens) {
FieldAccessInfoCollectionImpl collection = new FieldAccessInfoCollectionImpl();
infos.forEach(
(field, info) ->
- collection.infos.put(lens.lookupField(field), info.rewrittenWithLens(lens)));
+ collection.infos.put(
+ lens.lookupField(field), info.rewrittenWithLens(definitions, lens)));
return collection;
}
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
index fcd4f5c..a263cb6 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessInfoImpl.java
@@ -189,25 +189,31 @@
writesWithContexts = null;
}
- public FieldAccessInfoImpl rewrittenWithLens(GraphLense lens) {
+ public FieldAccessInfoImpl rewrittenWithLens(DexDefinitionSupplier definitions, GraphLense lens) {
FieldAccessInfoImpl rewritten = new FieldAccessInfoImpl(lens.lookupField(field));
if (readsWithContexts != null) {
rewritten.readsWithContexts = new IdentityHashMap<>();
readsWithContexts.forEach(
- (access, contexts) ->
- rewritten
- .readsWithContexts
- .computeIfAbsent(lens.lookupField(access), ignore -> Sets.newIdentityHashSet())
- .addAll(contexts));
+ (access, contexts) -> {
+ Set<DexEncodedMethod> newContexts =
+ rewritten.readsWithContexts.computeIfAbsent(
+ lens.lookupField(access), ignore -> Sets.newIdentityHashSet());
+ for (DexEncodedMethod context : contexts) {
+ newContexts.add(lens.mapDexEncodedMethod(context, definitions));
+ }
+ });
}
if (writesWithContexts != null) {
rewritten.writesWithContexts = new IdentityHashMap<>();
writesWithContexts.forEach(
- (access, contexts) ->
- rewritten
- .writesWithContexts
- .computeIfAbsent(lens.lookupField(access), ignore -> Sets.newIdentityHashSet())
- .addAll(contexts));
+ (access, contexts) -> {
+ Set<DexEncodedMethod> newContexts =
+ rewritten.writesWithContexts.computeIfAbsent(
+ lens.lookupField(access), ignore -> Sets.newIdentityHashSet());
+ for (DexEncodedMethod context : contexts) {
+ newContexts.add(lens.mapDexEncodedMethod(context, definitions));
+ }
+ });
}
return rewritten;
}
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 16dab7e..8c5dd71 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLense.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLense.java
@@ -420,6 +420,10 @@
public DexEncodedMethod mapDexEncodedMethod(
DexEncodedMethod originalEncodedMethod, DexDefinitionSupplier definitions) {
+ assert originalEncodedMethod != DexEncodedMethod.SENTINEL;
+ if (originalEncodedMethod == DexEncodedMethod.ANNOTATION_REFERENCE) {
+ return DexEncodedMethod.ANNOTATION_REFERENCE;
+ }
DexMethod newMethod = getRenamedMethodSignature(originalEncodedMethod.method);
// Note that:
// * Even if `newMethod` is the same as `originalEncodedMethod.method`, we still need to look it
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/FieldValueHelper.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/FieldValueHelper.java
index d06b3f9..832e8b9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/FieldValueHelper.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/FieldValueHelper.java
@@ -111,6 +111,11 @@
// we just created for future use we should delay removing trivial
// phis until we are done with replacing fields reads.
phi.addOperands(operands, false);
+
+ TypeLatticeElement phiType = phi.computePhiType(appView);
+ assert phiType.lessThanOrEqual(phi.getTypeLattice(), appView);
+ phi.setTypeLattice(phiType);
+
value = phi;
}
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index f89e220..f5b0de8 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -4,7 +4,6 @@
package com.android.tools.r8.shaking;
import static com.android.tools.r8.graph.GraphLense.rewriteReferenceKeys;
-import static com.google.common.base.Predicates.not;
import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.DexApplication;
@@ -35,6 +34,7 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
+import com.google.common.collect.ImmutableSortedSet.Builder;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
@@ -81,6 +81,8 @@
* removed.
*/
final SortedSet<DexMethod> targetedMethods;
+
+ final Set<DexMethod> targetedMethodsThatMustRemainNonAbstract;
/**
* Set of program methods that are used as the bootstrap method for an invoke-dynamic instruction.
*/
@@ -100,16 +102,6 @@
* each field. The latter is used, for example, during member rebinding.
*/
private final FieldAccessInfoCollectionImpl fieldAccessInfoCollection;
- /**
- * Set of all instance fields that are only written inside the <init>() methods of their enclosing
- * class.
- */
- private Set<DexField> instanceFieldsWrittenOnlyInEnclosingInstanceInitializers;
- /**
- * Set of all static fields that are only written inside the <clinit>() method of their enclosing
- * class.
- */
- private Set<DexField> staticFieldsWrittenOnlyInEnclosingStaticInitializer;
/** Set of all methods referenced in virtual invokes, along with calling context. */
public final SortedMap<DexMethod, Set<DexEncodedMethod>> virtualInvokes;
/** Set of all methods referenced in interface invokes, along with calling context. */
@@ -195,13 +187,12 @@
Set<DexType> instantiatedAppServices,
Set<DexType> instantiatedTypes,
SortedSet<DexMethod> targetedMethods,
+ Set<DexMethod> targetedMethodsThatMustRemainNonAbstract,
SortedSet<DexMethod> bootstrapMethods,
SortedSet<DexMethod> methodsTargetedByInvokeDynamic,
SortedSet<DexMethod> virtualMethodsTargetedByInvokeDirect,
SortedSet<DexMethod> liveMethods,
FieldAccessInfoCollectionImpl fieldAccessInfoCollection,
- Set<DexField> instanceFieldsWrittenOnlyInEnclosingInstanceInitializers,
- Set<DexField> staticFieldsWrittenOnlyInEnclosingStaticInitializer,
SortedMap<DexMethod, Set<DexEncodedMethod>> virtualInvokes,
SortedMap<DexMethod, Set<DexEncodedMethod>> interfaceInvokes,
SortedMap<DexMethod, Set<DexEncodedMethod>> superInvokes,
@@ -234,15 +225,12 @@
this.instantiatedAppServices = instantiatedAppServices;
this.instantiatedTypes = instantiatedTypes;
this.targetedMethods = targetedMethods;
+ this.targetedMethodsThatMustRemainNonAbstract = targetedMethodsThatMustRemainNonAbstract;
this.bootstrapMethods = bootstrapMethods;
this.methodsTargetedByInvokeDynamic = methodsTargetedByInvokeDynamic;
this.virtualMethodsTargetedByInvokeDirect = virtualMethodsTargetedByInvokeDirect;
this.liveMethods = liveMethods;
this.fieldAccessInfoCollection = fieldAccessInfoCollection;
- this.instanceFieldsWrittenOnlyInEnclosingInstanceInitializers =
- instanceFieldsWrittenOnlyInEnclosingInstanceInitializers;
- this.staticFieldsWrittenOnlyInEnclosingStaticInitializer =
- staticFieldsWrittenOnlyInEnclosingStaticInitializer;
this.pinnedItems = pinnedItems;
this.mayHaveSideEffects = mayHaveSideEffects;
this.noSideEffects = noSideEffects;
@@ -278,13 +266,12 @@
Set<DexType> instantiatedAppServices,
Set<DexType> instantiatedTypes,
SortedSet<DexMethod> targetedMethods,
+ Set<DexMethod> targetedMethodsThatMustRemainNonAbstract,
SortedSet<DexMethod> bootstrapMethods,
SortedSet<DexMethod> methodsTargetedByInvokeDynamic,
SortedSet<DexMethod> virtualMethodsTargetedByInvokeDirect,
SortedSet<DexMethod> liveMethods,
FieldAccessInfoCollectionImpl fieldAccessInfoCollection,
- Set<DexField> instanceFieldsWrittenOnlyInEnclosingInstanceInitializers,
- Set<DexField> staticFieldsWrittenOnlyInEnclosingStaticInitializer,
SortedMap<DexMethod, Set<DexEncodedMethod>> virtualInvokes,
SortedMap<DexMethod, Set<DexEncodedMethod>> interfaceInvokes,
SortedMap<DexMethod, Set<DexEncodedMethod>> superInvokes,
@@ -317,15 +304,12 @@
this.instantiatedAppServices = instantiatedAppServices;
this.instantiatedTypes = instantiatedTypes;
this.targetedMethods = targetedMethods;
+ this.targetedMethodsThatMustRemainNonAbstract = targetedMethodsThatMustRemainNonAbstract;
this.bootstrapMethods = bootstrapMethods;
this.methodsTargetedByInvokeDynamic = methodsTargetedByInvokeDynamic;
this.virtualMethodsTargetedByInvokeDirect = virtualMethodsTargetedByInvokeDirect;
this.liveMethods = liveMethods;
this.fieldAccessInfoCollection = fieldAccessInfoCollection;
- this.instanceFieldsWrittenOnlyInEnclosingInstanceInitializers =
- instanceFieldsWrittenOnlyInEnclosingInstanceInitializers;
- this.staticFieldsWrittenOnlyInEnclosingStaticInitializer =
- staticFieldsWrittenOnlyInEnclosingStaticInitializer;
this.pinnedItems = pinnedItems;
this.mayHaveSideEffects = mayHaveSideEffects;
this.noSideEffects = noSideEffects;
@@ -362,13 +346,12 @@
previous.instantiatedAppServices,
previous.instantiatedTypes,
previous.targetedMethods,
+ previous.targetedMethodsThatMustRemainNonAbstract,
previous.bootstrapMethods,
previous.methodsTargetedByInvokeDynamic,
previous.virtualMethodsTargetedByInvokeDirect,
previous.liveMethods,
previous.fieldAccessInfoCollection,
- previous.instanceFieldsWrittenOnlyInEnclosingInstanceInitializers,
- previous.staticFieldsWrittenOnlyInEnclosingStaticInitializer,
previous.virtualInvokes,
previous.interfaceInvokes,
previous.superInvokes,
@@ -410,13 +393,12 @@
previous.instantiatedAppServices,
previous.instantiatedTypes,
previous.targetedMethods,
+ previous.targetedMethodsThatMustRemainNonAbstract,
previous.bootstrapMethods,
previous.methodsTargetedByInvokeDynamic,
previous.virtualMethodsTargetedByInvokeDirect,
previous.liveMethods,
previous.fieldAccessInfoCollection,
- previous.instanceFieldsWrittenOnlyInEnclosingInstanceInitializers,
- previous.staticFieldsWrittenOnlyInEnclosingStaticInitializer,
previous.virtualInvokes,
previous.interfaceInvokes,
previous.superInvokes,
@@ -462,19 +444,16 @@
this.instantiatedTypes = rewriteItems(previous.instantiatedTypes, lense::lookupType);
this.instantiatedLambdas = rewriteItems(previous.instantiatedLambdas, lense::lookupType);
this.targetedMethods = lense.rewriteMethodsConservatively(previous.targetedMethods);
+ this.targetedMethodsThatMustRemainNonAbstract =
+ lense.rewriteMethodsConservatively(previous.targetedMethodsThatMustRemainNonAbstract);
this.bootstrapMethods = lense.rewriteMethodsConservatively(previous.bootstrapMethods);
this.methodsTargetedByInvokeDynamic =
lense.rewriteMethodsConservatively(previous.methodsTargetedByInvokeDynamic);
this.virtualMethodsTargetedByInvokeDirect =
lense.rewriteMethodsConservatively(previous.virtualMethodsTargetedByInvokeDirect);
this.liveMethods = lense.rewriteMethodsConservatively(previous.liveMethods);
- this.fieldAccessInfoCollection = previous.fieldAccessInfoCollection.rewrittenWithLens(lense);
- this.instanceFieldsWrittenOnlyInEnclosingInstanceInitializers =
- rewriteItems(
- previous.instanceFieldsWrittenOnlyInEnclosingInstanceInitializers, lense::lookupField);
- this.staticFieldsWrittenOnlyInEnclosingStaticInitializer =
- rewriteItems(
- previous.staticFieldsWrittenOnlyInEnclosingStaticInitializer, lense::lookupField);
+ this.fieldAccessInfoCollection =
+ previous.fieldAccessInfoCollection.rewrittenWithLens(application, lense);
this.pinnedItems = lense.rewriteReferencesConservatively(previous.pinnedItems);
this.virtualInvokes =
rewriteKeysConservativelyWhileMergingValues(
@@ -547,15 +526,13 @@
this.instantiatedTypes = previous.instantiatedTypes;
this.instantiatedLambdas = previous.instantiatedLambdas;
this.targetedMethods = previous.targetedMethods;
+ this.targetedMethodsThatMustRemainNonAbstract =
+ previous.targetedMethodsThatMustRemainNonAbstract;
this.bootstrapMethods = previous.bootstrapMethods;
this.methodsTargetedByInvokeDynamic = previous.methodsTargetedByInvokeDynamic;
this.virtualMethodsTargetedByInvokeDirect = previous.virtualMethodsTargetedByInvokeDirect;
this.liveMethods = previous.liveMethods;
this.fieldAccessInfoCollection = previous.fieldAccessInfoCollection;
- this.instanceFieldsWrittenOnlyInEnclosingInstanceInitializers =
- previous.instanceFieldsWrittenOnlyInEnclosingInstanceInitializers;
- this.staticFieldsWrittenOnlyInEnclosingStaticInitializer =
- previous.staticFieldsWrittenOnlyInEnclosingStaticInitializer;
this.pinnedItems = previous.pinnedItems;
this.mayHaveSideEffects = previous.mayHaveSideEffects;
this.noSideEffects = previous.noSideEffects;
@@ -668,10 +645,6 @@
info.clearWrites();
}
});
- result.staticFieldsWrittenOnlyInEnclosingStaticInitializer =
- filter(
- staticFieldsWrittenOnlyInEnclosingStaticInitializer,
- not(noLongerWrittenFields::contains));
return result;
}
@@ -785,16 +758,20 @@
return false;
}
- public boolean isInstanceFieldWrittenOnlyInEnclosingInstanceInitializers(DexEncodedField field) {
- assert checkIfObsolete();
- assert isFieldWritten(field) : "Expected field `" + field.toSourceString() + "` to be written";
- return instanceFieldsWrittenOnlyInEnclosingInstanceInitializers.contains(field.field);
- }
-
public boolean isStaticFieldWrittenOnlyInEnclosingStaticInitializer(DexEncodedField field) {
assert checkIfObsolete();
assert isFieldWritten(field) : "Expected field `" + field.toSourceString() + "` to be written";
- return staticFieldsWrittenOnlyInEnclosingStaticInitializer.contains(field.field);
+ if (!isPinned(field.field)) {
+ DexEncodedMethod staticInitializer =
+ definitionFor(field.field.holder).asProgramClass().getClassInitializer();
+ if (staticInitializer != null) {
+ FieldAccessInfo fieldAccessInfo = fieldAccessInfoCollection.get(field.field);
+ return fieldAccessInfo != null
+ && fieldAccessInfo.isWritten()
+ && !fieldAccessInfo.isWrittenOutside(staticInitializer);
+ }
+ }
+ return false;
}
public boolean mayPropagateValueFor(DexReference reference) {
@@ -809,8 +786,7 @@
private static <T extends PresortedComparable<T>> ImmutableSortedSet<T> rewriteItems(
Set<T> original, Function<T, T> rewrite) {
- ImmutableSortedSet.Builder<T> builder =
- new ImmutableSortedSet.Builder<>(PresortedComparable::slowCompare);
+ Builder<T> builder = new Builder<>(PresortedComparable::slowCompare);
for (T item : original) {
builder.add(rewrite.apply(item));
}
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 fe19510..400ce93 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -8,7 +8,6 @@
import static com.android.tools.r8.naming.IdentifierNameStringUtils.isReflectionMethod;
import static com.android.tools.r8.shaking.AnnotationRemover.shouldKeepAnnotation;
import static com.android.tools.r8.shaking.EnqueuerUtils.toImmutableSortedMap;
-import static com.google.common.base.Predicates.not;
import com.android.tools.r8.Diagnostic;
import com.android.tools.r8.dex.IndexedItemCollection;
@@ -146,10 +145,6 @@
private final Map<DexMethod, Set<DexEncodedMethod>> staticInvokes = new IdentityHashMap<>();
private final FieldAccessInfoCollectionImpl fieldAccessInfoCollection =
new FieldAccessInfoCollectionImpl();
- private final Set<DexField> instanceFieldsWrittenOutsideEnclosingInstanceInitializers =
- Sets.newIdentityHashSet();
- private final Set<DexField> staticFieldsWrittenOutsideEnclosingStaticInitializer =
- Sets.newIdentityHashSet();
private final Set<DexCallSite> callSites = Sets.newIdentityHashSet();
private final Set<DexReference> identifierNameStrings = Sets.newIdentityHashSet();
@@ -206,6 +201,10 @@
* its implementation may be removed and it may be marked abstract.
*/
private final SetWithReason<DexEncodedMethod> targetedMethods;
+
+ /** Subset of 'targetedMethods' for which the method must not be marked abstract. */
+ private final Set<DexEncodedMethod> targetedMethodsThatMustRemainNonAbstract;
+
/**
* Set of program methods that are used as the bootstrap method for an invoke-dynamic instruction.
*/
@@ -317,6 +316,10 @@
liveAnnotations = new SetWithReason<>(graphReporter::registerAnnotation);
instantiatedTypes = new SetWithReason<>(graphReporter::registerClass);
targetedMethods = new SetWithReason<>(graphReporter::registerMethod);
+ // This set is only populated in edge cases due to multiple default interface methods.
+ // The set is generally expected to be empty and in the unlikely chance it is not, it will
+ // likely contain two methods. Thus the default capacity of 2.
+ targetedMethodsThatMustRemainNonAbstract = SetUtils.newIdentityHashSet(2);
liveMethods = new LiveMethodsSet(graphReporter::registerMethod);
liveFields = new SetWithReason<>(graphReporter::registerField);
instantiatedInterfaceTypes = new SetWithReason<>(graphReporter::registerInterface);
@@ -375,18 +378,6 @@
}
}
- private Set<DexField> instanceFieldsWrittenOnlyInEnclosingInstanceInitializers() {
- Set<DexField> result = getNonPinnedWrittenFields(not(DexEncodedField::isStatic));
- result.removeAll(instanceFieldsWrittenOutsideEnclosingInstanceInitializers);
- return result;
- }
-
- private Set<DexField> staticFieldsWrittenOnlyInEnclosingStaticInitializer() {
- Set<DexField> result = getNonPinnedWrittenFields(DexEncodedField::isStatic);
- result.removeAll(staticFieldsWrittenOutsideEnclosingStaticInitializer);
- return result;
- }
-
private Set<DexField> getNonPinnedWrittenFields(Predicate<DexEncodedField> predicate) {
Set<DexField> result = Sets.newIdentityHashSet();
fieldAccessInfoCollection.forEach(
@@ -960,14 +951,6 @@
Log.verbose(getClass(), "Register Iput `%s`.", field);
}
- // If it is written outside of the <init>s of its enclosing class, record it.
- boolean isWrittenOutsideEnclosingInstanceInitializers =
- currentMethod.method.holder != encodedField.field.holder
- || !currentMethod.isInstanceInitializer();
- if (isWrittenOutsideEnclosingInstanceInitializers) {
- instanceFieldsWrittenOutsideEnclosingInstanceInitializers.add(encodedField.field);
- }
-
// If unused interface removal is enabled, then we won't necessarily mark the actual holder of
// the field as live, if the holder is an interface.
if (appView.options().enableUnusedInterfaceRemoval) {
@@ -1062,14 +1045,6 @@
}
}
- // If it is written outside of the <clinit> of its enclosing class, record it.
- boolean isWrittenOutsideEnclosingStaticInitializer =
- currentMethod.method.holder != encodedField.field.holder
- || !currentMethod.isClassInitializer();
- if (isWrittenOutsideEnclosingStaticInitializer) {
- staticFieldsWrittenOutsideEnclosingStaticInitializer.add(encodedField.field);
- }
-
if (encodedField.field != field) {
// Mark the non-rebound field access as targeted. Note that this should only be done if the
// field is not a dead proto field (in which case we bail-out above).
@@ -2037,13 +2012,15 @@
private MarkedResolutionTarget findAndMarkResolutionTarget(
DexMethod method, boolean interfaceInvoke, KeepReason reason) {
- DexEncodedMethod resolutionTarget =
- appInfo.resolveMethod(method.holder, method, interfaceInvoke).asResultOfResolve();
- if (resolutionTarget == null) {
- reportMissingMethod(method);
+ ResolutionResult resolutionResult =
+ appInfo.resolveMethod(method.holder, method, interfaceInvoke);
+ if (!resolutionResult.hasSingleTarget()) {
+ // If the resolution fails, mark each dependency causing a failure.
+ markFailedResolutionTargets(resolutionResult, reason);
return MarkedResolutionTarget.unresolved();
}
+ DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
DexClass resolutionTargetClass = appInfo.definitionFor(resolutionTarget.method.holder);
if (resolutionTargetClass == null) {
reportMissingClass(resolutionTarget.method.holder);
@@ -2065,6 +2042,17 @@
return new MarkedResolutionTarget(resolutionTargetClass, resolutionTarget);
}
+ private void markFailedResolutionTargets(ResolutionResult failedResolution, KeepReason reason) {
+ failedResolution.forEachTarget(
+ method -> {
+ DexProgramClass clazz = getProgramClassOrNull(method.method.holder);
+ if (clazz != null) {
+ targetedMethodsThatMustRemainNonAbstract.add(method);
+ markMethodAsTargeted(clazz, method, reason);
+ }
+ });
+ }
+
private DexMethod generatedEnumValuesMethod(DexClass enumClass) {
DexType arrayOfEnumClass =
appView
@@ -2200,6 +2188,8 @@
Collections.unmodifiableSet(instantiatedAppServices),
SetUtils.mapIdentityHashSet(instantiatedTypes.getItems(), DexProgramClass::getType),
Enqueuer.toSortedDescriptorSet(targetedMethods.getItems()),
+ SetUtils.mapIdentityHashSet(
+ targetedMethodsThatMustRemainNonAbstract, DexEncodedMethod::getKey),
ImmutableSortedSet.copyOf(DexMethod::slowCompareTo, bootstrapMethods),
ImmutableSortedSet.copyOf(DexMethod::slowCompareTo, methodsTargetedByInvokeDynamic),
ImmutableSortedSet.copyOf(
@@ -2207,8 +2197,6 @@
toSortedDescriptorSet(liveMethods.getItems()),
// Filter out library fields and pinned fields, because these are read by default.
fieldAccessInfoCollection,
- instanceFieldsWrittenOnlyInEnclosingInstanceInitializers(),
- staticFieldsWrittenOnlyInEnclosingStaticInitializer(),
// TODO(b/132593519): Do we require these sets to be sorted for determinism?
toImmutableSortedMap(virtualInvokes, PresortedComparable::slowCompare),
toImmutableSortedMap(interfaceInvokes, PresortedComparable::slowCompare),
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 b7e9923..af0efe5 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -288,13 +288,15 @@
}
// Final classes cannot be abstract, so we have to keep the method in that case.
// Also some other kinds of methods cannot be abstract, so keep them around.
- boolean allowAbstract = clazz.accessFlags.isAbstract()
- && !method.accessFlags.isFinal()
- && !method.accessFlags.isNative()
- && !method.accessFlags.isStrict()
- && !method.accessFlags.isSynchronized()
- && !method.accessFlags.isPrivate()
- && !method.accessFlags.isStatic();
+ boolean allowAbstract =
+ clazz.accessFlags.isAbstract()
+ && !method.accessFlags.isFinal()
+ && !method.accessFlags.isNative()
+ && !method.accessFlags.isStrict()
+ && !method.accessFlags.isSynchronized()
+ && !method.accessFlags.isPrivate()
+ && !method.accessFlags.isStatic()
+ && !appInfo.targetedMethodsThatMustRemainNonAbstract.contains(method.method);
// Private methods and static methods can only be targeted yet non-live as the result of
// an invalid invoke. They will not actually be called at runtime but we have to keep them
// as non-abstract (see above) to produce the same failure mode.
diff --git a/src/main/java/com/android/tools/r8/utils/FileUtils.java b/src/main/java/com/android/tools/r8/utils/FileUtils.java
index 7185445..24c35fb 100644
--- a/src/main/java/com/android/tools/r8/utils/FileUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/FileUtils.java
@@ -28,6 +28,7 @@
public static final String JAR_EXTENSION = ".jar";
public static final String ZIP_EXTENSION = ".zip";
public static final String JAVA_EXTENSION = ".java";
+ public static final String KT_EXTENSION = ".kt";
public static final String MODULE_INFO_CLASS = "module-info.class";
public static final boolean isAndroid =
diff --git a/src/test/java/com/android/tools/r8/KotlinCompilerTestBuilder.java b/src/test/java/com/android/tools/r8/KotlinCompilerTestBuilder.java
new file mode 100644
index 0000000..7fa1ba1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/KotlinCompilerTestBuilder.java
@@ -0,0 +1,90 @@
+// 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.R8Command.Builder;
+import com.android.tools.r8.TestBase.Backend;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.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;
+
+public class KotlinCompilerTestBuilder
+ extends TestCompilerBuilder<
+ R8Command,
+ Builder,
+ KotlinTestCompileResult,
+ KotlinTestRunResult,
+ KotlinCompilerTestBuilder> {
+
+ private List<Path> ktPaths;
+ private List<Path> classPaths;
+
+ private KotlinCompilerTestBuilder(TestState state, Builder builder) {
+ super(state, builder, Backend.CF);
+ }
+
+ public static KotlinCompilerTestBuilder create(TestState state) {
+ return new KotlinCompilerTestBuilder(state, R8Command.builder());
+ }
+
+ @Override
+ KotlinCompilerTestBuilder self() {
+ return this;
+ }
+
+ @Override
+ KotlinTestCompileResult internalCompile(
+ Builder builder,
+ Consumer<InternalOptions> optionsConsumer,
+ Supplier<AndroidApp> app)
+ throws CompilationFailedException {
+ try {
+ Path outputFolder = getState().getNewTempFolder();
+ Path outputJar = outputFolder.resolve("output.jar");
+ ProcessResult processResult =
+ ToolHelper.runKotlinc(
+ null,
+ classPaths,
+ outputJar,
+ null, // extra options
+ ktPaths == null ? ToolHelper.EMPTY_PATH : ktPaths.toArray(ToolHelper.EMPTY_PATH)
+ );
+ return new KotlinTestCompileResult(getState(), outputJar, processResult);
+ } catch (IOException e) {
+ throw new CompilationFailedException(e);
+ }
+ }
+
+ @Override
+ public KotlinCompilerTestBuilder addClasspathClasses(Collection<Class<?>> classes) {
+ throw new Unimplemented("No support for adding classpath classes directly");
+ }
+
+ @Override
+ public KotlinCompilerTestBuilder addClasspathFiles(Collection<Path> files) {
+ if (classPaths == null) {
+ classPaths = new ArrayList<>();
+ }
+ classPaths.addAll(files);
+ return self();
+ }
+
+ public KotlinCompilerTestBuilder addSourceFiles(Path... files) {
+ if (ktPaths == null) {
+ ktPaths = new ArrayList<>();
+ }
+ ktPaths.addAll(Arrays.asList(files));
+ return self();
+ }
+
+}
diff --git a/src/test/java/com/android/tools/r8/KotlinTestBase.java b/src/test/java/com/android/tools/r8/KotlinTestBase.java
index a728201..87f946c 100644
--- a/src/test/java/com/android/tools/r8/KotlinTestBase.java
+++ b/src/test/java/com/android/tools/r8/KotlinTestBase.java
@@ -4,17 +4,20 @@
package com.android.tools.r8;
import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.graph.DexAnnotation;
+import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.utils.FileUtils;
import java.nio.file.Path;
import java.nio.file.Paths;
public abstract class KotlinTestBase extends TestBase {
- public static final String checkParameterIsNotNullSignature =
+ protected static final String checkParameterIsNotNullSignature =
"void kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull("
+ "java.lang.Object, java.lang.String)";
- public static final String throwParameterIsNotNullExceptionSignature =
+ protected static final String throwParameterIsNotNullExceptionSignature =
"void kotlin.jvm.internal.Intrinsics.throwParameterIsNullException(java.lang.String)";
+ protected static final String METADATA_DESCRIPTOR = "Lkotlin/Metadata;";
private static final String RSRC = "kotlinR8TestResources";
@@ -24,6 +27,14 @@
this.targetVersion = targetVersion;
}
+ protected static Path getKotlinFileInTest(String folder, String fileName) {
+ return Paths.get(ToolHelper.TESTS_DIR, "java", folder, fileName + FileUtils.KT_EXTENSION);
+ }
+
+ protected static Path getKotlinFileInResource(String folder, String fileName) {
+ return Paths.get(ToolHelper.TESTS_DIR, RSRC, folder, fileName + FileUtils.KT_EXTENSION);
+ }
+
protected Path getKotlinJarFile(String folder) {
return Paths.get(ToolHelper.TESTS_BUILD_DIR, RSRC,
targetVersion.getFolderName(), folder + FileUtils.JAR_EXTENSION);
@@ -38,4 +49,12 @@
return Paths.get(ToolHelper.TESTS_DIR, RSRC, folder, mappingFileName);
}
+ protected DexAnnotation retrieveMetadata(DexClass dexClass) {
+ for (DexAnnotation annotation : dexClass.annotations.annotations) {
+ if (annotation.annotation.type.toDescriptorString().equals(METADATA_DESCRIPTOR)) {
+ return annotation;
+ }
+ }
+ return null;
+ }
}
diff --git a/src/test/java/com/android/tools/r8/KotlinTestCompileResult.java b/src/test/java/com/android/tools/r8/KotlinTestCompileResult.java
new file mode 100644
index 0000000..550e72c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/KotlinTestCompileResult.java
@@ -0,0 +1,52 @@
+// 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.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.AndroidApp;
+import java.nio.file.Path;
+
+public class KotlinTestCompileResult
+ extends TestCompileResult<KotlinTestCompileResult, KotlinTestRunResult> {
+
+ private final Path outputJar;
+ private final ProcessResult processResult;
+
+ KotlinTestCompileResult(TestState state, Path outputJar, ProcessResult processResult) {
+ super(state, AndroidApp.builder().addProgramFile(outputJar).build(), OutputMode.ClassFile);
+ this.outputJar = outputJar;
+ this.processResult = processResult;
+ }
+
+ public Path outputJar() {
+ return outputJar;
+ }
+
+ public int exitCode() {
+ return processResult.exitCode;
+ }
+
+ public String stdout() {
+ return processResult.stdout;
+ }
+
+ public String stderr() {
+ return processResult.stderr;
+ }
+
+ @Override
+ public KotlinTestCompileResult self() {
+ return this;
+ }
+
+ @Override
+ public TestDiagnosticMessages getDiagnosticMessages() {
+ throw new UnsupportedOperationException("No diagnostics messages from kotlinc");
+ }
+
+ @Override
+ public KotlinTestRunResult createRunResult(TestRuntime runtime, ProcessResult result) {
+ return new KotlinTestRunResult(app, runtime, result);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/KotlinTestRunResult.java b/src/test/java/com/android/tools/r8/KotlinTestRunResult.java
new file mode 100644
index 0000000..a4e413c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/KotlinTestRunResult.java
@@ -0,0 +1,19 @@
+// 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.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.AndroidApp;
+
+public class KotlinTestRunResult extends TestRunResult<KotlinTestRunResult> {
+
+ KotlinTestRunResult(AndroidApp app, TestRuntime runtime, ProcessResult result) {
+ super(app, runtime, result);
+ }
+
+ @Override
+ protected KotlinTestRunResult self() {
+ return this;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index c927084..1fc315b 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -111,6 +111,10 @@
return JvmTestBuilder.create(new TestState(temp));
}
+ public static KotlinCompilerTestBuilder testForKotlin(TemporaryFolder temp) {
+ return KotlinCompilerTestBuilder.create(new TestState(temp));
+ }
+
public static ProguardTestBuilder testForProguard(TemporaryFolder temp) {
return ProguardTestBuilder.create(new TestState(temp));
}
@@ -147,6 +151,10 @@
return testForJvm(temp);
}
+ public KotlinCompilerTestBuilder testForKotlin() {
+ return testForKotlin(temp);
+ }
+
public TestBuilder<? extends TestRunResult<?>, ?> testForRuntime(
TestRuntime runtime, AndroidApiLevel apiLevel) {
if (runtime.isCf()) {
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 66de7d1..a84064a 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -80,6 +80,8 @@
public class ToolHelper {
+ static final Path[] EMPTY_PATH = {};
+
public static final String SOURCE_DIR = "src/main/java/";
public static final String BUILD_DIR = "build/";
public static final String GENERATED_TEST_BUILD_DIR = BUILD_DIR + "generated/test/";
@@ -126,6 +128,8 @@
public static final String RHINO_ANDROID_JAR =
"third_party/rhino-android-1.1.1/rhino-android-1.1.1.jar";
public static final String RHINO_JAR = "third_party/rhino-1.7.10/rhino-1.7.10.jar";
+ static final String KT_PRELOADER = "third_party/kotlin/kotlinc/lib/kotlin-preloader.jar";
+ static final String KT_COMPILER = "third_party/kotlin/kotlinc/lib/kotlin-compiler.jar";
public static final String KT_STDLIB = "third_party/kotlin/kotlinc/lib/kotlin-stdlib.jar";
public static final String KT_REFLECT = "third_party/kotlin/kotlinc/lib/kotlin-reflect.jar";
private static final String ANDROID_JAR_PATTERN = "third_party/android_jar/lib-v%d/android.jar";
@@ -1309,6 +1313,42 @@
return ToolHelper.runProcess(builder);
}
+ public static ProcessResult runKotlinc(
+ CfVm runtime,
+ List<Path> classPaths,
+ Path directoryToCompileInto,
+ List<String> extraOptions,
+ Path... filesToCompile)
+ throws IOException {
+ String jvm = runtime == null ? getSystemJavaExecutable() : getJavaExecutable(runtime);
+ List<String> cmdline = new ArrayList<String>(Arrays.asList(jvm));
+ cmdline.add("-jar");
+ cmdline.add(KT_PRELOADER);
+ cmdline.add("org.jetbrains.kotlin.preloading.Preloader");
+ cmdline.add("-cp");
+ cmdline.add(KT_COMPILER);
+ cmdline.add("org.jetbrains.kotlin.cli.jvm.K2JVMCompiler");
+ String[] strings = Arrays.stream(filesToCompile).map(Path::toString).toArray(String[]::new);
+ Collections.addAll(cmdline, strings);
+ cmdline.add("-d");
+ cmdline.add(directoryToCompileInto.toString());
+ List<String> cp = classPaths == null ? null
+ : classPaths.stream().map(Path::toString).collect(Collectors.toList());
+ if (cp != null) {
+ cmdline.add("-cp");
+ if (isWindows()) {
+ cmdline.add(String.join(";", cp));
+ } else {
+ cmdline.add(String.join(":", cp));
+ }
+ }
+ if (extraOptions != null) {
+ cmdline.addAll(extraOptions);
+ }
+ ProcessBuilder builder = new ProcessBuilder(cmdline);
+ return ToolHelper.runProcess(builder);
+ }
+
public static ProcessResult runJava(
CfVm runtime, List<String> vmArgs, List<Path> classpath, String... args) throws IOException {
String cp =
diff --git a/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java b/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java
index 12fe936..3e9a3cb 100644
--- a/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java
+++ b/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java
@@ -20,7 +20,6 @@
import com.android.tools.r8.ResourceException;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.naming.MemberNaming.FieldSignature;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.utils.AndroidApiLevel;
@@ -37,7 +36,6 @@
import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
import com.android.tools.r8.utils.codeinspector.FoundFieldSubject;
import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
-import com.google.common.collect.ImmutableList;
import com.google.common.io.ByteStreams;
import com.google.common.io.Closer;
import java.io.File;
@@ -142,19 +140,13 @@
if (pgConfs != null) {
// Sanitize libraries for apps relying on the Proguard behaviour of lookup in program
// classes before library classes. See tools/sanitize_libraries.py for more information.
- Path sanitizedLibrary = temp.getRoot().toPath().resolve("sanitized_lib.jar");
- Path sanitizedPgConf = temp.getRoot().toPath().resolve("sanitized.config");
- List<String> command =
- new ImmutableList.Builder<String>()
- .add("tools/sanitize_libraries.py")
- .add(sanitizedLibrary.toString())
- .add(sanitizedPgConf.toString())
- .addAll(pgConfs)
- .build();
- ProcessResult result = ToolHelper.runProcess(new ProcessBuilder(command));
- assert result.exitCode == 0;
- builder.addProguardConfigurationFiles(
- pgConfs.stream().map(Paths::get).collect(Collectors.toList()));
+ LibrarySanitizer librarySanitizer =
+ new LibrarySanitizer(temp)
+ .addProguardConfigurationFiles(
+ pgConfs.stream().map(Paths::get).collect(Collectors.toList()))
+ .sanitize();
+ builder.addLibraryFiles(librarySanitizer.getSanitizedLibrary());
+ builder.addProguardConfigurationFiles(librarySanitizer.getSanitizedProguardConfiguration());
} else {
builder.setDisableTreeShaking(true);
builder.setDisableMinification(true);
diff --git a/src/test/java/com/android/tools/r8/internal/LibrarySanitizer.java b/src/test/java/com/android/tools/r8/internal/LibrarySanitizer.java
new file mode 100644
index 0000000..c4509c4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/internal/LibrarySanitizer.java
@@ -0,0 +1,86 @@
+// 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.internal;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.FileUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.rules.TemporaryFolder;
+
+class LibrarySanitizer {
+
+ private final Path sanitizedLibrary;
+ private final Path sanitizedPgConf;
+
+ private final List<Path> libraryFiles = new ArrayList<>();
+ private final List<Path> programFiles = new ArrayList<>();
+ private final List<Path> proguardConfigurationFiles = new ArrayList<>();
+
+ LibrarySanitizer(TemporaryFolder temp) {
+ this.sanitizedLibrary = temp.getRoot().toPath().resolve("sanitized_lib.jar");
+ this.sanitizedPgConf = temp.getRoot().toPath().resolve("sanitized.config");
+ }
+
+ LibrarySanitizer assertSanitizedProguardConfigurationIsEmpty() throws IOException {
+ if (sanitizedPgConf.toFile().exists()) {
+ List<String> lines = FileUtils.readAllLines(sanitizedPgConf);
+ for (String line : lines) {
+ assertTrue(line.trim().isEmpty());
+ }
+ }
+ return this;
+ }
+
+ LibrarySanitizer addLibraryFiles(List<Path> libraryFiles) {
+ this.libraryFiles.addAll(libraryFiles);
+ return this;
+ }
+
+ LibrarySanitizer addProgramFiles(List<Path> programFiles) {
+ this.programFiles.addAll(programFiles);
+ return this;
+ }
+
+ LibrarySanitizer addProguardConfigurationFiles(List<Path> proguardConfigurationFiles) {
+ this.proguardConfigurationFiles.addAll(proguardConfigurationFiles);
+ return this;
+ }
+
+ Path getSanitizedLibrary() {
+ return sanitizedLibrary;
+ }
+
+ Path getSanitizedProguardConfiguration() {
+ return sanitizedPgConf;
+ }
+
+ LibrarySanitizer sanitize() throws IOException {
+ ImmutableList.Builder<String> command =
+ new ImmutableList.Builder<String>()
+ .add("tools/sanitize_libraries.py")
+ .add(sanitizedLibrary.toString())
+ .add(sanitizedPgConf.toString());
+ for (Path programFile : programFiles) {
+ command.add("--injar").add(programFile.toString());
+ }
+ for (Path libraryFile : libraryFiles) {
+ command.add("--libraryjar").add(libraryFile.toString());
+ }
+ for (Path proguardConfigurationFile : proguardConfigurationFiles) {
+ command.add("--pgconf").add(proguardConfigurationFile.toString());
+ }
+ ProcessResult result = ToolHelper.runProcess(new ProcessBuilder(command.build()));
+ assertEquals(result.command, 0, result.exitCode);
+ return this;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/internal/YouTubeCompilationBase.java b/src/test/java/com/android/tools/r8/internal/YouTubeCompilationBase.java
index 9fc88c8..df8bcb0 100644
--- a/src/test/java/com/android/tools/r8/internal/YouTubeCompilationBase.java
+++ b/src/test/java/com/android/tools/r8/internal/YouTubeCompilationBase.java
@@ -34,6 +34,10 @@
Paths.get(ToolHelper.PROGUARD_SETTINGS_FOR_INTERNAL_APPS).resolve(PG_CONF));
}
+ protected List<Path> getLibraryFiles() {
+ return ImmutableList.of(Paths.get(base, "legacy_YouTubeRelease_combined_library_jars.jar"));
+ }
+
protected List<Path> getProgramFiles() throws IOException {
List<Path> result = new ArrayList<>();
for (Path keepRuleFile : getKeepRuleFiles()) {
diff --git a/src/test/java/com/android/tools/r8/internal/YouTubeV1444TreeShakeJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/YouTubeV1444TreeShakeJarVerificationTest.java
index 016427f..49b41aa 100644
--- a/src/test/java/com/android/tools/r8/internal/YouTubeV1444TreeShakeJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/YouTubeV1444TreeShakeJarVerificationTest.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.analysis.ProtoApplicationStats;
import org.junit.Test;
@@ -43,8 +44,17 @@
assumeTrue(isLocalDevelopment());
assumeTrue(shouldRunSlowTests());
+ LibrarySanitizer librarySanitizer =
+ new LibrarySanitizer(temp)
+ .addProgramFiles(getProgramFiles())
+ .addLibraryFiles(getLibraryFiles())
+ .sanitize()
+ .assertSanitizedProguardConfigurationIsEmpty();
+
R8TestCompileResult compileResult =
testForR8(parameters.getBackend())
+ .addProgramFiles(getProgramFiles())
+ .addLibraryFiles(librarySanitizer.getSanitizedLibrary())
.addKeepRuleFiles(getKeepRuleFiles())
.addOptionsModification(
options -> {
@@ -63,6 +73,7 @@
assert !options.enableStringSwitchConversion;
options.enableStringSwitchConversion = true;
})
+ .setMinApi(AndroidApiLevel.H_MR2)
.allowUnusedProguardConfigurationRules()
.compile();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/sync/InlineStaticSynchronizedMethodTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/sync/InlineStaticSynchronizedMethodTest.java
index a8578e3..8a606a7 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/sync/InlineStaticSynchronizedMethodTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/sync/InlineStaticSynchronizedMethodTest.java
@@ -30,7 +30,7 @@
@Parameters(name = "{0}")
public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimes().build();
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
}
public InlineStaticSynchronizedMethodTest(TestParameters parameters) {
@@ -42,7 +42,7 @@
testForR8(parameters.getBackend())
.addInnerClasses(InlineStaticSynchronizedMethodTest.class)
.addKeepMainRule(TestClass.class)
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
.compile()
.inspect(this::verifySynchronizedMethodsAreInlined)
.run(parameters.getRuntime(), TestClass.class)
@@ -73,8 +73,8 @@
}
static void log(String message) {
- logs.add(message);
System.out.println(message);
+ logs.add(message);
}
static void waitUntil(BooleanSupplier condition) {
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameTest.java
new file mode 100644
index 0000000..160dc3f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRenameTest.java
@@ -0,0 +1,172 @@
+// 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.kotlin.metadata;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.KotlinTestBase;
+import com.android.tools.r8.KotlinTestCompileResult;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.graph.DexAnnotation;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import java.nio.file.Path;
+import java.util.Collection;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MetadataRenameTest extends KotlinTestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0} target: {1}")
+ public static Collection<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withCfRuntimes().build(), KotlinTargetVersion.values());
+ }
+
+ public MetadataRenameTest(TestParameters parameters, KotlinTargetVersion targetVersion) {
+ super(targetVersion);
+ this.parameters = parameters;
+ }
+
+ private static final String PKG_PREFIX =
+ DescriptorUtils.getBinaryNameFromJavaType(MetadataRenameTest.class.getPackage().getName());
+ private static Path supertypeLibJar;
+ private static Path extLibJar;
+
+ @BeforeClass
+ public static void createLibJar() throws Exception {
+ String supertypeLibFolder = PKG_PREFIX + "/supertype_lib";
+ supertypeLibJar = getStaticTemp().newFile("supertype_lib.jar").toPath();
+ ProcessResult processResult =
+ ToolHelper.runKotlinc(
+ null,
+ null,
+ supertypeLibJar,
+ null,
+ getKotlinFileInTest(supertypeLibFolder, "impl"),
+ getKotlinFileInTest(supertypeLibFolder + "/internal", "itf")
+ );
+ assertEquals(0, processResult.exitCode);
+
+ String extLibFolder = PKG_PREFIX + "/extension_lib";
+ extLibJar = getStaticTemp().newFile("ext_lib.jar").toPath();
+ processResult =
+ ToolHelper.runKotlinc(
+ null,
+ null,
+ extLibJar,
+ null,
+ getKotlinFileInTest(extLibFolder, "B")
+ );
+ assertEquals(0, processResult.exitCode);
+ }
+
+ @Test
+ public void b143687784() throws Exception {
+ assumeTrue(parameters.getRuntime().isCf());
+
+ R8TestCompileResult compileResult =
+ testForR8(parameters.getBackend())
+ .addProgramFiles(supertypeLibJar)
+ // Keep non-private members except for ones in `internal` definitions.
+ .addKeepRules("-keep public class !**.internal.**, * { !private *; }")
+ .addKeepAttributes("*Annotation*")
+ .compile();
+ String pkg = getClass().getPackage().getName();
+ final String itfClassName = pkg + ".supertype_lib.internal.Itf";
+ final String implClassName = pkg + ".supertype_lib.Impl";
+ compileResult.inspect(inspector -> {
+ ClassSubject itf = inspector.clazz(itfClassName);
+ assertThat(itf, not(isPresent()));
+
+ ClassSubject impl = inspector.clazz(implClassName);
+ assertThat(impl, isPresent());
+ assertThat(impl, not(isRenamed()));
+ // API entry is kept, hence the presence of Metadata.
+ DexAnnotation metadata = retrieveMetadata(impl.getDexClass());
+ assertNotNull(metadata);
+ // TODO(b/143687784): test its metadata doesn't point to shrunken itf.
+ });
+
+ Path r8ProcessedLibZip = temp.newFile("r8-lib.zip").toPath();
+ compileResult.writeToZip(r8ProcessedLibZip);
+
+ String appFolder = PKG_PREFIX + "/supertype_app";
+ KotlinTestCompileResult kotlinTestCompileResult =
+ testForKotlin()
+ .addClasspathFiles(r8ProcessedLibZip)
+ .addSourceFiles(getKotlinFileInTest(appFolder, "main"))
+ .compile();
+ // TODO(b/143687784): should be able to compile!
+ assertNotEquals(0, kotlinTestCompileResult.exitCode());
+ assertThat(
+ kotlinTestCompileResult.stderr(),
+ containsString("unresolved supertypes: " + pkg + ".supertype_lib.internal.Itf"));
+ }
+
+ @Test
+ public void testMetadataInExtension() throws Exception {
+ assumeTrue(parameters.getRuntime().isCf());
+
+ R8TestCompileResult compileResult =
+ testForR8(parameters.getBackend())
+ .addProgramFiles(extLibJar)
+ // Keep the B class and its interface (which has the doStuff method).
+ .addKeepRules("-keep class **.B")
+ .addKeepRules("-keep class **.I { <methods>; }")
+ // Keep the BKt extension method which requires metadata
+ // to be called with Kotlin syntax from other kotlin code.
+ .addKeepRules("-keep class **.BKt { <methods>; }")
+ .addKeepAttributes("*Annotation*")
+ .compile();
+ String pkg = getClass().getPackage().getName();
+ final String superClassName = pkg + ".extension_lib.Super";
+ final String bClassName = pkg + ".extension_lib.B";
+ compileResult.inspect(inspector -> {
+ ClassSubject sup = inspector.clazz(superClassName);
+ assertThat(sup, not(isPresent()));
+
+ ClassSubject impl = inspector.clazz(bClassName);
+ assertThat(impl, isPresent());
+ assertThat(impl, not(isRenamed()));
+ // API entry is kept, hence the presence of Metadata.
+ DexAnnotation metadata = retrieveMetadata(impl.getDexClass());
+ assertNotNull(metadata);
+ // TODO(b/143687784): test its metadata doesn't point to shrunken Super.
+ });
+
+ Path r8ProcessedLibZip = temp.newFile("r8-lib.zip").toPath();
+ compileResult.writeToZip(r8ProcessedLibZip);
+
+ String appFolder = PKG_PREFIX + "/extension_app";
+ KotlinTestCompileResult kotlinTestCompileResult =
+ testForKotlin()
+ .addClasspathFiles(r8ProcessedLibZip)
+ .addSourceFiles(getKotlinFileInTest(appFolder, "main"))
+ .compile();
+ // TODO(b/143687784): should be able to compile!
+ assertNotEquals(0, kotlinTestCompileResult.exitCode());
+ assertThat(
+ kotlinTestCompileResult.stderr(),
+ containsString("unresolved supertypes: " + pkg + ".extension_lib.Super"));
+ assertThat(kotlinTestCompileResult.stderr(), containsString("unresolved reference: doStuff"));
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/MetadataStripTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java
similarity index 80%
rename from src/test/java/com/android/tools/r8/kotlin/MetadataStripTest.java
rename to src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java
index 204901f..155c91c 100644
--- a/src/test/java/com/android/tools/r8/kotlin/MetadataStripTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataStripTest.java
@@ -1,7 +1,7 @@
// 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.kotlin;
+package com.android.tools.r8.kotlin.metadata;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
@@ -26,15 +26,13 @@
@RunWith(Parameterized.class)
public class MetadataStripTest extends KotlinTestBase {
- private static final String METADATA_DESCRIPTOR = "Lkotlin/Metadata;";
- private static final String KEEP_ANNOTATIONS = "-keepattributes *Annotation*";
private final TestParameters parameters;
@Parameterized.Parameters(name = "{0} target: {1}")
public static Collection<Object[]> data() {
return buildParameters(
- getTestParameters().withAllRuntimes().build(), KotlinTargetVersion.values());
+ getTestParameters().withAllRuntimesAndApiLevels().build(), KotlinTargetVersion.values());
}
public MetadataStripTest(TestParameters parameters, KotlinTargetVersion targetVersion) {
@@ -53,9 +51,9 @@
.addProgramFiles(getJavaJarFile(folder))
.addProgramFiles(ToolHelper.getKotlinReflectJar())
.addKeepMainRule(mainClassName)
- .addKeepRules(KEEP_ANNOTATIONS)
+ .addKeepAttributes("*Annotation*")
.addKeepRules("-keep class kotlin.Metadata")
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), mainClassName);
CodeInspector inspector = result.inspector();
ClassSubject clazz = inspector.clazz(mainClassName);
@@ -70,13 +68,4 @@
assertNull(retrieveMetadata(impl1.getDexClass()));
}
- private DexAnnotation retrieveMetadata(DexClass dexClass) {
- for (DexAnnotation annotation : dexClass.annotations.annotations) {
- if (annotation.annotation.type.toDescriptorString().equals(METADATA_DESCRIPTOR)) {
- return annotation;
- }
- }
- return null;
- }
-
}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/extension_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/extension_app/main.kt
new file mode 100644
index 0000000..246d3fe
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/extension_app/main.kt
@@ -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 com.android.tools.r8.kotlin.metadata.extension_app
+
+import com.android.tools.r8.kotlin.metadata.extension_lib.B
+import com.android.tools.r8.kotlin.metadata.extension_lib.extension
+
+fun main() {
+ B().doStuff()
+ B().extension()
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/extension_lib/B.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/extension_lib/B.kt
new file mode 100644
index 0000000..a6fe9ab
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/extension_lib/B.kt
@@ -0,0 +1,20 @@
+// 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.kotlin.metadata.extension_lib
+
+interface I {
+ fun doStuff()
+}
+
+open class Super : I {
+ override fun doStuff() {
+ println("do stuff")
+ }
+}
+
+class B : Super() { }
+
+fun B.extension() {
+ doStuff()
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/supertype_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/supertype_app/main.kt
new file mode 100644
index 0000000..4192774
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/supertype_app/main.kt
@@ -0,0 +1,17 @@
+// 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.kotlin.metadata.supertype_app
+
+import com.android.tools.r8.kotlin.metadata.supertype_lib.Impl
+
+class ProgramClass : Impl() {
+ override fun foo() {
+ super.foo()
+ println("Program::foo")
+ }
+}
+
+fun main(args: Array<String>) {
+ ProgramClass().foo()
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/supertype_lib/impl.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/supertype_lib/impl.kt
new file mode 100644
index 0000000..448cb10
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/supertype_lib/impl.kt
@@ -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 com.android.tools.r8.kotlin.metadata.supertype_lib
+
+import com.android.tools.r8.kotlin.metadata.supertype_lib.internal.Itf
+
+open class Impl : Itf {
+ override fun foo() {
+ println("Impl::foo")
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/supertype_lib/internal/itf.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/supertype_lib/internal/itf.kt
new file mode 100644
index 0000000..50e18d4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/supertype_lib/internal/itf.kt
@@ -0,0 +1,8 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.kotlin.metadata.supertype_lib.internal
+
+interface Itf {
+ fun foo();
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractLeftTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractLeftTest.java
index cddbcfe..38e564f 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractLeftTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractLeftTest.java
@@ -18,6 +18,7 @@
import com.android.tools.r8.graph.ResolutionResult;
import com.android.tools.r8.resolution.SingleTargetLookupTest;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.DescriptorUtils;
import com.google.common.collect.ImmutableList;
import java.util.Collection;
@@ -63,8 +64,7 @@
ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
List<DexEncodedMethod> resolutionTargets = resolutionResult.asListOfTargets();
assertEquals(1, resolutionTargets.size());
- // TODO(b/144085169): This should resolve to L::f as T::f is not maximally specific.
- assertEquals(T.class.getTypeName(), resolutionTargets.get(0).method.holder.toSourceString());
+ assertEquals(L.class.getTypeName(), resolutionTargets.get(0).method.holder.toSourceString());
}
@Test
@@ -73,7 +73,7 @@
.addProgramClasses(CLASSES)
.addProgramClassFileData(DumpB.dump())
.run(parameters.getRuntime(), Main.class)
- .assertFailureWithErrorThatMatches(getExpectedErrorMatcher());
+ .assertFailureWithErrorThatMatches(getExpectedErrorMatcher(false));
}
@Test
@@ -84,11 +84,15 @@
.addKeepMainRule(Main.class)
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), Main.class)
- // TODO(b/144085169): R8 should not cause the code to no longer fail.
- .assertSuccessWithOutputLines("T::f");
+ .assertFailureWithErrorThatMatches(getExpectedErrorMatcher(true));
}
- private Matcher<String> getExpectedErrorMatcher() {
+ private Matcher<String> getExpectedErrorMatcher(boolean isR8) {
+ if (isR8
+ && (parameters.isCfRuntime() || parameters.getApiLevel().isLessThan(AndroidApiLevel.L))) {
+ // TODO(b/144085169): R8 replaces the entire main method by 'throw null', why?
+ return containsString("NullPointerException");
+ }
if (parameters.isDexRuntime()
&& parameters
.getRuntime()
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractRightTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractRightTest.java
index 2f71762..1601394 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractRightTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAbstractRightTest.java
@@ -18,6 +18,7 @@
import com.android.tools.r8.graph.ResolutionResult;
import com.android.tools.r8.resolution.SingleTargetLookupTest;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.DescriptorUtils;
import com.google.common.collect.ImmutableList;
import java.util.Collection;
@@ -63,8 +64,7 @@
ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
List<DexEncodedMethod> resolutionTargets = resolutionResult.asListOfTargets();
assertEquals(1, resolutionTargets.size());
- // TODO(b/144085169): This should resolve to R::f as T::f is not maximally specific.
- assertEquals(T.class.getTypeName(), resolutionTargets.get(0).method.holder.toSourceString());
+ assertEquals(R.class.getTypeName(), resolutionTargets.get(0).method.holder.toSourceString());
}
@Test
@@ -73,7 +73,7 @@
.addProgramClasses(CLASSES)
.addProgramClassFileData(DumpB.dump())
.run(parameters.getRuntime(), Main.class)
- .assertFailureWithErrorThatMatches(getExpectedErrorMatcher());
+ .assertFailureWithErrorThatMatches(getExpectedErrorMatcher(false));
}
@Test
@@ -84,11 +84,15 @@
.addKeepMainRule(Main.class)
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), Main.class)
- // TODO(b/144085169): R8 should not cause the code to no longer fail.
- .assertSuccessWithOutputLines("T::f");
+ .assertFailureWithErrorThatMatches(getExpectedErrorMatcher(true));
}
- private Matcher<String> getExpectedErrorMatcher() {
+ private Matcher<String> getExpectedErrorMatcher(boolean isR8) {
+ if (isR8
+ && (parameters.isCfRuntime() || parameters.getApiLevel().isLessThan(AndroidApiLevel.L))) {
+ // TODO(b/144085169): R8 replaces the entire main method by 'throw null', why?
+ return containsString("NullPointerException");
+ }
if (parameters.isDexRuntime()
&& parameters
.getRuntime()
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndLeftTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndLeftTest.java
index 03a8e2b..a860e90 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndLeftTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndLeftTest.java
@@ -4,7 +4,6 @@
package com.android.tools.r8.resolution.interfacediamonds;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.TestBase;
@@ -50,11 +49,8 @@
DexMethod method = SingleTargetLookupTest.buildMethod(B.class, "f", appInfo);
ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
List<DexEncodedMethod> resolutionTargets = resolutionResult.asListOfTargets();
- // TODO(b/144085169): The resolution should not include T::f as it is not maximally specific.
- assertEquals(2 /* Should be 1 */, resolutionTargets.size());
- assertTrue(
- resolutionTargets.stream()
- .anyMatch(m -> m.method.holder.toSourceString().equals(L.class.getTypeName())));
+ assertEquals(1, resolutionTargets.size());
+ assertEquals(L.class.getTypeName(), resolutionTargets.get(0).method.holder.toSourceString());
}
@Test
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndRightTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndRightTest.java
index fe96ebc..8e9b843 100644
--- a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndRightTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/DefaultTopAndRightTest.java
@@ -4,7 +4,6 @@
package com.android.tools.r8.resolution.interfacediamonds;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.TestBase;
@@ -50,11 +49,8 @@
DexMethod method = SingleTargetLookupTest.buildMethod(B.class, "f", appInfo);
ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
List<DexEncodedMethod> resolutionTargets = resolutionResult.asListOfTargets();
- // TODO(b/144085169): The resolution should not include T::f as it is not maximally specific.
- assertEquals(2 /* Should be 1 */, resolutionTargets.size());
- assertTrue(
- resolutionTargets.stream()
- .anyMatch(m -> m.method.holder.toSourceString().equals(R.class.getTypeName())));
+ assertEquals(1, resolutionTargets.size());
+ assertEquals(R.class.getTypeName(), resolutionTargets.get(0).method.holder.toSourceString());
}
@Test
diff --git a/src/test/java/com/android/tools/r8/resolution/interfacediamonds/TwoDefaultMethodsWithoutTopTest.java b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/TwoDefaultMethodsWithoutTopTest.java
new file mode 100644
index 0000000..ae678cd
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/interfacediamonds/TwoDefaultMethodsWithoutTopTest.java
@@ -0,0 +1,181 @@
+// 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.resolution.interfacediamonds;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.TestRuntime;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.resolution.SingleTargetLookupTest;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.Collections;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.util.ASMifier;
+
+@RunWith(Parameterized.class)
+public class TwoDefaultMethodsWithoutTopTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public TwoDefaultMethodsWithoutTopTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ private static final List<Class<?>> CLASSES =
+ ImmutableList.of(I.class, J.class, A.class, Main.class);
+
+ @Test
+ public void testResolution() throws Exception {
+ // The resolution is runtime independent, so just run it on the default CF VM.
+ assumeTrue(parameters.getRuntime().equals(TestRuntime.getDefaultJavaRuntime()));
+ AppInfoWithLiveness appInfo =
+ SingleTargetLookupTest.createAppInfoWithLiveness(
+ buildClasses(CLASSES, Collections.emptyList())
+ .addClassProgramData(Collections.singletonList(DumpB.dump()))
+ .build(),
+ Main.class);
+ DexMethod method = SingleTargetLookupTest.buildMethod(B.class, "f", appInfo);
+ ResolutionResult resolutionResult = appInfo.resolveMethod(method.holder, method);
+ List<DexEncodedMethod> resolutionTargets = resolutionResult.asListOfTargets();
+ assertEquals(2, resolutionTargets.size());
+ assertTrue(
+ resolutionTargets.stream()
+ .anyMatch(m -> m.method.holder.toSourceString().equals(I.class.getTypeName())));
+ assertTrue(
+ resolutionTargets.stream()
+ .anyMatch(m -> m.method.holder.toSourceString().equals(J.class.getTypeName())));
+ }
+
+ @Test
+ public void testReference() throws Exception {
+ testForRuntime(parameters)
+ .addProgramClasses(CLASSES)
+ .addProgramClassFileData(DumpB.dump())
+ .run(parameters.getRuntime(), Main.class)
+ .apply(r -> checkResult(r, false));
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(CLASSES)
+ .addProgramClassFileData(DumpB.dump())
+ .addKeepMainRule(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), Main.class)
+ .apply(r -> checkResult(r, true));
+ }
+
+ private void checkResult(TestRunResult<?> runResult, boolean isR8) {
+ // TODO(b/144085169): JDK 11 execution produces a different error condition on the R8 output?
+ if (isR8
+ && parameters.getRuntime().isCf()
+ && parameters.getRuntime().asCf().getVm() == CfVm.JDK11) {
+ runResult.assertFailureWithErrorThatMatches(containsString("AbstractMethodError"));
+ } else if (parameters.isDexRuntime()
+ && parameters.getApiLevel().isLessThan(AndroidApiLevel.N)) {
+ if (isR8) {
+ // TODO(b/144085169): Maybe R8 introduces another error due to removal of targets?
+ runResult.assertFailureWithErrorThatMatches(containsString("AbstractMethodError"));
+ } else {
+ // TODO(b/72208584): Desugare changes error result.
+ runResult.assertSuccessWithOutputLines("I::f");
+ }
+ } else {
+ runResult.assertFailureWithErrorThatMatches(containsString("IncompatibleClassChangeError"));
+ }
+ }
+
+ public interface I {
+ default void f() {
+ System.out.println("I::f");
+ }
+ }
+
+ public interface J {
+ default void f() {
+ System.out.println("J::f");
+ }
+ }
+
+ public static class A implements I {}
+
+ public static class B extends A /* implements J via ASM */ {
+ // Intentionally empty.
+ }
+
+ static class Main {
+ public static void main(String[] args) {
+ new B().f();
+ }
+ }
+
+ private static class DumpB implements Opcodes {
+
+ public static void main(String[] args) throws Exception {
+ ASMifier.main(
+ new String[] {"-debug", ToolHelper.getClassFileForTestClass(B.class).toString()});
+ }
+
+ public static byte[] dump() {
+
+ ClassWriter classWriter = new ClassWriter(0);
+ MethodVisitor methodVisitor;
+
+ classWriter.visit(
+ V1_8,
+ ACC_PUBLIC | ACC_SUPER,
+ DescriptorUtils.getBinaryNameFromJavaType(B.class.getTypeName()),
+ null,
+ DescriptorUtils.getBinaryNameFromJavaType(A.class.getTypeName()),
+ new String[] {
+ // Manually added 'implements J'.
+ DescriptorUtils.getBinaryNameFromJavaType(J.class.getTypeName()),
+ });
+
+ {
+ methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ methodVisitor.visitCode();
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(
+ INVOKESPECIAL,
+ DescriptorUtils.getBinaryNameFromJavaType(A.class.getTypeName()),
+ "<init>",
+ "()V",
+ false);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(1, 1);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
+ }
+}
diff --git a/tools/r8_release.py b/tools/r8_release.py
index cf1125d..f312919 100755
--- a/tools/r8_release.py
+++ b/tools/r8_release.py
@@ -15,7 +15,7 @@
import update_prebuilds_in_android
import utils
-R8_DEV_BRANCH = '1.7'
+R8_DEV_BRANCH = '2.0'
R8_VERSION_FILE = os.path.join(
'src', 'main', 'java', 'com', 'android', 'tools', 'r8', 'Version.java')
THIS_FILE_RELATIVE = os.path.join('tools', 'r8_release.py')
diff --git a/tools/sanitize_libraries.py b/tools/sanitize_libraries.py
index e3e88ce..8ca54a7 100755
--- a/tools/sanitize_libraries.py
+++ b/tools/sanitize_libraries.py
@@ -14,12 +14,14 @@
# classes by creating a new library jar which have all the provided library
# classes which are not also in program classes.
def SanitizeLibrariesInPgconf(
- sanitized_lib_path, sanitized_pgconf_path, pgconfs):
-
- injars = []
- libraryjars = []
-
+ sanitized_lib_path,
+ sanitized_pgconf_path,
+ pgconfs,
+ injars = None,
+ libraryjars = None):
with open(sanitized_pgconf_path, 'w') as sanitized_pgconf:
+ injars = [] if injars is None else injars
+ libraryjars = [] if libraryjars is None else libraryjars
for pgconf in pgconfs:
pgconf_dirname = os.path.abspath(os.path.dirname(pgconf))
first_library_jar = True
@@ -49,7 +51,6 @@
def SanitizeLibraries(sanitized_lib_path, libraryjars, injars):
-
program_entries = set()
library_entries = set()
@@ -68,14 +69,40 @@
output_zf.writestr(zipinfo, input_zf.read(zipinfo))
+def usage(argv, error):
+ print(error)
+ print("Usage: sanitize_libraries.py <sanitized_lib> <sanitized_pgconf> ("
+ + "--injar <existing_injar>"
+ + "|--libraryjar <existing_library_jar>"
+ + "|--pgconf <existing_pgconf>)+")
+ return 1
+
+
def main(argv):
- if (len(argv) < 3):
- print("Wrong number of arguments!")
- print("Usage: sanitize_libraries.py " +
- "<sanitized_lib> <sanitized_pgconf> (<existing_pgconf)+")
- return 1
- else:
- SanitizeLibrariesInPgconf(argv[0], argv[1], argv[2:])
+ if (len(argv) < 4):
+ return usage(argv, "Wrong number of arguments!")
+ pgconfs = []
+ injars = []
+ libraryjars = []
+ i = 2
+ while i < len(argv):
+ directive = argv[i]
+ if directive not in ['--pgconf', '--injar', '--libraryjar']:
+ return usage(
+ argv,
+ 'Unexpected argument, expected one of --pgconf, --injar, and '
+ + '--libraryjar.')
+ if i + 1 >= len(argv):
+ return usage(argv, 'Expected argument after ' + directive + '.')
+ file = argv[i + 1]
+ if directive == '--pgconf':
+ pgconfs.append(file)
+ elif directive == '--injar':
+ injars.append(file)
+ elif directive == '--libraryjar':
+ libraryjars.append(file)
+ i = i + 2
+ SanitizeLibrariesInPgconf(argv[0], argv[1], pgconfs, injars, libraryjars)
if __name__ == '__main__':
sys.exit(main(sys.argv[1:]))