Merge commit '180e357dde825969b7839774ecacf4c4cae4cf05' into dev-release
diff --git a/infra/config/global/luci-scheduler.cfg b/infra/config/global/luci-scheduler.cfg
index dd2df27..ab75708 100644
--- a/infra/config/global/luci-scheduler.cfg
+++ b/infra/config/global/luci-scheduler.cfg
@@ -70,6 +70,7 @@
triggers: "linux-android-7.0.0_release"
triggers: "linux-android-8.1.0_release"
triggers: "linux-android-9.0.0_release"
+ triggers: "linux-android-10.0.0_release"
triggers: "linux-internal_release"
triggers: "linux-jctf_release"
triggers: "linux-run-on-as-app_release"
@@ -78,17 +79,6 @@
triggers: "windows_release"
}
-trigger {
- id: "branch-gitiles-trigger-10"
- acl_sets: "default"
- gitiles: {
- repo: "https://r8.googlesource.com/r8"
- refs: "refs/heads/2.0"
- path_regexps: "src/main/java/com/android/tools/r8/Version.java"
- }
- triggers: "linux-android-10.0.0_release"
-}
-
job {
id: "archive"
@@ -160,6 +150,9 @@
job {
id: "linux-android-4.0.4_release"
acl_sets: "default"
+ triggering_policy: {
+ max_batch_size: 1
+ }
buildbucket {
server: "cr-buildbucket.appspot.com"
bucket: "luci.r8.ci"
@@ -183,6 +176,9 @@
job {
id: "linux-android-4.4.4_release"
acl_sets: "default"
+ triggering_policy: {
+ max_batch_size: 1
+ }
buildbucket {
server: "cr-buildbucket.appspot.com"
bucket: "luci.r8.ci"
@@ -206,6 +202,9 @@
job {
id: "linux-android-5.1.1_release"
acl_sets: "default"
+ triggering_policy: {
+ max_batch_size: 1
+ }
buildbucket {
server: "cr-buildbucket.appspot.com"
bucket: "luci.r8.ci"
@@ -229,6 +228,9 @@
job {
id: "linux-android-6.0.1_release"
acl_sets: "default"
+ triggering_policy: {
+ max_batch_size: 1
+ }
buildbucket {
server: "cr-buildbucket.appspot.com"
bucket: "luci.r8.ci"
@@ -252,6 +254,9 @@
job {
id: "linux-android-7.0.0_release"
acl_sets: "default"
+ triggering_policy: {
+ max_batch_size: 1
+ }
buildbucket {
server: "cr-buildbucket.appspot.com"
bucket: "luci.r8.ci"
@@ -276,7 +281,7 @@
id: "linux-android-8.1.0_release"
acl_sets: "default"
triggering_policy: {
- max_concurrent_invocations: 2
+ max_batch_size: 1
}
buildbucket {
server: "cr-buildbucket.appspot.com"
@@ -303,7 +308,7 @@
id: "linux-android-9.0.0_release"
acl_sets: "default"
triggering_policy: {
- max_concurrent_invocations: 2
+ max_batch_size: 1
}
buildbucket {
server: "cr-buildbucket.appspot.com"
@@ -329,7 +334,7 @@
id: "linux-android-10.0.0_release"
acl_sets: "default"
triggering_policy: {
- max_concurrent_invocations: 2
+ max_batch_size: 1
}
buildbucket {
server: "cr-buildbucket.appspot.com"
@@ -381,6 +386,9 @@
job {
id: "linux-run-on-as-app_release"
acl_sets: "default"
+ triggering_policy: {
+ max_batch_size: 1
+ }
buildbucket {
server: "cr-buildbucket.appspot.com"
bucket: "luci.r8.ci"
@@ -402,6 +410,9 @@
job {
id: "linux-jctf_release"
acl_sets: "default"
+ triggering_policy: {
+ max_batch_size: 1
+ }
buildbucket {
server: "cr-buildbucket.appspot.com"
bucket: "luci.r8.ci"
@@ -412,6 +423,9 @@
job {
id: "linux_release"
acl_sets: "default"
+ triggering_policy: {
+ max_batch_size: 1
+ }
buildbucket {
server: "cr-buildbucket.appspot.com"
bucket: "luci.r8.ci"
@@ -432,6 +446,9 @@
job {
id: "r8cf-linux-jctf_release"
acl_sets: "default"
+ triggering_policy: {
+ max_batch_size: 1
+ }
buildbucket {
server: "cr-buildbucket.appspot.com"
bucket: "luci.r8.ci"
@@ -456,7 +473,7 @@
id: "windows_release"
acl_sets: "default"
triggering_policy: {
- max_concurrent_invocations: 3
+ max_batch_size: 1
}
buildbucket {
server: "cr-buildbucket.appspot.com"
diff --git a/src/library_desugar/desugar_jdk_libs.json b/src/library_desugar/desugar_jdk_libs.json
index 07765bb..8c023aa 100644
--- a/src/library_desugar/desugar_jdk_libs.json
+++ b/src/library_desugar/desugar_jdk_libs.json
@@ -2,7 +2,7 @@
"configuration_format_version": 4,
"group_id" : "com.tools.android",
"artifact_id" : "desugar_jdk_libs",
- "version": "0.11.2",
+ "version": "0.12.0",
"required_compilation_api_level": 26,
"synthesized_library_classes_package_prefix": "j$.",
"library_flags": [
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index c443620..c40d755 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -26,6 +26,7 @@
import com.android.tools.r8.jar.CfApplicationWriter;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.naming.PrefixRewritingNamingLens;
+import com.android.tools.r8.naming.signature.GenericSignatureRewriter;
import com.android.tools.r8.origin.CommandLineOrigin;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApp;
@@ -256,6 +257,8 @@
hasDexResources
? NamingLens.getIdentityLens()
: PrefixRewritingNamingLens.createPrefixRewritingNamingLens(appView);
+ new GenericSignatureRewriter(appView, namingLens)
+ .run(appView.appInfo().classes(), executor);
} else {
// There are both cf and dex inputs in the program, and rewriting is required for
// desugared library only on cf inputs. We cannot easily rewrite part of the program
@@ -315,6 +318,10 @@
}
DexApplication cfApp = app.builder().replaceProgramClasses(nonDexProgramClasses).build();
ConvertedCfFiles convertedCfFiles = new ConvertedCfFiles();
+ NamingLens prefixRewritingNamingLens =
+ PrefixRewritingNamingLens.createPrefixRewritingNamingLens(appView);
+ new GenericSignatureRewriter(appView, prefixRewritingNamingLens)
+ .run(appView.appInfo().classes(), executor);
new ApplicationWriter(
cfApp,
null,
@@ -322,7 +329,7 @@
null,
GraphLense.getIdentityLense(),
InitClassLens.getDefault(),
- PrefixRewritingNamingLens.createPrefixRewritingNamingLens(appView),
+ prefixRewritingNamingLens,
null,
convertedCfFiles)
.write(executor);
diff --git a/src/main/java/com/android/tools/r8/L8.java b/src/main/java/com/android/tools/r8/L8.java
index 4ba88f4..4bef8cec 100644
--- a/src/main/java/com/android/tools/r8/L8.java
+++ b/src/main/java/com/android/tools/r8/L8.java
@@ -15,7 +15,9 @@
import com.android.tools.r8.ir.conversion.IRConverter;
import com.android.tools.r8.ir.desugar.PrefixRewritingMapper;
import com.android.tools.r8.jar.CfApplicationWriter;
+import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.naming.PrefixRewritingNamingLens;
+import com.android.tools.r8.naming.signature.GenericSignatureRewriter;
import com.android.tools.r8.origin.CommandLineOrigin;
import com.android.tools.r8.shaking.AnnotationRemover;
import com.android.tools.r8.shaking.L8TreePruner;
@@ -132,13 +134,16 @@
app = converter.convert(app, executor);
assert appView.appInfo() == appInfo;
+ NamingLens namingLens = PrefixRewritingNamingLens.createPrefixRewritingNamingLens(appView);
+ new GenericSignatureRewriter(appView, namingLens).run(appInfo.classes(), executor);
+
new CfApplicationWriter(
app,
appView,
options,
options.getMarker(Tool.L8),
GraphLense.getIdentityLense(),
- PrefixRewritingNamingLens.createPrefixRewritingNamingLens(appView),
+ namingLens,
null)
.write(options.getClassFileConsumer());
options.printWarnings();
diff --git a/src/main/java/com/android/tools/r8/PrintUses.java b/src/main/java/com/android/tools/r8/PrintUses.java
index 5fe37ab..c94d67c 100644
--- a/src/main/java/com/android/tools/r8/PrintUses.java
+++ b/src/main/java/com/android/tools/r8/PrintUses.java
@@ -120,7 +120,7 @@
@Override
public boolean registerInvokeStatic(DexMethod method) {
- DexEncodedMethod target = appInfo.lookupStaticTarget(method, context);
+ DexEncodedMethod target = appInfo.unsafeResolveMethodDueToDexFormat(method).getSingleTarget();
if (target != null && target.method != method) {
addType(method.holder);
addMethod(target.method);
@@ -148,13 +148,13 @@
@Override
public boolean registerInstanceFieldWrite(DexField field) {
- addField(field, false);
+ addField(field);
return false;
}
@Override
public boolean registerInstanceFieldRead(DexField field) {
- addField(field, false);
+ addField(field);
return false;
}
@@ -166,13 +166,13 @@
@Override
public boolean registerStaticFieldRead(DexField field) {
- addField(field, true);
+ addField(field);
return false;
}
@Override
public boolean registerStaticFieldWrite(DexField field) {
- addField(field, true);
+ addField(field);
return false;
}
@@ -200,10 +200,9 @@
return descriptors.contains(type.toDescriptorString());
}
- private void addField(DexField field, boolean isStatic) {
+ private void addField(DexField field) {
addType(field.type);
- DexEncodedField baseField =
- isStatic ? appInfo.lookupStaticTarget(field) : appInfo.lookupInstanceTarget(field);
+ DexEncodedField baseField = appInfo.resolveField(field).getResolvedField();
if (baseField != null && baseField.holder() != field.holder) {
field = baseField.field;
}
@@ -229,13 +228,14 @@
addType(method.proto.returnType);
Set<DexMethod> typeMethods = methods.get(method.holder);
if (typeMethods != null) {
- DexEncodedMethod encodedMethod = appInfo.definitionFor(method);
- assert encodedMethod != null : "Could not find method " + method.toString();
+ DexClass holder = appInfo.definitionForHolder(method);
+ DexEncodedMethod definition = method.lookupOnClass(holder);
+ assert definition != null : "Could not find method " + method.toString();
if (!allowObfuscation) {
noObfuscationTypes.add(method.holder);
}
- if (encodedMethod.accessFlags.isVisibilityDependingOnPackage()) {
- keepPackageNames.add(encodedMethod.holder().getPackageName());
+ if (definition.accessFlags.isVisibilityDependingOnPackage()) {
+ keepPackageNames.add(definition.holder().getPackageName());
}
typeMethods.add(method);
}
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 34677e0..8f6bcca 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -802,12 +802,6 @@
namingLens = new Minifier(appView.withLiveness()).run(executorService, timing);
timing.end();
} else {
- // Rewrite signature annotations for applications that are not minified.
- if (appView.appInfo().hasLiveness()) {
- // TODO(b/124726014): Rewrite signature annotations in lens rewriting instead of here?
- new GenericSignatureRewriter(appView.withLiveness())
- .run(appView.appInfo().classes(), executorService);
- }
namingLens = NamingLens.getIdentityLens();
}
@@ -839,6 +833,7 @@
}
// Validity checks.
+ assert application.asDirect().verifyCodeObjectsOwners();
assert application.classes().stream().allMatch(clazz -> clazz.isValid(options));
if (options.isShrinking()
|| options.isMinifying()
@@ -859,6 +854,12 @@
options.syntheticProguardRulesConsumer.accept(synthesizedProguardRules);
}
+ NamingLens prefixRewritingNamingLens =
+ PrefixRewritingNamingLens.createPrefixRewritingNamingLens(appView, namingLens);
+
+ new GenericSignatureRewriter(appView, prefixRewritingNamingLens)
+ .run(appView.appInfo().classes(), executorService);
+
// Generate the resulting application resources.
writeApplication(
executorService,
@@ -866,7 +867,7 @@
appView,
appView.graphLense(),
appView.initClassLens(),
- PrefixRewritingNamingLens.createPrefixRewritingNamingLens(appView, namingLens),
+ prefixRewritingNamingLens,
options,
ProguardMapSupplier.create(classNameMapper, options));
@@ -975,9 +976,7 @@
new StringDiagnostic(
"Item " + definition.toSourceString() + " was not discarded.\n" + baos.toString()));
}
- if (!options.testing.allowCheckDiscardedErrors) {
- throw new CompilationError("Discard checks failed.");
- }
+ throw new CompilationError("Discard checks failed.");
}
private static boolean verifyNoJarApplicationReaders(List<DexProgramClass> classes) {
diff --git a/src/main/java/com/android/tools/r8/algorithms/scc/SCC.java b/src/main/java/com/android/tools/r8/algorithms/scc/SCC.java
new file mode 100644
index 0000000..c705c8c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/algorithms/scc/SCC.java
@@ -0,0 +1,74 @@
+// Copyright (c) 2020, 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.algorithms.scc;
+
+import com.android.tools.r8.utils.SetUtils;
+import com.google.common.collect.Sets;
+import it.unimi.dsi.fastutil.objects.Reference2IntMap;
+import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Deque;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Function;
+
+public class SCC<Node> {
+
+ private int currentTime = 0;
+ private final Reference2IntMap<Node> discoverTime = new Reference2IntOpenHashMap<>();
+ private final Set<Node> unassignedSet = Sets.newIdentityHashSet();
+ private final Deque<Node> unassignedStack = new ArrayDeque<>();
+ private final Deque<Node> preorderStack = new ArrayDeque<>();
+ private final List<Set<Node>> components = new ArrayList<>();
+
+ private final Function<Node, Iterable<? extends Node>> successors;
+
+ public SCC(Function<Node, Iterable<? extends Node>> successors) {
+ this.successors = successors;
+ }
+
+ public List<Set<Node>> computeSCC(Node v) {
+ assert currentTime == 0;
+ dfs(v);
+ return components;
+ }
+
+ private void dfs(Node value) {
+ discoverTime.put(value, currentTime++);
+ unassignedSet.add(value);
+ unassignedStack.push(value);
+ preorderStack.push(value);
+ for (Node successor : successors.apply(value)) {
+ if (!discoverTime.containsKey(successor)) {
+ // If not seen yet, continue the search.
+ dfs(successor);
+ } else if (unassignedSet.contains(successor)) {
+ // If seen already and the element is on the unassigned stack we have found a cycle.
+ // Pop off everything discovered later than the target from the preorder stack. This may
+ // not coincide with the cycle as an outer cycle may already have popped elements off.
+ int discoverTimeOfPhi = discoverTime.getInt(successor);
+ while (discoverTimeOfPhi < discoverTime.getInt(preorderStack.peek())) {
+ preorderStack.pop();
+ }
+ }
+ }
+ if (preorderStack.peek() == value) {
+ // If the current element is the top of the preorder stack, then we are at entry to a
+ // strongly-connected component consisting of this element and every element above this
+ // element on the stack.
+ Set<Node> component = SetUtils.newIdentityHashSet(unassignedStack.size());
+ while (true) {
+ Node member = unassignedStack.pop();
+ unassignedSet.remove(member);
+ component.add(member);
+ if (member == value) {
+ components.add(component);
+ break;
+ }
+ }
+ preorderStack.pop();
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
index b47570c..8889e81 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
@@ -286,7 +286,7 @@
// default interface methods, it is expected they are targeted with invoke-direct.
return this.itf && desugaringEnabled ? Type.DIRECT : Type.SUPER;
}
- if (!encodedMethod.isNonPrivateVirtualMethod()) {
+ if (encodedMethod.isPrivateMethod() || !encodedMethod.isVirtualMethod()) {
return Type.DIRECT;
}
if (encodedMethod.accessFlags.isFinal()) {
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 3706dec..60b8220 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -161,21 +161,6 @@
return definition == null ? Origin.unknown() : definition.origin;
}
- @Deprecated
- @Override
- public DexEncodedMethod definitionFor(DexMethod method) {
- assert checkIfObsolete();
- assert method.holder.isClassType();
- if (!method.holder.isClassType()) {
- return null;
- }
- DexClass clazz = definitionFor(method.holder);
- if (clazz == null) {
- return null;
- }
- return clazz.getMethodCollection().getMethod(method);
- }
-
/**
* Lookup static method on the method holder, or answers null.
*
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index b4534f5..9763f50 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -226,12 +226,6 @@
return this.sourceDebugExtensions.get(clazz);
}
- @Deprecated
- @Override
- public final DexEncodedMethod definitionFor(DexMethod method) {
- return appInfo().definitionFor(method);
- }
-
@Override
public final DexClass definitionFor(DexType type) {
return appInfo().definitionFor(type);
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 09aeb11..1825fb3 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -15,6 +15,7 @@
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
import com.google.common.collect.Sets;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
@@ -827,6 +828,16 @@
return superClass == null || superClass.hasInstanceFieldsDirectlyOrIndirectly(appView);
}
+ public List<DexEncodedField> getDirectAndIndirectInstanceFields(AppView<?> appView) {
+ List<DexEncodedField> result = new ArrayList<>();
+ DexClass current = this;
+ while (current != null && current.type != appView.dexItemFactory().objectType) {
+ result.addAll(current.instanceFields());
+ current = appView.definitionFor(current.superType);
+ }
+ return result;
+ }
+
public boolean isValid(InternalOptions options) {
assert verifyNoAbstractMethodsOnNonAbstractClasses(virtualMethods(), options);
assert !isInterface() || !getMethodCollection().hasVirtualMethods(DexEncodedMethod::isFinal);
diff --git a/src/main/java/com/android/tools/r8/graph/DexDefinitionSupplier.java b/src/main/java/com/android/tools/r8/graph/DexDefinitionSupplier.java
index 214e41c..2ec6dbf 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDefinitionSupplier.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDefinitionSupplier.java
@@ -6,9 +6,6 @@
public interface DexDefinitionSupplier {
- @Deprecated
- DexEncodedMethod definitionFor(DexMethod method);
-
DexClass definitionFor(DexType type);
DexProgramClass definitionForProgramType(DexType type);
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 c12f3f4..74a31bf 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -250,6 +250,18 @@
assert parameterAnnotationsList != null;
}
+ public DexType getHolderType() {
+ return getReference().holder;
+ }
+
+ public DexString getName() {
+ return getReference().name;
+ }
+
+ public DexProto getProto() {
+ return getReference().proto;
+ }
+
public DexMethod getReference() {
return method;
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index d61746d..8b2bd65 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -764,6 +764,8 @@
public final DexMethod booleanValue =
createMethod(boxedBooleanType, createProto(booleanType), "booleanValue");
+ public final DexMethod parseBoolean =
+ createMethod(boxedBooleanType, createProto(booleanType, stringType), "parseBoolean");
public final DexMethod valueOf =
createMethod(boxedBooleanType, createProto(boxedBooleanType, booleanType), "valueOf");
@@ -1887,6 +1889,10 @@
return createProto(proto.returnType, parameterTypes);
}
+ public DexProto prependHolderToProto(DexMethod method) {
+ return prependTypeToProto(method.holder, method.proto);
+ }
+
public DexProto prependTypeToProto(DexType extraFirstType, DexProto initialProto) {
DexType[] parameterTypes = new DexType[initialProto.parameters.size() + 1];
parameterTypes[0] = extraFirstType;
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 3699d2e..ab9be40 100644
--- a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
@@ -73,13 +73,6 @@
return classpathClasses;
}
- @Deprecated
- @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;
@@ -173,7 +166,7 @@
return computeCodeObjectOwnersForDebugging().get(code);
}
- private boolean verifyCodeObjectsOwners() {
+ public boolean verifyCodeObjectsOwners() {
codeOwners.clear();
for (DexProgramClass clazz : programClasses) {
for (DexEncodedMethod method :
diff --git a/src/main/java/com/android/tools/r8/graph/LookupCompletenessHelper.java b/src/main/java/com/android/tools/r8/graph/LookupCompletenessHelper.java
index 5a8f39f..3c7b2d8 100644
--- a/src/main/java/com/android/tools/r8/graph/LookupCompletenessHelper.java
+++ b/src/main/java/com/android/tools/r8/graph/LookupCompletenessHelper.java
@@ -21,7 +21,7 @@
}
void checkClass(DexClass clazz) {
- if (pinnedPredicate.isPinned(clazz.type)) {
+ if (pinnedPredicate.isPinned(clazz)) {
if (pinnedInstantiations == null) {
pinnedInstantiations = Sets.newIdentityHashSet();
}
@@ -30,7 +30,7 @@
}
void checkMethod(DexEncodedMethod method) {
- if (pinnedPredicate.isPinned(method.method)) {
+ if (pinnedPredicate.isPinned(method)) {
if (pinnedMethods == null) {
pinnedMethods = Sets.newIdentityHashSet();
}
@@ -70,7 +70,7 @@
}
DexEncodedMethod methodInClass = parent.lookupVirtualMethod(method);
if (methodInClass != null
- && (parent.isNotProgramClass() || pinnedPredicate.isPinned(methodInClass.method))) {
+ && (parent.isNotProgramClass() || pinnedPredicate.isPinned(methodInClass))) {
return true;
}
if (parent.superType != null) {
diff --git a/src/main/java/com/android/tools/r8/graph/PinnedPredicate.java b/src/main/java/com/android/tools/r8/graph/PinnedPredicate.java
index d81becc..5828fd5 100644
--- a/src/main/java/com/android/tools/r8/graph/PinnedPredicate.java
+++ b/src/main/java/com/android/tools/r8/graph/PinnedPredicate.java
@@ -7,5 +7,5 @@
@FunctionalInterface
public interface PinnedPredicate {
- boolean isPinned(DexReference reference);
+ boolean isPinned(DexDefinition reference);
}
diff --git a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
index fab752df..d8eab37 100644
--- a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
@@ -70,6 +70,11 @@
public abstract OptionalBool isAccessibleForVirtualDispatchFrom(
DexProgramClass context, AppInfoWithClassHierarchy appInfo);
+ public final OptionalBool isAccessibleForVirtualDispatchFrom(
+ ProgramMethod context, AppInfoWithClassHierarchy appInfo) {
+ return isAccessibleForVirtualDispatchFrom(context.getHolder(), appInfo);
+ }
+
public abstract boolean isVirtualTarget();
/** Lookup the single target of an invoke-special on this resolution result if possible. */
@@ -367,8 +372,7 @@
// This is assuming that the method is accessible, which implies self/nest access.
// Only include if the target has code or is native.
boolean isIncomplete =
- pinnedPredicate.isPinned(resolvedHolder.type)
- && pinnedPredicate.isPinned(resolvedMethod.method);
+ pinnedPredicate.isPinned(resolvedHolder) && pinnedPredicate.isPinned(resolvedMethod);
return LookupResult.createResult(
Collections.singletonMap(
resolvedMethod, DexClassAndMethod.create(resolvedHolder, resolvedMethod)),
@@ -536,18 +540,17 @@
LambdaDescriptor lambdaInstance, AppInfoWithClassHierarchy appInfo) {
if (lambdaInstance.getMainMethod().match(resolvedMethod)) {
DexMethod method = lambdaInstance.implHandle.asMethod();
- DexClass holder = appInfo.definitionFor(method.holder);
+ DexClass holder = appInfo.definitionForHolder(method);
if (holder == null) {
assert false;
return null;
}
- DexEncodedMethod encodedMethod = appInfo.definitionFor(method);
- if (encodedMethod == null) {
+ DexEncodedMethod definition = holder.lookupMethod(method);
+ if (definition == null) {
// The targeted method might not exist, eg, Throwable.addSuppressed in an old library.
return null;
}
- return new LookupLambdaTarget(
- lambdaInstance, DexClassAndMethod.create(holder, encodedMethod));
+ return new LookupLambdaTarget(lambdaInstance, DexClassAndMethod.create(holder, definition));
}
return lookupMaximallySpecificDispatchTarget(lambdaInstance, appInfo);
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java
index 41f5dc8..416f064 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java
@@ -297,7 +297,9 @@
return false;
}
}
- DexEncodedMethod method = appView.definitionFor(instruction.getInvokedMethod());
+ DexMethod invokedMethod = instruction.getInvokedMethod();
+ DexClass holder = appView.definitionForHolder(invokedMethod);
+ DexEncodedMethod method = invokedMethod.lookupOnClass(holder);
return method != null && isTypeInitializedBy(instruction, type, method, appView, mode);
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java
index 01d86a1..29d38c6 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java
@@ -12,12 +12,15 @@
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.environmentdependence.ValueGraph;
+import com.android.tools.r8.ir.analysis.environmentdependence.ValueGraph.Node;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
import com.android.tools.r8.ir.code.ArrayPut;
+import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InvokeDirect;
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.InvokeNewArray;
import com.android.tools.r8.ir.code.NewArrayEmpty;
@@ -26,9 +29,14 @@
import com.android.tools.r8.ir.code.StaticPut;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
-import com.android.tools.r8.utils.LongInterval;
-import com.google.common.collect.ImmutableSet;
+import com.android.tools.r8.utils.SetUtils;
+import com.android.tools.r8.utils.WorkList;
import com.google.common.collect.Sets;
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
import java.util.Set;
/**
@@ -68,219 +76,175 @@
public class ValueMayDependOnEnvironmentAnalysis {
private final AppView<?> appView;
- private final IRCode code;
private final ProgramMethod context;
- private final Set<Value> knownNotToDependOnEnvironment = Sets.newIdentityHashSet();
- private final Set<Value> visited = Sets.newIdentityHashSet();
-
public ValueMayDependOnEnvironmentAnalysis(AppView<?> appView, IRCode code) {
this.appView = appView;
- this.code = code;
this.context = code.context();
}
- public boolean valueMayDependOnEnvironment(Value value) {
- boolean result = valueMayDependOnEnvironment(value, Sets.newIdentityHashSet());
- assert visited.isEmpty();
- return result;
- }
+ public boolean anyValueMayDependOnEnvironment(Iterable<Value> values) {
+ ValueGraph graph = new ValueGraph();
+ Set<Instruction> consumedInstructions = Sets.newIdentityHashSet();
+ Set<Value> mutableValues = Sets.newIdentityHashSet();
+ WorkList<Value> worklist = WorkList.newIdentityWorkList(values);
+ while (worklist.hasNext()) {
+ Value value = worklist.next();
+ Value root = value.getAliasedValue();
+ Node node = graph.createNodeIfAbsent(root);
+ if (root != value) {
+ // An alias depends on the environment if the aliased value depends on the environment, thus
+ // an edge is added from the alias to the aliased value.
+ graph.addDirectedEdge(graph.createNodeIfAbsent(value), node);
+ }
+ if (!addValueToValueGraph(root, node, graph, consumedInstructions, mutableValues, worklist)) {
+ return true;
+ }
+ }
- private boolean valueMayDependOnEnvironment(
- Value value, Set<Value> assumedNotToDependOnEnvironment) {
- Value root = value.getAliasedValue();
- if (assumedNotToDependOnEnvironment.contains(root)) {
- return false;
- }
- if (knownNotToDependOnEnvironment.contains(root)) {
- return false;
- }
- if (!visited.add(root)) {
- // Guard against cycle by conservatively returning true.
- return true;
- }
- try {
- if (root.isConstant()) {
- return false;
- }
- if (isConstantArrayThroughoutMethod(root, assumedNotToDependOnEnvironment)) {
- return false;
- }
- AbstractValue abstractValue = root.getAbstractValue(appView, context);
- if (abstractValue.isSingleFieldValue()) {
- DexField fieldReference = abstractValue.asSingleFieldValue().getField();
- DexClass holder = appView.definitionForHolder(fieldReference);
- DexEncodedField field = fieldReference.lookupOnClass(holder);
- if (field != null && field.isEnum()) {
- return false;
+ // At this point, the graph has been populated with a node for each value of interest, and edges
+ // have been added to reflect the dependency. We now attempt to prove that no values depend on
+ // environment, starting from the leaves of the graph.
+ //
+ // First we collapse strongly connected components in the graph. By doing so we will attempt to
+ // prove that all values in a strongly connected component are independent of the environment at
+ // once.
+ graph.mergeStronglyConnectedComponents();
+
+ Set<Node> nodesDependentOnEnvironment = SetUtils.newIdentityHashSet(graph.getNodes());
+ while (!nodesDependentOnEnvironment.isEmpty()) {
+ Set<Node> newNodesIndependentOfEnvironment = Sets.newIdentityHashSet();
+ for (Node node : nodesDependentOnEnvironment) {
+ boolean isDependentOfEnvironment =
+ node.hasSuccessorThatMatches(
+ successor ->
+ nodesDependentOnEnvironment.contains(successor)
+ && !newNodesIndependentOfEnvironment.contains(successor));
+ if (!isDependentOfEnvironment) {
+ newNodesIndependentOfEnvironment.add(node);
}
}
- if (isNewInstanceWithoutEnvironmentDependentFields(root, assumedNotToDependOnEnvironment)) {
- return false;
+ if (newNodesIndependentOfEnvironment.isEmpty()) {
+ return true;
}
+ nodesDependentOnEnvironment.removeAll(newNodesIndependentOfEnvironment);
+ }
+
+ // At this point, we have proved that all values in the graph are independent on the
+ // environment. However, we still need to prove that they are not mutated between the point
+ // where they are defined and all normal exits.
+ return anyValueMayBeMutatedBeforeMethodExit(mutableValues, consumedInstructions);
+ }
+
+ private boolean addValueToValueGraph(
+ Value value,
+ Node node,
+ ValueGraph graph,
+ Set<Instruction> consumedInstructions,
+ Set<Value> mutableValues,
+ WorkList<Value> worklist) {
+ return addConstantValueToValueGraph(value)
+ || addArrayValueToValueGraph(
+ value, node, graph, consumedInstructions, mutableValues, worklist)
+ || addNewInstanceValueToValueGraph(
+ value, node, graph, consumedInstructions, mutableValues, worklist);
+ }
+
+ private boolean addConstantValueToValueGraph(Value value) {
+ // Constants do not depend on any other values, thus no edges are added to the graph.
+ if (value.isConstant()) {
return true;
- } finally {
- boolean changed = visited.remove(root);
- assert changed;
}
- }
-
- private boolean valueMayNotDependOnEnvironmentAssumingArrayDoesNotDependOnEnvironment(
- Value value, Value array, Set<Value> assumedNotToDependOnEnvironment) {
- assert !value.hasAliasedValue();
- assert !array.hasAliasedValue();
-
- if (assumedNotToDependOnEnvironment.add(array)) {
- boolean valueMayDependOnEnvironment =
- valueMayDependOnEnvironment(value, assumedNotToDependOnEnvironment);
- boolean changed = assumedNotToDependOnEnvironment.remove(array);
- assert changed;
- return !valueMayDependOnEnvironment;
+ assert !value.getAliasedValue().isConstant();
+ AbstractValue abstractValue = value.getAbstractValue(appView, context);
+ if (abstractValue.isSingleConstValue()) {
+ return true;
}
- return !valueMayDependOnEnvironment(value, assumedNotToDependOnEnvironment);
+ if (abstractValue.isSingleFieldValue()) {
+ DexField fieldReference = abstractValue.asSingleFieldValue().getField();
+ DexClass holder = appView.definitionForHolder(fieldReference);
+ DexEncodedField field = fieldReference.lookupOnClass(holder);
+ if (field != null && field.isEnum()) {
+ return true;
+ }
+ }
+ return false;
}
- /**
- * Used to identify if an array is "constant" in the sense that none of the values written into
- * the array may depend on the environment.
- *
- * <p>Examples include {@code new int[] {1,2,3}} and {@code new Object[]{new Object()}}.
- */
- public boolean isConstantArrayThroughoutMethod(Value value) {
- boolean result = isConstantArrayThroughoutMethod(value, Sets.newIdentityHashSet());
- assert visited.isEmpty();
- return result;
- }
-
- private boolean isConstantArrayThroughoutMethod(
- Value value, Set<Value> assumedNotToDependOnEnvironment) {
- Value root = value.getAliasedValue();
- if (root.isPhi()) {
+ private boolean addArrayValueToValueGraph(
+ Value value,
+ Node node,
+ ValueGraph graph,
+ Set<Instruction> consumedInstructions,
+ Set<Value> mutableValues,
+ WorkList<Value> worklist) {
+ if (value.isPhi()) {
// Would need to track the aliases, just give up.
return false;
}
- Instruction definition = root.definition;
+ Instruction definition = value.definition;
// Check that it is a constant array with a known size at this point in the IR.
- long size;
if (definition.isInvokeNewArray()) {
InvokeNewArray invokeNewArray = definition.asInvokeNewArray();
for (Value argument : invokeNewArray.arguments()) {
- if (!argument.isConstant()) {
- return false;
- }
+ graph.addDirectedEdge(node, graph.createNodeIfAbsent(argument));
+ worklist.addIfNotSeen(argument);
}
- size = invokeNewArray.arguments().size();
} else if (definition.isNewArrayEmpty()) {
NewArrayEmpty newArrayEmpty = definition.asNewArrayEmpty();
- Value sizeValue = newArrayEmpty.size().getAliasedValue();
- if (!sizeValue.hasValueRange()) {
- return false;
- }
- LongInterval sizeRange = sizeValue.getValueRange();
- if (!sizeRange.isSingleValue()) {
- return false;
- }
- size = sizeRange.getSingleValue();
+ Value sizeValue = newArrayEmpty.size();
+ graph.addDirectedEdge(node, graph.createNodeIfAbsent(sizeValue));
+ worklist.addIfNotSeen(sizeValue);
} else {
// Some other array creation.
return false;
}
- if (size < 0) {
- // Check for NegativeArraySizeException.
- return false;
- }
-
- if (size == 0) {
- // Empty arrays are always constant.
- return true;
- }
-
// Allow array-put and new-array-filled-data instructions that immediately follow the array
// creation.
- Set<Value> arrayValues = Sets.newIdentityHashSet();
- Set<Instruction> consumedInstructions = Sets.newIdentityHashSet();
-
for (Instruction instruction : definition.getBlock().instructionsAfter(definition)) {
if (instruction.isArrayPut()) {
ArrayPut arrayPut = instruction.asArrayPut();
Value array = arrayPut.array().getAliasedValue();
- if (array != root) {
+ if (array != value) {
// This ends the chain of array-put instructions that are allowed immediately after the
// array creation.
break;
}
-
- LongInterval indexRange = arrayPut.index().getValueRange();
- if (!indexRange.isSingleValue()) {
- return false;
- }
-
- long index = indexRange.getSingleValue();
- if (index < 0 || index >= size) {
- return false;
- }
-
- // Check if the value being written into the array may depend on the environment.
- //
- // When analyzing if the value may depend on the environment, we assume that the current
- // array does not depend on the environment. Otherwise, we would classify the value as
- // possibly depending on the environment since it could escape via the array and then
- // be mutated indirectly.
- Value rhs = arrayPut.value().getAliasedValue();
- if (!valueMayNotDependOnEnvironmentAssumingArrayDoesNotDependOnEnvironment(
- rhs, root, assumedNotToDependOnEnvironment)) {
- return false;
- }
-
- arrayValues.add(rhs);
- consumedInstructions.add(arrayPut);
- continue;
- }
-
- if (instruction.isNewArrayFilledData()) {
+ graph.addDirectedEdge(node, graph.createNodeIfAbsent(arrayPut.index()));
+ worklist.addIfNotSeen(arrayPut.index());
+ graph.addDirectedEdge(node, graph.createNodeIfAbsent(arrayPut.value()));
+ worklist.addIfNotSeen(arrayPut.value());
+ } else if (instruction.isNewArrayFilledData()) {
NewArrayFilledData newArrayFilledData = instruction.asNewArrayFilledData();
Value array = newArrayFilledData.src();
- if (array != root) {
+ if (array != value) {
+ // This ends the chain of array-put instructions that are allowed immediately after the
+ // array creation.
break;
}
-
- consumedInstructions.add(newArrayFilledData);
- continue;
- }
-
- if (instruction.instructionMayHaveSideEffects(appView, context)) {
+ consumedInstructions.add(instruction);
+ } else if (instruction.instructionMayHaveSideEffects(appView, context)) {
// This ends the chain of array-put instructions that are allowed immediately after the
// array creation.
break;
}
+ consumedInstructions.add(instruction);
}
-
- // Check that the array is not mutated before the end of this method.
- //
- // Currently, we only allow the array to flow into static-put instructions that are not
- // followed by an instruction that may have side effects. Instructions that do not have any
- // side effects are ignored because they cannot mutate the array.
- if (valueMayBeMutatedBeforeMethodExit(
- root, assumedNotToDependOnEnvironment, consumedInstructions)) {
- return false;
- }
-
- if (assumedNotToDependOnEnvironment.isEmpty()) {
- knownNotToDependOnEnvironment.add(root);
- knownNotToDependOnEnvironment.addAll(arrayValues);
- }
-
+ mutableValues.add(value);
return true;
}
- private boolean isNewInstanceWithoutEnvironmentDependentFields(
- Value value, Set<Value> assumedNotToDependOnEnvironment) {
- assert !value.hasAliasedValue();
-
- if (value.isPhi() || !value.definition.isNewInstance()) {
+ private boolean addNewInstanceValueToValueGraph(
+ Value value,
+ Node node,
+ ValueGraph graph,
+ Set<Instruction> consumedInstructions,
+ Set<Value> mutableValues,
+ WorkList<Value> worklist) {
+ if (!value.isDefinedByInstructionSatisfying(Instruction::isNewInstance)) {
return false;
}
@@ -291,123 +255,154 @@
}
// Find the single constructor invocation.
- InvokeMethod constructorInvoke = null;
- for (Instruction instruction : value.uniqueUsers()) {
- if (!instruction.isInvokeDirect()) {
- continue;
- }
-
- InvokeDirect invoke = instruction.asInvokeDirect();
- if (!appView.dexItemFactory().isConstructor(invoke.getInvokedMethod())) {
- continue;
- }
-
- if (invoke.getReceiver().getAliasedValue() != value) {
- continue;
- }
-
- if (constructorInvoke == null) {
- constructorInvoke = invoke;
- } else {
- // Not a single constructor invocation, give up.
- return false;
- }
- }
-
- if (constructorInvoke == null) {
- // Didn't find a constructor invocation, give up.
+ InvokeMethod constructorInvoke =
+ newInstance.getUniqueConstructorInvoke(appView.dexItemFactory());
+ if (constructorInvoke == null || constructorInvoke.getInvokedMethod().holder != clazz.type) {
+ // Didn't find a (valid) constructor invocation, give up.
return false;
}
// Check that it is a trivial initializer (otherwise, the constructor could do anything).
- DexEncodedMethod constructor = appView.definitionFor(constructorInvoke.getInvokedMethod());
+ DexEncodedMethod constructor = clazz.lookupMethod(constructorInvoke.getInvokedMethod());
if (constructor == null) {
return false;
}
- if (clazz.hasInstanceFieldsDirectlyOrIndirectly(appView)) {
- InstanceInitializerInfo initializerInfo =
- constructor.getOptimizationInfo().getInstanceInitializerInfo();
+ InstanceInitializerInfo initializerInfo =
+ constructor.getOptimizationInfo().getInstanceInitializerInfo();
+
+ List<DexEncodedField> fields = clazz.getDirectAndIndirectInstanceFields(appView);
+ if (!fields.isEmpty()) {
if (initializerInfo.instanceFieldInitializationMayDependOnEnvironment()) {
return false;
}
// Check that none of the arguments to the constructor depend on the environment.
for (int i = 1; i < constructorInvoke.arguments().size(); i++) {
- Value argument = constructorInvoke.arguments().get(i);
- if (valueMayDependOnEnvironment(argument, assumedNotToDependOnEnvironment)) {
- return false;
- }
+ Value argument = constructorInvoke.getArgument(i);
+ graph.addDirectedEdge(node, graph.createNodeIfAbsent(argument));
+ worklist.addIfNotSeen(argument);
}
- // Finally, check that the object does not escape.
- if (valueMayBeMutatedBeforeMethodExit(
- value, assumedNotToDependOnEnvironment, ImmutableSet.of(constructorInvoke))) {
- return false;
+ // Mark this value as mutable if it has a non-final field.
+ boolean hasNonFinalField = false;
+ for (DexEncodedField field : fields) {
+ if (!field.isFinal()) {
+ hasNonFinalField = true;
+ break;
+ }
+ }
+ if (hasNonFinalField) {
+ mutableValues.add(value);
}
}
- if (assumedNotToDependOnEnvironment.isEmpty()) {
- knownNotToDependOnEnvironment.add(value);
+ if (!initializerInfo.mayHaveOtherSideEffectsThanInstanceFieldAssignments()) {
+ consumedInstructions.add(constructorInvoke);
}
return true;
}
- private boolean valueMayBeMutatedBeforeMethodExit(
- Value value, Set<Value> assumedNotToDependOnEnvironment, Set<Instruction> whitelist) {
- assert !value.hasAliasedValue();
-
- if (value.numberOfPhiUsers() > 0) {
- // Could be mutated indirectly.
- return true;
+ private boolean anyValueMayBeMutatedBeforeMethodExit(
+ Set<Value> values, Set<Instruction> whitelist) {
+ Set<BasicBlock> initialBlocks = Sets.newIdentityHashSet();
+ for (Value value : values) {
+ assert !value.isPhi();
+ initialBlocks.add(value.definition.getBlock());
}
-
- Set<Instruction> visited = Sets.newIdentityHashSet();
- for (Instruction user : value.uniqueUsers()) {
- if (whitelist.contains(user)) {
- continue;
- }
-
- if (user.isArrayPut()) {
- ArrayPut arrayPut = user.asArrayPut();
- if (value == arrayPut.value()
- && !valueMayDependOnEnvironment(arrayPut.array(), assumedNotToDependOnEnvironment)) {
+ Map<BasicBlock, TrackedValuesState> blockExitStates = new IdentityHashMap<>();
+ Deque<BasicBlock> worklist = new ArrayDeque<>(initialBlocks);
+ while (!worklist.isEmpty()) {
+ BasicBlock block = worklist.removeFirst();
+ TrackedValuesState state = computeBlockEntryState(block, blockExitStates);
+ boolean changed = false;
+ for (Instruction instruction : block.getInstructions()) {
+ if (whitelist.contains(instruction)) {
continue;
}
- }
-
- if (user.isStaticPut()) {
- StaticPut staticPut = user.asStaticPut();
- if (visited.contains(staticPut)) {
- // Already visited previously.
- continue;
- }
- for (Instruction instruction : code.getInstructionsReachableFrom(staticPut)) {
- if (!visited.add(instruction)) {
- // Already visited previously.
- continue;
+ if (instruction.isStaticPut()) {
+ StaticPut staticPut = instruction.asStaticPut();
+ if (state.isTrackingValue(staticPut.value())) {
+ changed |= state.recordTrackedValueHasEscaped();
}
- if (instruction.isStaticPut()) {
- StaticPut otherStaticPut = instruction.asStaticPut();
- if (otherStaticPut.getField().holder == staticPut.getField().holder
- && !instruction.instructionInstanceCanThrow(appView, context)) {
- continue;
+ if (state.hasTrackedValueEscaped()) {
+ DexType holder = staticPut.getField().holder;
+ if (holder.classInitializationMayHaveSideEffects(
+ appView,
+ // Types that are a super type of the current context are guaranteed to be
+ // initialized already.
+ type -> appView.isSubtype(context.getHolderType(), type).isTrue(),
+ Sets.newIdentityHashSet())) {
+ return true;
}
- return true;
}
- if (instruction.instructionMayTriggerMethodInvocation(appView, context)
+ continue;
+ }
+ if (instruction.instructionMayTriggerMethodInvocation(appView, context)) {
+ if (instruction.hasInValueThatMatches(state::isTrackingValue)) {
+ changed |= state.recordTrackedValueHasEscaped();
+ }
+ if (state.hasTrackedValueEscaped()
&& instruction.instructionMayHaveSideEffects(appView, context)) {
return true;
}
}
- continue;
+ if (instruction.hasOutValue() && values.contains(instruction.outValue())) {
+ changed |= state.startTrackingValue(instruction.outValue());
+ }
}
+ blockExitStates.put(block, state);
+ if (changed) {
+ worklist.addAll(block.getSuccessors());
+ }
+ }
+ return false;
+ }
- // Other user than array-put or static-put, just give up.
- return false;
+ private TrackedValuesState computeBlockEntryState(
+ BasicBlock block, Map<BasicBlock, TrackedValuesState> states) {
+ TrackedValuesState state = new TrackedValuesState();
+ for (BasicBlock predecessor : block.getPredecessors()) {
+ state.add(states.getOrDefault(predecessor, TrackedValuesState.empty()));
+ }
+ return state;
+ }
+
+ static class TrackedValuesState {
+
+ private static final TrackedValuesState EMPTY = new TrackedValuesState();
+
+ boolean hasTrackedValueEscaped;
+ Set<Value> trackedValues = Sets.newIdentityHashSet();
+
+ public static TrackedValuesState empty() {
+ return EMPTY;
}
- return false;
+ public void add(TrackedValuesState state) {
+ hasTrackedValueEscaped |= state.hasTrackedValueEscaped;
+ trackedValues.addAll(state.trackedValues);
+ }
+
+ public boolean hasTrackedValueEscaped() {
+ return hasTrackedValueEscaped;
+ }
+
+ public boolean isTrackingValue(Value value) {
+ return trackedValues.contains(value);
+ }
+
+ public boolean recordTrackedValueHasEscaped() {
+ if (hasTrackedValueEscaped) {
+ return false;
+ }
+ hasTrackedValueEscaped = true;
+ return true;
+ }
+
+ public boolean startTrackingValue(Value value) {
+ return trackedValues.add(value);
+ }
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/environmentdependence/ValueGraph.java b/src/main/java/com/android/tools/r8/ir/analysis/environmentdependence/ValueGraph.java
new file mode 100644
index 0000000..8857645
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/environmentdependence/ValueGraph.java
@@ -0,0 +1,113 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.analysis.environmentdependence;
+
+import com.android.tools.r8.algorithms.scc.SCC;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.utils.WorkList;
+import com.google.common.collect.Sets;
+import java.util.Collection;
+import java.util.IdentityHashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Predicate;
+
+/**
+ * A directed graph where the nodes are values.
+ *
+ * <ol>
+ * <li>An edge from a value v to another value v' specifies that if v' may depend on the
+ * environment then v may also depend on the environment.
+ * <li>If a value v has no outgoing edges, then it does not depend on the environment.
+ * </ol>
+ */
+public class ValueGraph {
+
+ private final Map<Value, Node> nodes = new IdentityHashMap<>();
+
+ public Node createNodeIfAbsent(Value value) {
+ return nodes.computeIfAbsent(value, Node::new);
+ }
+
+ public void addDirectedEdge(Node from, Node to) {
+ to.predecessors.add(from);
+ from.successors.add(to);
+ }
+
+ public Collection<Node> getNodes() {
+ return nodes.values();
+ }
+
+ public void mergeNodes(Iterable<Node> iterable) {
+ Iterator<Node> iterator = iterable.iterator();
+ assert iterator.hasNext();
+ Node primary = iterator.next();
+ while (iterator.hasNext()) {
+ Node secondary = iterator.next();
+ secondary.moveEdgesTo(primary);
+ primary.addLabel(secondary.label);
+ nodes.put(secondary.value, primary);
+ }
+ }
+
+ public void mergeStronglyConnectedComponents() {
+ WorkList<Node> worklist = WorkList.newIdentityWorkList(nodes.values());
+ while (worklist.hasNext()) {
+ Node node = worklist.next();
+ List<Set<Node>> components = new SCC<>(Node::getSuccessors).computeSCC(node);
+ for (Set<Node> component : components) {
+ mergeNodes(component);
+ worklist.markAsSeen(component);
+ }
+ }
+ }
+
+ public static class Node {
+
+ private final Value value;
+
+ private final Set<Value> label = Sets.newIdentityHashSet();
+ private final Set<Node> predecessors = Sets.newIdentityHashSet();
+ private final Set<Node> successors = Sets.newIdentityHashSet();
+
+ public Node(Value value) {
+ this.label.add(value);
+ this.value = value;
+ }
+
+ public void addLabel(Set<Value> label) {
+ this.label.addAll(label);
+ }
+
+ public Set<Node> getSuccessors() {
+ return successors;
+ }
+
+ public boolean hasSuccessorThatMatches(Predicate<Node> predicate) {
+ for (Node successor : successors) {
+ if (predicate.test(successor)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public void moveEdgesTo(Node node) {
+ for (Node predecessor : predecessors) {
+ predecessor.successors.remove(this);
+ predecessor.successors.add(node);
+ node.predecessors.add(predecessor);
+ }
+ predecessors.clear();
+ for (Node successor : successors) {
+ successor.predecessors.remove(this);
+ successor.predecessors.add(node);
+ node.successors.add(successor);
+ }
+ successors.clear();
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
index 1481061..5b1dd91 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
@@ -23,7 +23,6 @@
import com.android.tools.r8.ir.code.NewInstance;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.optimize.ClassInitializerDefaultsOptimization.ClassInitializerDefaultsResult;
-import com.android.tools.r8.ir.optimize.info.FieldOptimizationInfo;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
import com.android.tools.r8.ir.optimize.info.field.InstanceFieldArgumentInitializationInfo;
import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfo;
@@ -131,8 +130,6 @@
}
private void recordFieldPut(DexEncodedField field, Value value, ProgramMethod context) {
- assert verifyValueIsConsistentWithFieldOptimizationInfo(
- value, field.getOptimizationInfo(), context);
if (!value.isZero()) {
nonZeroFields.add(field);
}
@@ -293,16 +290,6 @@
feedback.updateVisibleOptimizationInfo();
}
- private boolean verifyValueIsConsistentWithFieldOptimizationInfo(
- Value value, FieldOptimizationInfo optimizationInfo, ProgramMethod context) {
- AbstractValue abstractValue = optimizationInfo.getAbstractValue();
- if (abstractValue.isUnknown()) {
- return true;
- }
- assert abstractValue == value.getAbstractValue(appView, context);
- return true;
- }
-
static class FieldAccessGraph {
// The fields written by each method.
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
index c589048..96cdb99 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexMethod;
@@ -74,19 +75,24 @@
}
private boolean computeEnableAggressiveBuilderOptimization() {
+ DexClass generatedMessageLiteBuilderClass =
+ appView
+ .appInfo()
+ .definitionForWithoutExistenceAssert(references.generatedMessageLiteBuilderType);
+ DexClass generatedMessageLiteExtendableBuilderClass =
+ appView
+ .appInfo()
+ .definitionForWithoutExistenceAssert(
+ references.generatedMessageLiteExtendableBuilderType);
+ if (generatedMessageLiteBuilderClass == null
+ && generatedMessageLiteExtendableBuilderClass == null) {
+ // This build likely doesn't contain any proto, so disable the optimization. Don't report a
+ // warning in this case.
+ return false;
+ }
boolean unexpectedGeneratedMessageLiteBuilder =
ObjectUtils.getBooleanOrElse(
- appView
- .appInfo()
- .definitionForWithoutExistenceAssert(references.generatedMessageLiteBuilderType),
- clazz -> clazz.getMethodCollection().hasMethods(DexEncodedMethod::isAbstract),
- true);
- boolean unexpectedGeneratedMessageLiteExtendableBuilder =
- ObjectUtils.getBooleanOrElse(
- appView
- .appInfo()
- .definitionForWithoutExistenceAssert(
- references.generatedMessageLiteExtendableBuilderType),
+ generatedMessageLiteBuilderClass,
clazz -> clazz.getMethodCollection().hasMethods(DexEncodedMethod::isAbstract),
true);
if (unexpectedGeneratedMessageLiteBuilder) {
@@ -99,6 +105,11 @@
+ "`: disabling aggressive protobuf builder optimization.");
return false;
}
+ boolean unexpectedGeneratedMessageLiteExtendableBuilder =
+ ObjectUtils.getBooleanOrElse(
+ generatedMessageLiteExtendableBuilderClass,
+ clazz -> clazz.getMethodCollection().hasMethods(DexEncodedMethod::isAbstract),
+ true);
if (unexpectedGeneratedMessageLiteExtendableBuilder) {
appView
.options()
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/RawMessageInfoDecoder.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/RawMessageInfoDecoder.java
index e4741d9..363a2de 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/RawMessageInfoDecoder.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/RawMessageInfoDecoder.java
@@ -7,6 +7,7 @@
import static com.android.tools.r8.ir.analysis.proto.ProtoUtils.getInfoValueFromMessageInfoConstructionInvoke;
import static com.android.tools.r8.ir.analysis.proto.ProtoUtils.getObjectsValueFromMessageInfoConstructionInvoke;
+import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexReference;
@@ -152,6 +153,10 @@
int fieldNumber = infoIterator.nextIntComputeIfAbsent(this::invalidInfoFailure);
int fieldTypeWithExtraBits = infoIterator.nextIntComputeIfAbsent(this::invalidInfoFailure);
ProtoFieldType fieldType = factory.createField(fieldTypeWithExtraBits);
+ if (fieldType.serialize() != fieldTypeWithExtraBits) {
+ throw new CompilationError(
+ "Unexpected proto field type `" + fieldTypeWithExtraBits + "`");
+ }
OptionalInt auxData;
if (fieldType.hasAuxData(isProto2)) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoFieldType.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoFieldType.java
index a9cff43..39419b4 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoFieldType.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoFieldType.java
@@ -22,23 +22,31 @@
private static final int FIELD_ID_MASK = 0xFF;
private static final int FIELD_IS_REQUIRED_MASK = 0x100;
+ private static final int FIELD_ENFORCE_UTF8_MASK = 0x200;
private static final int FIELD_NEEDS_IS_INITIALIZED_CHECK_MASK = 0x400;
private static final int FIELD_IS_MAP_FIELD_WITH_PROTO_2_ENUM_VALUE_MASK = 0x800;
+ private static final int FIELD_HAS_HAS_BIT_MASK = 0x1000;
private final int id;
private final boolean isRequired;
+ private final boolean enforceUtf8Mask;
private final boolean needsIsInitializedCheck;
private final boolean isMapFieldWithProto2EnumValue;
+ private final boolean hasHasBit;
ProtoFieldType(
int id,
boolean isRequired,
+ boolean enforceUtf8Mask,
boolean needsIsInitializedCheck,
- boolean isMapFieldWithProto2EnumValue) {
+ boolean isMapFieldWithProto2EnumValue,
+ boolean hasHasBit) {
this.id = id;
this.isRequired = isRequired;
+ this.enforceUtf8Mask = enforceUtf8Mask;
this.needsIsInitializedCheck = needsIsInitializedCheck;
this.isMapFieldWithProto2EnumValue = isMapFieldWithProto2EnumValue;
+ this.hasHasBit = hasHasBit;
assert isValid();
}
@@ -48,14 +56,18 @@
return new ProtoFieldType(
fieldTypeWithExtraBits & FIELD_ID_MASK,
isBitInMaskSet(fieldTypeWithExtraBits, FIELD_IS_REQUIRED_MASK),
+ isBitInMaskSet(fieldTypeWithExtraBits, FIELD_ENFORCE_UTF8_MASK),
isBitInMaskSet(fieldTypeWithExtraBits, FIELD_NEEDS_IS_INITIALIZED_CHECK_MASK),
- isBitInMaskSet(fieldTypeWithExtraBits, FIELD_IS_MAP_FIELD_WITH_PROTO_2_ENUM_VALUE_MASK));
+ isBitInMaskSet(fieldTypeWithExtraBits, FIELD_IS_MAP_FIELD_WITH_PROTO_2_ENUM_VALUE_MASK),
+ isBitInMaskSet(fieldTypeWithExtraBits, FIELD_HAS_HAS_BIT_MASK));
} else {
return new ProtoOneOfFieldType(
fieldTypeWithExtraBits & FIELD_ID_MASK,
isBitInMaskSet(fieldTypeWithExtraBits, FIELD_IS_REQUIRED_MASK),
+ isBitInMaskSet(fieldTypeWithExtraBits, FIELD_ENFORCE_UTF8_MASK),
isBitInMaskSet(fieldTypeWithExtraBits, FIELD_NEEDS_IS_INITIALIZED_CHECK_MASK),
- isBitInMaskSet(fieldTypeWithExtraBits, FIELD_IS_MAP_FIELD_WITH_PROTO_2_ENUM_VALUE_MASK));
+ isBitInMaskSet(fieldTypeWithExtraBits, FIELD_IS_MAP_FIELD_WITH_PROTO_2_ENUM_VALUE_MASK),
+ isBitInMaskSet(fieldTypeWithExtraBits, FIELD_HAS_HAS_BIT_MASK));
}
}
@@ -141,12 +153,18 @@
if (isRequired) {
result |= FIELD_IS_REQUIRED_MASK;
}
+ if (enforceUtf8Mask) {
+ result |= FIELD_ENFORCE_UTF8_MASK;
+ }
if (needsIsInitializedCheck) {
result |= FIELD_NEEDS_IS_INITIALIZED_CHECK_MASK;
}
if (isMapFieldWithProto2EnumValue) {
result |= FIELD_IS_MAP_FIELD_WITH_PROTO_2_ENUM_VALUE_MASK;
}
+ if (hasHasBit) {
+ result |= FIELD_HAS_HAS_BIT_MASK;
+ }
return result;
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoFieldTypeFactory.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoFieldTypeFactory.java
index 54000eb..c655ce8 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoFieldTypeFactory.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoFieldTypeFactory.java
@@ -15,6 +15,7 @@
ProtoFieldType result = fieldTypes.get(fieldTypeIdWithExtraBits);
if (result == null) {
result = ProtoFieldType.fromFieldIdWithExtraBits(fieldTypeIdWithExtraBits);
+ assert result.serialize() == fieldTypeIdWithExtraBits;
fieldTypes.put(fieldTypeIdWithExtraBits, result);
}
return result;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoOneOfFieldType.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoOneOfFieldType.java
index c476382..1bc2ab5 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoOneOfFieldType.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoOneOfFieldType.java
@@ -13,9 +13,17 @@
ProtoOneOfFieldType(
int id,
boolean isRequired,
+ boolean enforceUtf8Mask,
boolean needsIsInitializedCheck,
- boolean isMapFieldWithProto2EnumValue) {
- super(id, isRequired, needsIsInitializedCheck, isMapFieldWithProto2EnumValue);
+ boolean isMapFieldWithProto2EnumValue,
+ boolean hasHasBit) {
+ super(
+ id,
+ isRequired,
+ enforceUtf8Mask,
+ needsIsInitializedCheck,
+ isMapFieldWithProto2EnumValue,
+ hasHasBit);
}
public ProtoFieldType getActualFieldType(ProtoFieldTypeFactory factory) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/sideeffect/ClassInitializerSideEffectAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/sideeffect/ClassInitializerSideEffectAnalysis.java
index 136c209..5d771a5 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/sideeffect/ClassInitializerSideEffectAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/sideeffect/ClassInitializerSideEffectAnalysis.java
@@ -11,12 +11,11 @@
import com.android.tools.r8.ir.code.ArrayPut;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InvokeNewArray;
-import com.android.tools.r8.ir.code.NewArrayFilledData;
import com.android.tools.r8.ir.code.StaticPut;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.OptionalBool;
+import com.google.common.collect.Sets;
+import java.util.Set;
public class ClassInitializerSideEffectAnalysis {
@@ -41,69 +40,28 @@
public static ClassInitializerSideEffect classInitializerCanBePostponed(
AppView<AppInfoWithLiveness> appView, IRCode code) {
ProgramMethod context = code.context();
- OptionalBool controlFlowMayDependOnEnvironment = OptionalBool.unknown();
- boolean mayHaveSideEffects = false;
-
- ValueMayDependOnEnvironmentAnalysis environmentAnalysis =
- new ValueMayDependOnEnvironmentAnalysis(appView, code);
+ // Will be set to true if the control flow must be independent of the environment in order for
+ // this class initializer to be postponeable.
+ boolean controlFlowRequiredToBeIndependentOfControlFlow = false;
+ // The set of values that must be independent of the environment in order for this class
+ // initializer to be postponeable.
+ Set<Value> valuesRequiredToBeIndependentOfEnvironment = Sets.newIdentityHashSet();
for (Instruction instruction : code.instructions()) {
- // Array stores to a newly created array are only observable if they may throw, or if the
- // array content may depend on the environment.
+ // Array stores are observable if they mutate a non-local array or if they may throw.
if (instruction.isArrayPut()) {
ArrayPut arrayPut = instruction.asArrayPut();
Value array = arrayPut.array().getAliasedValue();
- if (array.isPhi()
- || !array.definition.isCreatingArray()
- || environmentAnalysis.valueMayDependOnEnvironment(arrayPut.index())
- || environmentAnalysis.valueMayDependOnEnvironment(arrayPut.value())
+ if (!array.isDefinedByInstructionSatisfying(Instruction::isCreatingArray)
|| arrayPut.instructionInstanceCanThrow(appView, context)) {
return ClassInitializerSideEffect.SIDE_EFFECTS_THAT_CANNOT_BE_POSTPONED;
}
- if (controlFlowMayDependOnEnvironment.isUnknown()) {
- controlFlowMayDependOnEnvironment =
- OptionalBool.of(code.controlFlowMayDependOnEnvironment(environmentAnalysis));
- }
- if (controlFlowMayDependOnEnvironment.isTrue()) {
- return ClassInitializerSideEffect.SIDE_EFFECTS_THAT_CANNOT_BE_POSTPONED;
- }
continue;
}
- // NewArrayFilledData is handled similarly to ArrayPut.
- if (instruction.isNewArrayFilledData()) {
- NewArrayFilledData newArrayFilledData = instruction.asNewArrayFilledData();
- Value array = newArrayFilledData.src();
- if (array.isPhi()
- || !array.definition.isCreatingArray()
- || newArrayFilledData.instructionInstanceCanThrow(appView, context)) {
- return ClassInitializerSideEffect.SIDE_EFFECTS_THAT_CANNOT_BE_POSTPONED;
- }
- if (controlFlowMayDependOnEnvironment.isUnknown()) {
- controlFlowMayDependOnEnvironment =
- OptionalBool.of(code.controlFlowMayDependOnEnvironment(environmentAnalysis));
- }
- if (controlFlowMayDependOnEnvironment.isTrue()) {
- return ClassInitializerSideEffect.SIDE_EFFECTS_THAT_CANNOT_BE_POSTPONED;
- }
- continue;
- }
-
- // Array creations are only observable if they may throw, or if the array content may depend
- // on the environment.
- if (instruction.isInvokeNewArray()) {
- InvokeNewArray invokeNewArray = instruction.asInvokeNewArray();
- if (invokeNewArray.instructionInstanceCanThrow(appView, context)) {
- return ClassInitializerSideEffect.SIDE_EFFECTS_THAT_CANNOT_BE_POSTPONED;
- }
- for (Value argument : invokeNewArray.arguments()) {
- if (environmentAnalysis.valueMayDependOnEnvironment(argument)) {
- return ClassInitializerSideEffect.SIDE_EFFECTS_THAT_CANNOT_BE_POSTPONED;
- }
- }
- continue;
- }
-
- if (instruction.isNewArrayEmpty()) {
+ // Array creations are observable if they may throw.
+ if (instruction.isInvokeNewArray()
+ || instruction.isNewArrayEmpty()
+ || instruction.isNewArrayFilledData()) {
if (instruction.instructionInstanceCanThrow(appView, context)) {
return ClassInitializerSideEffect.SIDE_EFFECTS_THAT_CANNOT_BE_POSTPONED;
}
@@ -116,11 +74,11 @@
appView.appInfo().resolveField(staticPut.getField()).getResolvedField();
if (field == null
|| field.holder() != context.getHolderType()
- || environmentAnalysis.valueMayDependOnEnvironment(staticPut.value())
|| instruction.instructionInstanceCanThrow(appView, context)) {
return ClassInitializerSideEffect.SIDE_EFFECTS_THAT_CANNOT_BE_POSTPONED;
}
- mayHaveSideEffects = true;
+ controlFlowRequiredToBeIndependentOfControlFlow = true;
+ valuesRequiredToBeIndependentOfEnvironment.add(staticPut.value());
continue;
}
@@ -130,8 +88,21 @@
}
}
- return mayHaveSideEffects
- ? ClassInitializerSideEffect.SIDE_EFFECTS_THAT_CAN_BE_POSTPONED
- : ClassInitializerSideEffect.NONE;
+ if (controlFlowRequiredToBeIndependentOfControlFlow) {
+ if (code.controlFlowMayDependOnEnvironment(valuesRequiredToBeIndependentOfEnvironment::add)) {
+ return ClassInitializerSideEffect.SIDE_EFFECTS_THAT_CANNOT_BE_POSTPONED;
+ }
+ }
+
+ if (!valuesRequiredToBeIndependentOfEnvironment.isEmpty()) {
+ ValueMayDependOnEnvironmentAnalysis environmentAnalysis =
+ new ValueMayDependOnEnvironmentAnalysis(appView, code);
+ if (environmentAnalysis.anyValueMayDependOnEnvironment(
+ valuesRequiredToBeIndependentOfEnvironment)) {
+ return ClassInitializerSideEffect.SIDE_EFFECTS_THAT_CANNOT_BE_POSTPONED;
+ }
+ return ClassInitializerSideEffect.SIDE_EFFECTS_THAT_CAN_BE_POSTPONED;
+ }
+ return ClassInitializerSideEffect.NONE;
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
index 50cec6e..57d52df 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
@@ -12,6 +12,7 @@
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.code.Phi.RegisterReadType;
@@ -544,12 +545,14 @@
Set<BasicBlock> blocksToRemove,
DexType downcast) {
assert blocksToRemove != null;
- DexType codeHolder = code.method().holder();
- DexType inlineeHolder = inlinee.method().holder();
- if (codeHolder != inlineeHolder && inlinee.method().isOnlyInlinedIntoNestMembers()) {
+ ProgramMethod callerContext = code.context();
+ ProgramMethod calleeContext = inlinee.context();
+ if (callerContext.getHolder() != calleeContext.getHolder()
+ && calleeContext.getDefinition().isOnlyInlinedIntoNestMembers()) {
// Should rewrite private calls to virtual calls.
- assert NestUtils.sameNest(codeHolder, inlineeHolder, appView);
- NestUtils.rewriteNestCallsForInlining(inlinee, codeHolder, appView);
+ assert NestUtils.sameNest(
+ callerContext.getHolderType(), calleeContext.getHolderType(), appView);
+ NestUtils.rewriteNestCallsForInlining(inlinee, callerContext, appView);
}
boolean inlineeCanThrow = canThrow(inlinee);
// Split the block with the invocation into three blocks, where the first block contains all
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 3ef2eee..f177619 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
@@ -15,7 +15,6 @@
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
import com.android.tools.r8.ir.analysis.TypeChecker;
-import com.android.tools.r8.ir.analysis.ValueMayDependOnEnvironmentAnalysis;
import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
import com.android.tools.r8.ir.analysis.type.Nullability;
import com.android.tools.r8.ir.analysis.type.TypeElement;
@@ -50,6 +49,7 @@
import java.util.Map;
import java.util.Queue;
import java.util.Set;
+import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@@ -283,8 +283,16 @@
return liveAtEntrySets;
}
+ /**
+ * Returns true if the control flow of this code object may depend on the environment. If false is
+ * returned, then the control flow of this code object does not depend on the environment if none
+ * of the values passed to the given consumer depend on the environment.
+ *
+ * <p>It is the responsibility of the client to prove that none of the values passed to {@param
+ * valueRequiredToBeIndependentOfEnvironmentConsumer} may depend on the environment.
+ */
public boolean controlFlowMayDependOnEnvironment(
- ValueMayDependOnEnvironmentAnalysis environmentAnalysis) {
+ Consumer<Value> valueRequiredToBeIndependentOfEnvironmentConsumer) {
for (BasicBlock block : blocks) {
if (block.hasCatchHandlers()) {
// Whether an instruction throws may generally depend on the environment.
@@ -292,18 +300,13 @@
}
if (block.exit().isIf()) {
If ifInstruction = block.exit().asIf();
- if (environmentAnalysis.valueMayDependOnEnvironment(ifInstruction.lhs())) {
- return true;
- }
- if (!ifInstruction.isZeroTest()
- && environmentAnalysis.valueMayDependOnEnvironment(ifInstruction.rhs())) {
- return true;
+ valueRequiredToBeIndependentOfEnvironmentConsumer.accept(ifInstruction.lhs());
+ if (!ifInstruction.isZeroTest()) {
+ valueRequiredToBeIndependentOfEnvironmentConsumer.accept(ifInstruction.rhs());
}
} else if (block.exit().isSwitch()) {
Switch switchInstruction = block.exit().asSwitch();
- if (environmentAnalysis.valueMayDependOnEnvironment(switchInstruction.value())) {
- return true;
- }
+ valueRequiredToBeIndependentOfEnvironmentConsumer.accept(switchInstruction.value());
}
}
return false;
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index ee7d57e..be64578 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -39,6 +39,7 @@
import java.util.List;
import java.util.Set;
import java.util.function.Function;
+import java.util.function.Predicate;
public abstract class Instruction implements InstructionOrPhi, TypeAndLocalInfoSupplier {
@@ -104,8 +105,12 @@
}
public boolean hasInValueWithLocalInfo() {
- for (Value inValue : inValues()) {
- if (inValue.hasLocalInfo()) {
+ return hasInValueThatMatches(Value::hasLocalInfo);
+ }
+
+ public boolean hasInValueThatMatches(Predicate<Value> predicate) {
+ for (Value value : inValues()) {
+ if (predicate.test(value)) {
return true;
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
index 00c88b8..e89d9b0 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
@@ -117,10 +117,10 @@
return result;
}
// Allow optimizing static library invokes in D8.
- DexClass clazz = appView.definitionFor(getInvokedMethod().holder);
+ DexClass clazz = appView.definitionForHolder(getInvokedMethod());
if (clazz != null
&& (clazz.isLibraryClass() || appView.libraryMethodOptimizer().isModeled(clazz.type))) {
- return appView.definitionFor(getInvokedMethod());
+ return clazz.lookupMethod(getInvokedMethod());
}
// In D8, we can treat invoke-static instructions as having a single target if the invoke is
// targeting a method in the enclosing class.
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
index 2d95115..951a451 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
@@ -130,7 +130,7 @@
DexClass clazz = appView.definitionFor(holder);
if (clazz != null
&& (clazz.isLibraryClass() || appView.libraryMethodOptimizer().isModeled(clazz.type))) {
- DexEncodedMethod singleTargetCandidate = appView.definitionFor(method);
+ DexEncodedMethod singleTargetCandidate = clazz.lookupMethod(method);
if (singleTargetCandidate != null && (clazz.isFinal() || singleTargetCandidate.isFinal())) {
return singleTargetCandidate;
}
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 35760b4..c53b686 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
@@ -741,14 +741,17 @@
enumUnboxer.finishAnalysis();
enumUnboxer.unboxEnums(postMethodProcessorBuilder, executorService, feedback);
}
- new TrivialFieldAccessReprocessor(appView.withLiveness(), postMethodProcessorBuilder)
- .run(executorService, feedback, timing);
+ if (!options.debug) {
+ new TrivialFieldAccessReprocessor(appView.withLiveness(), postMethodProcessorBuilder)
+ .run(executorService, feedback, timing);
+ }
timing.begin("IR conversion phase 2");
graphLenseForIR = appView.graphLense();
PostMethodProcessor postMethodProcessor =
postMethodProcessorBuilder.build(appView.withLiveness(), executorService, timing);
if (postMethodProcessor != null) {
+ assert !options.debug;
postMethodProcessor.forEachWave(feedback, executorService);
feedback.updateVisibleOptimizationInfo();
assert graphLenseForIR == appView.graphLense();
@@ -1193,6 +1196,10 @@
}
if (method.isProcessed()) {
+ // We loose locals information when processing dex code, so if in debug mode only process
+ // synthesized methods.
+ // TODO(b/158818229): Check if the synthesized check is needed.
+ assert !isDebugMode || method.isD8R8Synthesized();
assert !appView.enableWholeProgramOptimizations()
|| !appView.appInfo().withLiveness().neverReprocess.contains(method.method);
} else {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
index fbeff68..f1c403b 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
@@ -4,6 +4,8 @@
package com.android.tools.r8.ir.conversion;
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexProgramClass;
@@ -126,10 +128,10 @@
.reprocess
.forEach(
reference -> {
- DexEncodedMethod definition = appView.definitionFor(reference);
+ DexProgramClass clazz =
+ asProgramClassOrNull(appView.definitionForHolder(reference));
+ DexEncodedMethod definition = reference.lookupOnClass(clazz);
if (definition != null) {
- DexProgramClass clazz =
- appView.definitionForHolder(definition).asProgramClass();
set.createAndAdd(clazz, definition);
}
});
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
index 10f34af..9dfe8e3 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
@@ -89,7 +89,7 @@
SortedProgramMethodSet wave = callGraph.extractLeaves();
wave.forEach(
method -> {
- if (callSiteInformation.hasSingleCallSite(method)) {
+ if (callSiteInformation.hasSingleCallSite(method) && options.enableInlining) {
callGraph.cycleEliminationResult.forEachRemovedCaller(method, reprocessing::add);
}
});
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
index 4737d39..3971c88 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
@@ -337,8 +337,7 @@
if (method.isFinal()) {
return false;
}
- return appView.options().desugaredLibraryConfiguration.retargetMethod(method.method, appView)
- != null;
+ return appView.options().desugaredLibraryConfiguration.retargetMethod(method, appView) != null;
}
private boolean dontRewrite(DexClass clazz, DexEncodedMethod method) {
@@ -396,7 +395,7 @@
DexMethod forwardMethod =
targetHolder.isInterface()
? rewriter.defaultAsMethodOfCompanionClass(method)
- : appView.options().desugaredLibraryConfiguration.retargetMethod(method, appView);
+ : appView.options().desugaredLibraryConfiguration.retargetMethod(target, appView);
DexEncodedMethod desugaringForwardingMethod =
DexEncodedMethod.createDesugaringForwardingMethod(
target, clazz, forwardMethod, dexItemFactory);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/D8NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/D8NestBasedAccessDesugaring.java
index ba49fc0..bd468b0 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/D8NestBasedAccessDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/D8NestBasedAccessDesugaring.java
@@ -65,12 +65,18 @@
Instruction instruction = instructions.next();
if (instruction.isInvokeMethod()) {
InvokeMethod invokeMethod = instruction.asInvokeMethod();
- DexMethod methodCalled = invokeMethod.getInvokedMethod();
- DexEncodedMethod encodedMethodCalled =
- methodCalled.holder.isClassType() ? appView.definitionFor(methodCalled) : null;
- if (encodedMethodCalled != null && invokeRequiresRewriting(encodedMethodCalled, method)) {
- DexMethod bridge = ensureInvokeBridge(encodedMethodCalled);
- if (encodedMethodCalled.isInstanceInitializer()) {
+ DexMethod invokedMethod = invokeMethod.getInvokedMethod();
+ if (!invokedMethod.holder.isClassType()) {
+ continue;
+ }
+ // Since we only need to desugar accesses to private methods, and all accesses to private
+ // methods must be accessing the private method directly on its holder, we can lookup the
+ // method on the holder instead of resolving the method.
+ DexClass holder = appView.definitionForHolder(invokedMethod);
+ DexEncodedMethod definition = invokedMethod.lookupOnClass(holder);
+ if (definition != null && invokeRequiresRewriting(definition, method)) {
+ DexMethod bridge = ensureInvokeBridge(definition);
+ if (definition.isInstanceInitializer()) {
instructions.previous();
Value extraNullValue =
instructions.insertConstNullInstruction(code, appView.options());
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java
index 1590484..a7d5252 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.graph.AppView;
+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.DexString;
@@ -132,15 +133,15 @@
}
// If the method is retargeted, answers the retargeted method, else null.
- public DexMethod retargetMethod(DexMethod method, AppView<?> appView) {
- Map<DexType, DexType> typeMap = retargetCoreLibMember.get(method.name);
- if (typeMap != null && typeMap.containsKey(method.holder)) {
+ public DexMethod retargetMethod(DexEncodedMethod method, AppView<?> appView) {
+ Map<DexType, DexType> typeMap = retargetCoreLibMember.get(method.getName());
+ if (typeMap != null && typeMap.containsKey(method.getHolderType())) {
return appView
.dexItemFactory()
.createMethod(
- typeMap.get(method.holder),
- appView.dexItemFactory().prependTypeToProto(method.holder, method.proto),
- method.name);
+ typeMap.get(method.getHolderType()),
+ appView.dexItemFactory().prependHolderToProto(method.getReference()),
+ method.getName());
}
return null;
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
index 53ff67e..cfb48f2 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
@@ -55,7 +55,7 @@
// d8 needs to force resolution of given methods to see if the invoke needs to be rewritten.
private final Map<DexString, List<DexMethod>> virtualRewrites = new IdentityHashMap<>();
// Non final virtual library methods requiring generation of emulated dispatch.
- private final Set<DexMethod> emulatedDispatchMethods = Sets.newHashSet();
+ private final Set<DexEncodedMethod> emulatedDispatchMethods = Sets.newIdentityHashSet();
public DesugaredLibraryRetargeter(AppView<?> appView) {
this.appView = appView;
@@ -139,7 +139,7 @@
appView
.options()
.desugaredLibraryConfiguration
- .retargetMethod(dexEncodedMethod.method, appView);
+ .retargetMethod(dexEncodedMethod, appView);
if (retargetMethod != null) {
iterator.replaceCurrentInstruction(
new InvokeStatic(retargetMethod, invoke.outValue(), invoke.arguments()));
@@ -197,8 +197,8 @@
} else if (!encodedMethod.isFinal()) {
// Virtual rewrites require emulated dispatch for inheritance.
// The call is rewritten to the dispatch holder class instead.
- handleEmulateDispatch(appView, encodedMethod.method);
- newHolder = dispatchHolderTypeFor(encodedMethod.method);
+ handleEmulateDispatch(appView, encodedMethod);
+ newHolder = dispatchHolderTypeFor(encodedMethod);
}
}
DexProto proto = encodedMethod.method.proto;
@@ -213,8 +213,7 @@
private DexMethod computeRetargetMethod(DexMethod method, boolean isStatic, DexType newHolder) {
DexItemFactory factory = appView.dexItemFactory();
- DexProto newProto =
- isStatic ? method.proto : factory.prependTypeToProto(method.holder, method.proto);
+ DexProto newProto = isStatic ? method.proto : factory.prependHolderToProto(method);
return factory.createMethod(newHolder, newProto, method.name);
}
@@ -230,7 +229,7 @@
return found;
}
- private void handleEmulateDispatch(AppView<?> appView, DexMethod method) {
+ private void handleEmulateDispatch(AppView<?> appView, DexEncodedMethod method) {
emulatedDispatchMethods.add(method);
if (!appView.options().isDesugaredLibraryCompilation()) {
// Add rewrite rules so keeps rules are correctly generated in the program.
@@ -266,10 +265,10 @@
private void addInterfacesAndForwardingMethods(
ExecutorService executorService, IRConverter converter) throws ExecutionException {
assert !appView.options().isDesugaredLibraryCompilation();
- Map<DexType, List<DexMethod>> map = Maps.newIdentityHashMap();
- for (DexMethod emulatedDispatchMethod : emulatedDispatchMethods) {
- map.putIfAbsent(emulatedDispatchMethod.holder, new ArrayList<>(1));
- map.get(emulatedDispatchMethod.holder).add(emulatedDispatchMethod);
+ Map<DexType, List<DexEncodedMethod>> map = Maps.newIdentityHashMap();
+ for (DexEncodedMethod emulatedDispatchMethod : emulatedDispatchMethods) {
+ map.putIfAbsent(emulatedDispatchMethod.getHolderType(), new ArrayList<>(1));
+ map.get(emulatedDispatchMethod.getHolderType()).add(emulatedDispatchMethod);
}
SortedProgramMethodSet addedMethods = SortedProgramMethodSet.create();
for (DexProgramClass clazz : appView.appInfo().classes()) {
@@ -295,7 +294,8 @@
converter.processMethodsConcurrently(addedMethods, executorService);
}
- private boolean inherit(DexLibraryClass clazz, DexType typeToInherit, Set<DexMethod> retarget) {
+ private boolean inherit(
+ DexLibraryClass clazz, DexType typeToInherit, Set<DexEncodedMethod> retarget) {
DexLibraryClass current = clazz;
while (current.type != appView.dexItemFactory().objectType) {
if (current.type == typeToInherit) {
@@ -316,36 +316,35 @@
private void addInterfacesAndForwardingMethods(
DexProgramClass clazz,
- List<DexMethod> methods,
+ List<DexEncodedMethod> methods,
Consumer<DexEncodedMethod> newForwardingMethodsConsumer) {
// DesugaredLibraryRetargeter emulate dispatch: insertion of a marker interface & forwarding
// methods.
// We cannot use the ClassProcessor since this applies up to 26, while the ClassProcessor
// applies up to 24.
- for (DexMethod dexMethod : methods) {
+ for (DexEncodedMethod method : methods) {
DexType[] newInterfaces =
Arrays.copyOf(clazz.interfaces.values, clazz.interfaces.size() + 1);
- newInterfaces[newInterfaces.length - 1] = dispatchInterfaceTypeFor(dexMethod);
+ newInterfaces[newInterfaces.length - 1] = dispatchInterfaceTypeFor(method);
clazz.interfaces = new DexTypeList(newInterfaces);
- DexEncodedMethod dexEncodedMethod = clazz.lookupVirtualMethod(dexMethod);
- if (dexEncodedMethod == null) {
- DexEncodedMethod newMethod = createForwardingMethod(dexMethod, clazz);
+ if (clazz.lookupVirtualMethod(method.getReference()) == null) {
+ DexEncodedMethod newMethod = createForwardingMethod(method, clazz);
clazz.addVirtualMethod(newMethod);
newForwardingMethodsConsumer.accept(newMethod);
}
}
}
- private DexEncodedMethod createForwardingMethod(DexMethod target, DexClass clazz) {
+ private DexEncodedMethod createForwardingMethod(DexEncodedMethod target, DexClass clazz) {
// NOTE: Never add a forwarding method to methods of classes unknown or coming from
// android.jar
// even if this results in invalid code, these classes are never desugared.
// In desugared library, emulated interface methods can be overridden by retarget lib members.
DexMethod forwardMethod =
appView.options().desugaredLibraryConfiguration.retargetMethod(target, appView);
- assert forwardMethod != null && forwardMethod != target;
+ assert forwardMethod != null && forwardMethod != target.getReference();
return DexEncodedMethod.createDesugaringForwardingMethod(
- appView.definitionFor(target), clazz, forwardMethod, appView.dexItemFactory());
+ target, clazz, forwardMethod, appView.dexItemFactory());
}
private void synthesizeEmulatedDispatchMethods(DexApplication.Builder<?> builder) {
@@ -361,7 +360,7 @@
| Constants.ACC_INTERFACE);
ClassAccessFlags holderAccessFlags =
ClassAccessFlags.fromSharedAccessFlags(Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC);
- for (DexMethod emulatedDispatchMethod : emulatedDispatchMethods) {
+ for (DexEncodedMethod emulatedDispatchMethod : emulatedDispatchMethods) {
// Dispatch interface.
DexType interfaceType = dispatchInterfaceTypeFor(emulatedDispatchMethod);
DexEncodedMethod itfMethod =
@@ -390,7 +389,7 @@
}
private DexEncodedMethod generateInterfaceDispatchMethod(
- DexMethod emulatedDispatchMethod, DexType interfaceType) {
+ DexEncodedMethod emulatedDispatchMethod, DexType interfaceType) {
MethodAccessFlags flags =
MethodAccessFlags.fromSharedAccessFlags(
Constants.ACC_PUBLIC | Constants.ACC_ABSTRACT | Constants.ACC_SYNTHETIC, false);
@@ -398,13 +397,15 @@
appView
.dexItemFactory()
.createMethod(
- interfaceType, emulatedDispatchMethod.proto, emulatedDispatchMethod.name);
+ interfaceType,
+ emulatedDispatchMethod.getProto(),
+ emulatedDispatchMethod.getName());
return new DexEncodedMethod(
newMethod, flags, DexAnnotationSet.empty(), ParameterAnnotationsList.empty(), null, true);
}
private DexEncodedMethod generateHolderDispatchMethod(
- DexMethod emulatedDispatchMethod, DexType dispatchHolder, DexMethod itfMethod) {
+ DexEncodedMethod emulatedDispatchMethod, DexType dispatchHolder, DexMethod itfMethod) {
// The method should look like:
// static foo(rcvr, arg0, arg1) {
// if (rcvr instanceof interfaceType) {
@@ -423,9 +424,9 @@
DexMethod newMethod =
appView
.dexItemFactory()
- .createMethod(dispatchHolder, desugarMethod.proto, emulatedDispatchMethod.name);
+ .createMethod(dispatchHolder, desugarMethod.proto, emulatedDispatchMethod.getName());
return DexEncodedMethod.toEmulateDispatchLibraryMethod(
- emulatedDispatchMethod.holder,
+ emulatedDispatchMethod.getHolderType(),
newMethod,
desugarMethod,
itfMethod,
@@ -435,7 +436,7 @@
}
private void reportInvalidLibrarySupertype(
- DexLibraryClass libraryClass, Set<DexMethod> retarget) {
+ DexLibraryClass libraryClass, Set<DexEncodedMethod> retarget) {
DexClass dexClass = appView.definitionFor(libraryClass.superType);
String message;
if (dexClass == null) {
@@ -456,15 +457,15 @@
retarget);
}
- private DexType dispatchInterfaceTypeFor(DexMethod method) {
+ private DexType dispatchInterfaceTypeFor(DexEncodedMethod method) {
return dispatchTypeFor(method, "dispatchInterface");
}
- private DexType dispatchHolderTypeFor(DexMethod method) {
+ private DexType dispatchHolderTypeFor(DexEncodedMethod method) {
return dispatchTypeFor(method, "dispatchHolder");
}
- private DexType dispatchTypeFor(DexMethod method, String suffix) {
+ private DexType dispatchTypeFor(DexEncodedMethod method, String suffix) {
String descriptor =
"L"
+ appView
@@ -473,9 +474,9 @@
.getSynthesizedLibraryClassesPackagePrefix()
+ DESUGAR_LIB_RETARGET_CLASS_NAME_PREFIX
+ '$'
- + method.holder.getName()
+ + method.getHolderType().getName()
+ '$'
- + method.name
+ + method.getName()
+ '$'
+ suffix
+ ';';
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 901517a..27a5224 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
@@ -307,7 +307,23 @@
invokeSuper.outValue(), invokeSuper.arguments()));
} else {
DexType dexType = maximallySpecificEmulatedInterfaceOrNull(invokedMethod);
- if (dexType != null) {
+ if (dexType == null) {
+ if (clazz.isInterface()
+ && appView.rewritePrefix.hasRewrittenType(clazz.type, appView)) {
+ DexEncodedMethod target =
+ appView.appInfoForDesugaring().lookupSuperTarget(invokedMethod, code.context());
+ if (target != null && target.isDefaultMethod()) {
+ DexClass holder = appView.definitionFor(target.holder());
+ if (holder.isLibraryClass() && holder.isInterface()) {
+ instructions.replaceCurrentInstruction(
+ new InvokeStatic(
+ defaultAsMethodOfCompanionClass(target.method, factory),
+ invokeSuper.outValue(),
+ invokeSuper.arguments()));
+ }
+ }
+ }
+ } else {
// That invoke super may not resolve since the super method may not be present
// since it's in the emulated interface. We need to force resolution. If it resolves
// to a library method, then it needs to be rewritten.
@@ -324,7 +340,7 @@
// retarget or if it just calls a companion class method and rewrite.
DexMethod retargetMethod =
options.desugaredLibraryConfiguration.retargetMethod(
- dexEncodedMethod.method, appView);
+ dexEncodedMethod, appView);
if (retargetMethod == null) {
DexMethod originalCompanionMethod =
instanceAsMethodOfCompanionClass(
@@ -357,7 +373,7 @@
continue;
}
- DexClass clazz = appInfo.definitionFor(method.holder);
+ DexClass clazz = appInfo.definitionForHolder(method);
if (clazz == null) {
// Report missing class since we don't know if it is an interface.
warnMissingType(encodedMethod.method, method.holder);
@@ -367,7 +383,7 @@
"defined in library class " + clazz.toSourceString(),
getMethodOrigin(encodedMethod.method));
}
- DexEncodedMethod directTarget = appView.definitionFor(method);
+ DexEncodedMethod directTarget = clazz.lookupMethod(method);
if (directTarget != null) {
// This can be a private instance method call. Note that the referenced
// method is expected to be in the current class since it is private, but desugaring
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java
index 2586141..58730b9 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java
@@ -86,8 +86,9 @@
private DexEncodedMethod lookupOnHolder(
DexMethod method, DexClassAndMethod context, Invoke.Type invokeType) {
- return appView.definitionFor(
- appView.graphLense().lookupMethod(method, context.getReference(), invokeType).getMethod());
+ DexMethod rewritten =
+ appView.graphLense().lookupMethod(method, context.getReference(), invokeType).getMethod();
+ return rewritten.lookupOnClass(appView.definitionForHolder(rewritten));
}
private DexEncodedField lookupOnHolder(DexField field) {
@@ -235,7 +236,7 @@
DexProto proto =
encodedMethod.accessFlags.isStatic()
? method.proto
- : appView.dexItemFactory().prependTypeToProto(method.holder, method.proto);
+ : appView.dexItemFactory().prependHolderToProto(method);
return appView
.dexItemFactory()
.createMethod(method.holder, proto, computeMethodBridgeName(encodedMethod));
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 c6447c1..8f4650a 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
@@ -8,6 +8,7 @@
import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
+import com.android.tools.r8.algorithms.scc.SCC;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.Unreachable;
@@ -117,12 +118,10 @@
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
-import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Collection;
-import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
@@ -3591,7 +3590,8 @@
// This is a simplified variant of the removeRedundantPhis algorithm in Section 3.2 of:
// http://compilers.cs.uni-saarland.de/papers/bbhlmz13cc.pdf
private static void replaceTrivialNewInstancePhis(Value newInstanceValue) {
- List<Set<Value>> components = new SCC().computeSCC(newInstanceValue);
+ List<Set<Value>> components =
+ new SCC<Value>(Value::uniquePhiUsers).computeSCC(newInstanceValue);
for (int i = components.size() - 1; i >= 0; i--) {
Set<Value> component = components.get(i);
if (component.size() == 1 && component.iterator().next() == newInstanceValue) {
@@ -3621,61 +3621,6 @@
}
}
- // Dijkstra's path-based strongly-connected components algorithm.
- // https://en.wikipedia.org/wiki/Path-based_strong_component_algorithm
- private static class SCC {
-
- private int currentTime = 0;
- private final Reference2IntMap<Value> discoverTime = new Reference2IntOpenHashMap<>();
- private final Set<Value> unassignedSet = Sets.newIdentityHashSet();
- private final Deque<Value> unassignedStack = new ArrayDeque<>();
- private final Deque<Value> preorderStack = new ArrayDeque<>();
- private final List<Set<Value>> components = new ArrayList<>();
-
- public List<Set<Value>> computeSCC(Value v) {
- assert currentTime == 0;
- dfs(v);
- return components;
- }
-
- private void dfs(Value value) {
- discoverTime.put(value, currentTime++);
- unassignedSet.add(value);
- unassignedStack.push(value);
- preorderStack.push(value);
- for (Phi phi : value.uniquePhiUsers()) {
- if (!discoverTime.containsKey(phi)) {
- // If not seen yet, continue the search.
- dfs(phi);
- } else if (unassignedSet.contains(phi)) {
- // If seen already and the element is on the unassigned stack we have found a cycle.
- // Pop off everything discovered later than the target from the preorder stack. This may
- // not coincide with the cycle as an outer cycle may already have popped elements off.
- int discoverTimeOfPhi = discoverTime.getInt(phi);
- while (discoverTimeOfPhi < discoverTime.getInt(preorderStack.peek())) {
- preorderStack.pop();
- }
- }
- }
- if (preorderStack.peek() == value) {
- // If the current element is the top of the preorder stack, then we are at entry to a
- // strongly-connected component consisting of this element and every element above this
- // element on the stack.
- Set<Value> component = SetUtils.newIdentityHashSet(unassignedStack.size());
- while (true) {
- Value member = unassignedStack.pop();
- unassignedSet.remove(member);
- component.add(member);
- if (member == value) {
- components.add(component);
- break;
- }
- }
- preorderStack.pop();
- }
- }
- }
-
// See comment for InternalOptions.canHaveNumberConversionRegisterAllocationBug().
public void workaroundNumberConversionRegisterAllocationBug(IRCode code) {
ListIterator<BasicBlock> blocks = code.listIterator();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
index cb8f858..6fe0808 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
@@ -9,7 +9,7 @@
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.code.Assume;
@@ -290,61 +290,39 @@
if (!receiver.getType().isClassType()) {
return target;
}
- DexEncodedMethod encodedTarget = appView.definitionFor(target);
- if (encodedTarget == null
- || !canInvokeTargetWithInvokeVirtual(encodedTarget)
- || !hasAccessToInvokeTargetFromContext(encodedTarget, context)) {
- // Don't rewrite this instruction as it could remove an error from the program.
+
+ SingleResolutionResult resolutionResult =
+ appView.appInfo().resolveMethodOnClass(target).asSingleResolution();
+ if (resolutionResult == null
+ || resolutionResult
+ .isAccessibleForVirtualDispatchFrom(context, appView.appInfo())
+ .isPossiblyFalse()) {
+ // Method does not resolve or is not accessible.
return target;
}
- DexType receiverType =
- appView.graphLense().lookupType(receiver.getType().asClassType().getClassType());
+
+ DexType receiverType = receiver.getType().asClassType().getClassType();
if (receiverType == target.holder) {
// Virtual invoke is already as specific as it can get.
return target;
}
- ResolutionResult resolutionResult =
- appView.appInfo().resolveMethodOnClass(target, receiverType);
- DexEncodedMethod newTarget =
- resolutionResult.isVirtualTarget() ? resolutionResult.getSingleTarget() : null;
- if (newTarget == null || newTarget.method == target) {
- // Most likely due to a missing class, or invoke is already as specific as it gets.
+
+ SingleResolutionResult newResolutionResult =
+ appView.appInfo().resolveMethodOnClass(target, receiverType).asSingleResolution();
+ if (newResolutionResult == null
+ || newResolutionResult
+ .isAccessibleForVirtualDispatchFrom(context, appView.appInfo())
+ .isPossiblyFalse()) {
return target;
}
- DexClass newTargetClass = appView.definitionFor(newTarget.holder());
- if (newTargetClass == null
- || newTargetClass.isLibraryClass()
- || !canInvokeTargetWithInvokeVirtual(newTarget)
- || !hasAccessToInvokeTargetFromContext(newTarget, context)) {
- // Not safe to invoke `newTarget` with virtual invoke from the current context.
+
+ DexClass newTargetHolder = newResolutionResult.getResolvedHolder();
+ if (!newTargetHolder.isProgramClass() || newTargetHolder.isInterface()) {
+ // Not safe to invoke the new resolution result with virtual invoke from the current context.
return target;
}
- return newTarget.method;
- }
- private boolean canInvokeTargetWithInvokeVirtual(DexEncodedMethod target) {
- return target.isNonPrivateVirtualMethod() && appView.isInterface(target.holder()).isFalse();
- }
-
- private boolean hasAccessToInvokeTargetFromContext(
- DexEncodedMethod target, ProgramMethod context) {
- assert !target.accessFlags.isPrivate();
- DexType holder = target.holder();
- if (holder == context.getHolderType()) {
- // It is always safe to invoke a method from the same enclosing class.
- return true;
- }
- DexClass clazz = appView.definitionFor(holder);
- if (clazz == null) {
- // Conservatively report an illegal access.
- return false;
- }
- if (holder.isSamePackage(context.getHolderType())) {
- // The class must be accessible (note that we have already established that the method is not
- // private).
- return !clazz.accessFlags.isPrivate();
- }
- // If the method is in another package, then the method and its holder must be public.
- return clazz.accessFlags.isPublic() && target.accessFlags.isPublic();
+ // Change the invoke-virtual instruction to target the refined resolution result instead.
+ return newResolutionResult.getResolvedMethod().method;
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/NestUtils.java b/src/main/java/com/android/tools/r8/ir/optimize/NestUtils.java
index 9db947a..5ee91d0 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/NestUtils.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/NestUtils.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionListIterator;
@@ -41,42 +42,45 @@
}
public static void rewriteNestCallsForInlining(
- IRCode code, DexType callerHolder, AppView<?> appView) {
+ IRCode code, ProgramMethod callerContext, AppView<?> appView) {
// This method is called when inlining code into the nest member callerHolder.
InstructionListIterator iterator = code.instructionListIterator();
- DexClass callerHolderClass = appView.definitionFor(callerHolder);
- assert callerHolderClass != null;
- assert code.method().holder() != callerHolder;
+ assert code.context().getHolder() != callerContext.getHolder();
while (iterator.hasNext()) {
Instruction instruction = iterator.next();
if (instruction.isInvokeDirect()) {
InvokeDirect invoke = instruction.asInvokeDirect();
DexMethod method = invoke.getInvokedMethod();
- DexEncodedMethod encodedMethod = appView.definitionFor(method);
+ DexClass holder = appView.definitionForHolder(method);
+ DexEncodedMethod encodedMethod = method.lookupOnClass(holder);
if (encodedMethod != null && !encodedMethod.isInstanceInitializer()) {
assert encodedMethod.isPrivateMethod();
// Call to private method which has now to be interface/virtual
// (Now call to nest member private method).
if (invoke.getInterfaceBit()) {
iterator.replaceCurrentInstruction(
- new InvokeInterface(method, invoke.outValue(), invoke.inValues()));
+ new InvokeInterface(method, invoke.outValue(), invoke.arguments()));
} else {
iterator.replaceCurrentInstruction(
- new InvokeVirtual(method, invoke.outValue(), invoke.inValues()));
+ new InvokeVirtual(method, invoke.outValue(), invoke.arguments()));
}
}
} else if (instruction.isInvokeInterface() || instruction.isInvokeVirtual()) {
InvokeMethod invoke = instruction.asInvokeMethod();
DexMethod method = invoke.getInvokedMethod();
- if (method.holder == callerHolder) {
- DexEncodedMethod encodedMethod = appView.definitionFor(method);
+ if (method.holder == callerContext.getHolderType()) {
+ DexClass holder = appView.definitionForHolder(method);
+ DexEncodedMethod encodedMethod = method.lookupOnClass(holder);
if (encodedMethod != null && encodedMethod.isPrivateMethod()) {
// Interface/virtual nest member call to private method,
// which has now to be a direct call
// (Now call to same class private method).
iterator.replaceCurrentInstruction(
new InvokeDirect(
- method, invoke.outValue(), invoke.inValues(), callerHolderClass.isInterface()));
+ method,
+ invoke.outValue(),
+ invoke.arguments(),
+ callerContext.getHolder().isInterface()));
}
}
}
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 da8c4cc..b3bbbfc 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
@@ -484,21 +484,25 @@
throw new IllegalClassInlinerStateException();
}
- DexEncodedMethod singleTarget = appView.definitionFor(invokedMethod);
- if (singleTarget == null
- || !singleTarget.isInliningCandidate(
- method,
- Reason.SIMPLE,
- appView.appInfo(),
- NopWhyAreYouNotInliningReporter.getInstance())) {
+ DexProgramClass holder =
+ asProgramClassOrNull(appView.definitionForHolder(invokedMethod));
+ if (holder == null) {
throw new IllegalClassInlinerStateException();
}
- ProgramMethod singleTargetMethod =
- new ProgramMethod(
- appView.definitionForHolder(singleTarget).asProgramClass(), singleTarget);
- methodCallsOnInstance.put(
- invoke, new InliningInfo(singleTargetMethod, root.asNewInstance().clazz));
+ ProgramMethod singleTarget = holder.lookupProgramMethod(invokedMethod);
+ if (singleTarget == null
+ || !singleTarget
+ .getDefinition()
+ .isInliningCandidate(
+ method,
+ Reason.SIMPLE,
+ appView.appInfo(),
+ NopWhyAreYouNotInliningReporter.getInstance())) {
+ throw new IllegalClassInlinerStateException();
+ }
+
+ methodCallsOnInstance.put(invoke, new InliningInfo(singleTarget, eligibleClass.type));
break;
}
}
@@ -787,7 +791,11 @@
if (parent == null) {
return null;
}
- ProgramMethod encodedParent = asProgramMethodOrNull(appView.definitionFor(parent), appView);
+ DexProgramClass parentClass = asProgramClassOrNull(appView.definitionForHolder(parent));
+ if (parentClass == null) {
+ return null;
+ }
+ ProgramMethod encodedParent = parentClass.lookupProgramMethod(parent);
if (encodedParent == null) {
return null;
}
@@ -1250,12 +1258,13 @@
if (method == dexItemFactory.objectMembers.constructor) {
return true;
}
- DexEncodedMethod encodedMethod = appView.definitionFor(method);
- if (encodedMethod == null) {
+ DexProgramClass holder = asProgramClassOrNull(appView.definitionForHolder(method));
+ DexEncodedMethod definition = method.lookupOnClass(holder);
+ if (definition == null) {
return false;
}
InstanceInitializerInfo initializerInfo =
- encodedMethod.getOptimizationInfo().getInstanceInitializerInfo();
+ definition.getOptimizationInfo().getInstanceInitializerInfo();
return initializerInfo.receiverNeverEscapesOutsideConstructorChain();
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
index c5cd89d..e03bc6b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
@@ -768,9 +768,7 @@
+ "$"
+ method.name.toString());
DexProto proto =
- encodedMethod.isStatic()
- ? method.proto
- : factory.prependTypeToProto(method.holder, method.proto);
+ encodedMethod.isStatic() ? method.proto : factory.prependHolderToProto(method);
DexMethod newMethod = factory.createMethod(newHolder, fixupProto(proto), newMethodName);
lensBuilder.move(method, encodedMethod.isStatic(), newMethod, true);
encodedMethod.accessFlags.promoteToPublic();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
index 9e270af..8a25765 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
@@ -44,6 +44,7 @@
import static com.android.tools.r8.ir.code.Opcodes.XOR;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
@@ -549,7 +550,8 @@
{
InvokeDirect invoke = instruction.asInvokeDirect();
DexMethod invokedMethod = invoke.getInvokedMethod();
- DexEncodedMethod singleTarget = appView.definitionFor(invokedMethod);
+ DexClass holder = appView.definitionForHolder(invokedMethod);
+ DexEncodedMethod singleTarget = invokedMethod.lookupOnClass(holder);
if (singleTarget == null) {
return null;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/BooleanMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/BooleanMethodOptimizer.java
index a560844..dcb128a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/BooleanMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/BooleanMethodOptimizer.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.code.ConstString;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionListIterator;
@@ -42,6 +43,8 @@
Set<Value> affectedValues) {
if (singleTarget.method == dexItemFactory.booleanMembers.booleanValue) {
optimizeBooleanValue(code, instructionIterator, invoke);
+ } else if (singleTarget.method == dexItemFactory.booleanMembers.parseBoolean) {
+ optimizeParseBoolean(code, instructionIterator, invoke);
} else if (singleTarget.method == dexItemFactory.booleanMembers.valueOf) {
optimizeValueOf(code, instructionIterator, invoke, affectedValues);
}
@@ -64,6 +67,25 @@
}
}
+ private void optimizeParseBoolean(
+ IRCode code, InstructionListIterator instructionIterator, InvokeMethod invoke) {
+ Value argument = invoke.arguments().get(0).getAliasedValue();
+ if (!argument.isPhi()) {
+ Instruction definition = argument.definition;
+ if (definition.isConstString()) {
+ ConstString constString = definition.asConstString();
+ if (!constString.instructionInstanceCanThrow()) {
+ String value = constString.getValue().toString().toLowerCase();
+ if (value.equals("true")) {
+ instructionIterator.replaceCurrentInstructionWithConstInt(code, 1);
+ } else if (value.equals("false")) {
+ instructionIterator.replaceCurrentInstructionWithConstInt(code, 0);
+ }
+ }
+ }
+ }
+ }
+
private void optimizeValueOf(
IRCode code,
InstructionListIterator instructionIterator,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java
index 92c78af..e7e0fe6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java
@@ -7,6 +7,7 @@
import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
@@ -101,10 +102,11 @@
}
private DexEncodedMethod lookupMethod(DexMethod method) {
- DexEncodedMethod encodedMethod = appView.definitionFor(method);
- if (encodedMethod != null) {
+ DexClass holder = appView.definitionForHolder(method);
+ DexEncodedMethod definition = method.lookupOnClass(holder);
+ if (definition != null) {
modeledLibraryTypes.add(method.holder);
- return encodedMethod;
+ return definition;
}
return null;
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KmVisitorProviders.java b/src/main/java/com/android/tools/r8/kotlin/KmVisitorProviders.java
index 7431fb9..65bdeec 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KmVisitorProviders.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KmVisitorProviders.java
@@ -5,6 +5,11 @@
package com.android.tools.r8.kotlin;
import kotlinx.metadata.KmAnnotation;
+import kotlinx.metadata.KmContractVisitor;
+import kotlinx.metadata.KmEffectExpressionVisitor;
+import kotlinx.metadata.KmEffectInvocationKind;
+import kotlinx.metadata.KmEffectType;
+import kotlinx.metadata.KmEffectVisitor;
import kotlinx.metadata.KmFunctionVisitor;
import kotlinx.metadata.KmLambdaVisitor;
import kotlinx.metadata.KmPropertyVisitor;
@@ -108,4 +113,22 @@
KmVersionRequirementVisitor get();
}
+
+ @FunctionalInterface
+ public interface KmContractVisitorProvider {
+
+ KmContractVisitor get();
+ }
+
+ @FunctionalInterface
+ public interface KmEffectVisitorProvider {
+
+ KmEffectVisitor get(KmEffectType type, KmEffectInvocationKind effectInvocationKind);
+ }
+
+ @FunctionalInterface
+ public interface KmEffectExpressionVisitorProvider {
+
+ KmEffectExpressionVisitor get();
+ }
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/Kotlin.java b/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
index 07f6b0b..b16b8b6 100644
--- a/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
+++ b/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
@@ -43,6 +43,7 @@
public static final String arrayBinaryName = NAME + "/Array";
public static final String anyDescriptor = "L" + NAME + "/Any;";
+ public static final String anyName = NAME + "/Any";
}
// Mappings from JVM types to Kotlin types (of type DexType)
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinAnnotationArgumentInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinAnnotationArgumentInfo.java
new file mode 100644
index 0000000..6a9b23d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinAnnotationArgumentInfo.java
@@ -0,0 +1,210 @@
+// Copyright (c) 2020, 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;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexDefinitionSupplier;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.kotlin.Kotlin.ClassClassifiers;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.EnqueuerMetadataTraceable;
+import com.android.tools.r8.utils.Box;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import kotlinx.metadata.KmAnnotation;
+import kotlinx.metadata.KmAnnotationArgument;
+import kotlinx.metadata.KmAnnotationArgument.AnnotationValue;
+import kotlinx.metadata.KmAnnotationArgument.ArrayValue;
+import kotlinx.metadata.KmAnnotationArgument.EnumValue;
+import kotlinx.metadata.KmAnnotationArgument.KClassValue;
+
+abstract class KotlinAnnotationArgumentInfo implements EnqueuerMetadataTraceable {
+
+ private static final Map<String, KotlinAnnotationArgumentInfo> EMPTY_ARGUMENTS =
+ ImmutableMap.of();
+
+ abstract KmAnnotationArgument<?> rewrite(
+ AppView<AppInfoWithLiveness> appView, NamingLens namingLens);
+
+ private static KotlinAnnotationArgumentInfo createArgument(
+ KmAnnotationArgument<?> arg, DexItemFactory factory) {
+ if (arg instanceof KClassValue) {
+ return KotlinAnnotationClassValueInfo.create((KClassValue) arg, factory);
+ } else if (arg instanceof EnumValue) {
+ return KotlinAnnotationEnumValueInfo.create((EnumValue) arg, factory);
+ } else if (arg instanceof AnnotationValue) {
+ return KotlinAnnotationAnnotationValueInfo.create((AnnotationValue) arg, factory);
+ } else if (arg instanceof ArrayValue) {
+ return KotlinAnnotationArrayValueInfo.create((ArrayValue) arg, factory);
+ } else {
+ return KotlinAnnotationPrimitiveArgumentInfo.create(arg);
+ }
+ }
+
+ static Map<String, KotlinAnnotationArgumentInfo> create(
+ Map<String, KmAnnotationArgument<?>> arguments, DexItemFactory factory) {
+ if (arguments.isEmpty()) {
+ return EMPTY_ARGUMENTS;
+ }
+ LinkedHashMap<String, KotlinAnnotationArgumentInfo> modeled = new LinkedHashMap<>();
+ arguments.forEach((key, arg) -> modeled.put(key, createArgument(arg, factory)));
+ return modeled;
+ }
+
+ private static class KotlinAnnotationClassValueInfo extends KotlinAnnotationArgumentInfo {
+
+ private final KotlinTypeReference value;
+
+ private KotlinAnnotationClassValueInfo(KotlinTypeReference value) {
+ this.value = value;
+ }
+
+ private static KotlinAnnotationClassValueInfo create(KClassValue arg, DexItemFactory factory) {
+ return new KotlinAnnotationClassValueInfo(
+ KotlinTypeReference.fromBinaryName(arg.getValue(), factory));
+ }
+
+ @Override
+ public void trace(DexDefinitionSupplier definitionSupplier) {
+ value.trace(definitionSupplier);
+ }
+
+ @Override
+ KmAnnotationArgument<?> rewrite(AppView<AppInfoWithLiveness> appView, NamingLens namingLens) {
+ return new KClassValue(
+ value.toRenamedBinaryNameOrDefault(appView, namingLens, ClassClassifiers.anyName));
+ }
+ }
+
+ private static class KotlinAnnotationEnumValueInfo extends KotlinAnnotationArgumentInfo {
+
+ private final KotlinTypeReference enumClassName;
+ private final String enumEntryName;
+
+ private KotlinAnnotationEnumValueInfo(KotlinTypeReference enumClassName, String enumEntryName) {
+ this.enumClassName = enumClassName;
+ this.enumEntryName = enumEntryName;
+ }
+
+ private static KotlinAnnotationEnumValueInfo create(EnumValue arg, DexItemFactory factory) {
+ return new KotlinAnnotationEnumValueInfo(
+ KotlinTypeReference.fromBinaryName(arg.getEnumClassName(), factory),
+ arg.getEnumEntryName());
+ }
+
+ @Override
+ public void trace(DexDefinitionSupplier definitionSupplier) {
+ enumClassName.trace(definitionSupplier);
+ }
+
+ @Override
+ KmAnnotationArgument<?> rewrite(AppView<AppInfoWithLiveness> appView, NamingLens namingLens) {
+ return new EnumValue(
+ enumClassName.toRenamedBinaryNameOrDefault(appView, namingLens, ClassClassifiers.anyName),
+ enumEntryName);
+ }
+ }
+
+ private static class KotlinAnnotationAnnotationValueInfo extends KotlinAnnotationArgumentInfo {
+
+ private final KotlinAnnotationInfo value;
+
+ private KotlinAnnotationAnnotationValueInfo(KotlinAnnotationInfo value) {
+ this.value = value;
+ }
+
+ private static KotlinAnnotationAnnotationValueInfo create(
+ AnnotationValue arg, DexItemFactory factory) {
+ return new KotlinAnnotationAnnotationValueInfo(
+ KotlinAnnotationInfo.create(arg.getValue(), factory));
+ }
+
+ @Override
+ public void trace(DexDefinitionSupplier definitionSupplier) {
+ value.trace(definitionSupplier);
+ }
+
+ @Override
+ KmAnnotationArgument<?> rewrite(AppView<AppInfoWithLiveness> appView, NamingLens namingLens) {
+ Box<KmAnnotation> rewrittenAnnotation = new Box<>();
+ value.rewrite(rewrittenAnnotation::set, appView, namingLens);
+ if (rewrittenAnnotation.isSet()) {
+ return new AnnotationValue(rewrittenAnnotation.get());
+ }
+ return null;
+ }
+ }
+
+ private static class KotlinAnnotationArrayValueInfo extends KotlinAnnotationArgumentInfo {
+
+ private static final KotlinAnnotationArrayValueInfo EMPTY =
+ new KotlinAnnotationArrayValueInfo(ImmutableList.of());
+
+ private final List<KotlinAnnotationArgumentInfo> value;
+
+ private KotlinAnnotationArrayValueInfo(List<KotlinAnnotationArgumentInfo> value) {
+ this.value = value;
+ }
+
+ private static KotlinAnnotationArrayValueInfo create(ArrayValue arg, DexItemFactory factory) {
+ if (arg.getValue().isEmpty()) {
+ return EMPTY;
+ }
+ ImmutableList.Builder<KotlinAnnotationArgumentInfo> builder = ImmutableList.builder();
+ for (KmAnnotationArgument<?> argument : arg.getValue()) {
+ builder.add(createArgument(argument, factory));
+ }
+ return new KotlinAnnotationArrayValueInfo(builder.build());
+ }
+
+ @Override
+ public void trace(DexDefinitionSupplier definitionSupplier) {
+ for (KotlinAnnotationArgumentInfo kotlinAnnotationArgumentInfo : value) {
+ kotlinAnnotationArgumentInfo.trace(definitionSupplier);
+ }
+ }
+
+ @Override
+ KmAnnotationArgument<?> rewrite(AppView<AppInfoWithLiveness> appView, NamingLens namingLens) {
+ List<KmAnnotationArgument<?>> rewrittenArguments = new ArrayList<>();
+ for (KotlinAnnotationArgumentInfo kotlinAnnotationArgumentInfo : value) {
+ KmAnnotationArgument<?> rewrittenArg =
+ kotlinAnnotationArgumentInfo.rewrite(appView, namingLens);
+ if (rewrittenArg != null) {
+ rewrittenArguments.add(rewrittenArg);
+ }
+ }
+ return new ArrayValue(rewrittenArguments);
+ }
+ }
+
+ private static class KotlinAnnotationPrimitiveArgumentInfo extends KotlinAnnotationArgumentInfo {
+
+ private final KmAnnotationArgument<?> argument;
+
+ private KotlinAnnotationPrimitiveArgumentInfo(KmAnnotationArgument<?> argument) {
+ this.argument = argument;
+ }
+
+ private static KotlinAnnotationPrimitiveArgumentInfo create(KmAnnotationArgument<?> argument) {
+ return new KotlinAnnotationPrimitiveArgumentInfo(argument);
+ }
+
+ @Override
+ public void trace(DexDefinitionSupplier definitionSupplier) {
+ // Nothing to trace
+ }
+
+ @Override
+ KmAnnotationArgument<?> rewrite(AppView<AppInfoWithLiveness> appView, NamingLens namingLens) {
+ return argument;
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinAnnotationInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinAnnotationInfo.java
index 90ed44a..fd4d087 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinAnnotationInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinAnnotationInfo.java
@@ -12,6 +12,7 @@
import com.android.tools.r8.shaking.EnqueuerMetadataTraceable;
import com.android.tools.r8.utils.DescriptorUtils;
import com.google.common.collect.ImmutableList;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import kotlinx.metadata.KmAnnotation;
@@ -23,19 +24,18 @@
private static final List<KotlinAnnotationInfo> EMPTY_ANNOTATIONS = ImmutableList.of();
private final KotlinTypeReference annotationType;
- // TODO(b/155053894): Model KmAnnotationArgument.
- private final Map<String, KmAnnotationArgument<?>> arguments;
+ private final Map<String, KotlinAnnotationArgumentInfo> arguments;
private KotlinAnnotationInfo(
- KotlinTypeReference annotationType, Map<String, KmAnnotationArgument<?>> arguments) {
+ KotlinTypeReference annotationType, Map<String, KotlinAnnotationArgumentInfo> arguments) {
this.annotationType = annotationType;
this.arguments = arguments;
}
- private static KotlinAnnotationInfo create(KmAnnotation annotation, DexItemFactory factory) {
+ static KotlinAnnotationInfo create(KmAnnotation annotation, DexItemFactory factory) {
return new KotlinAnnotationInfo(
KotlinTypeReference.fromBinaryName(annotation.getClassName(), factory),
- annotation.getArguments());
+ KotlinAnnotationArgumentInfo.create(annotation.getArguments(), factory));
}
static List<KotlinAnnotationInfo> create(List<KmAnnotation> annotations, DexItemFactory factory) {
@@ -60,13 +60,20 @@
return;
}
String classifier = DescriptorUtils.descriptorToKotlinClassifier(renamedDescriptor);
- KmAnnotation annotation = new KmAnnotation(classifier, arguments);
- visitorProvider.get(annotation);
+ Map<String, KmAnnotationArgument<?>> rewrittenArguments = new LinkedHashMap<>();
+ arguments.forEach(
+ (key, arg) -> {
+ KmAnnotationArgument<?> rewrittenArg = arg.rewrite(appView, namingLens);
+ if (rewrittenArg != null) {
+ rewrittenArguments.put(key, rewrittenArg);
+ }
+ });
+ visitorProvider.get(new KmAnnotation(classifier, rewrittenArguments));
}
@Override
public void trace(DexDefinitionSupplier definitionSupplier) {
annotationType.trace(definitionSupplier);
- // TODO(b/155053894): Trace annotation arguments.
+ arguments.forEach((ignored, arg) -> arg.trace(definitionSupplier));
}
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java
index 9987457..c32bcc4 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java
@@ -27,6 +27,7 @@
import kotlinx.metadata.KmClass;
import kotlinx.metadata.KmConstructor;
import kotlinx.metadata.KmType;
+import kotlinx.metadata.jvm.JvmClassExtensionVisitor;
import kotlinx.metadata.jvm.JvmExtensionsKt;
import kotlinx.metadata.jvm.JvmMethodSignature;
import kotlinx.metadata.jvm.KotlinClassHeader;
@@ -48,6 +49,7 @@
private final KotlinVersionRequirementInfo versionRequirements;
private final KotlinTypeReference anonymousObjectOrigin;
private final String packageName;
+ private final KotlinLocalDelegatedPropertyInfo localDelegatedProperties;
private KotlinClassInfo(
int flags,
@@ -62,7 +64,8 @@
List<String> enumEntries,
KotlinVersionRequirementInfo versionRequirements,
KotlinTypeReference anonymousObjectOrigin,
- String packageName) {
+ String packageName,
+ KotlinLocalDelegatedPropertyInfo localDelegatedProperties) {
this.flags = flags;
this.name = name;
this.moduleName = moduleName;
@@ -76,6 +79,7 @@
this.versionRequirements = versionRequirements;
this.anonymousObjectOrigin = anonymousObjectOrigin;
this.packageName = packageName;
+ this.localDelegatedProperties = localDelegatedProperties;
}
public static KotlinClassInfo create(
@@ -125,7 +129,9 @@
kmClass.getEnumEntries(),
KotlinVersionRequirementInfo.create(kmClass.getVersionRequirements()),
getAnonymousObjectOrigin(kmClass, factory),
- packageName);
+ packageName,
+ KotlinLocalDelegatedPropertyInfo.create(
+ JvmExtensionsKt.getLocalDelegatedProperties(kmClass), factory, reporter));
}
private static KotlinTypeReference getAnonymousObjectOrigin(
@@ -266,15 +272,19 @@
// TODO(b/154347404): Understand enum entries.
kmClass.getEnumEntries().addAll(enumEntries);
versionRequirements.rewrite(kmClass::visitVersionRequirement);
- JvmExtensionsKt.setModuleName(kmClass, moduleName);
+ JvmClassExtensionVisitor extensionVisitor =
+ (JvmClassExtensionVisitor) kmClass.visitExtensions(JvmClassExtensionVisitor.TYPE);
+ extensionVisitor.visitModuleName(moduleName);
if (anonymousObjectOrigin != null) {
String renamedAnon =
anonymousObjectOrigin.toRenamedBinaryNameOrDefault(appView, namingLens, null);
if (renamedAnon != null) {
- JvmExtensionsKt.setAnonymousObjectOriginName(kmClass, renamedAnon);
+ extensionVisitor.visitAnonymousObjectOriginName(renamedAnon);
}
}
-
+ localDelegatedProperties.rewrite(
+ extensionVisitor::visitLocalDelegatedProperty, appView, namingLens);
+ extensionVisitor.visitEnd();
KotlinClassMetadata.Class.Writer writer = new KotlinClassMetadata.Class.Writer();
kmClass.accept(writer);
return writer.write().getHeader();
@@ -293,6 +303,7 @@
forEachApply(superTypes, type -> type::trace, definitionSupplier);
forEachApply(sealedSubClasses, sealed -> sealed::trace, definitionSupplier);
forEachApply(nestedClasses, nested -> nested::trace, definitionSupplier);
+ localDelegatedProperties.trace(definitionSupplier);
// TODO(b/154347404): trace enum entries.
if (anonymousObjectOrigin != null) {
anonymousObjectOrigin.trace(definitionSupplier);
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinContractInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinContractInfo.java
new file mode 100644
index 0000000..21fb43a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinContractInfo.java
@@ -0,0 +1,66 @@
+// Copyright (c) 2020, 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;
+
+import static com.android.tools.r8.utils.FunctionUtils.forEachApply;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexDefinitionSupplier;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.EnqueuerMetadataTraceable;
+import com.android.tools.r8.utils.Reporter;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import kotlinx.metadata.KmContract;
+import kotlinx.metadata.KmContractVisitor;
+import kotlinx.metadata.KmEffect;
+
+public class KotlinContractInfo implements EnqueuerMetadataTraceable {
+
+ private static final KotlinContractInfo NO_EFFECT = new KotlinContractInfo(ImmutableList.of());
+
+ private final List<KotlinEffectInfo> effects;
+
+ private KotlinContractInfo(List<KotlinEffectInfo> effects) {
+ this.effects = effects;
+ }
+
+ static KotlinContractInfo create(
+ KmContract kmContract, DexItemFactory factory, Reporter reporter) {
+ if (kmContract == null) {
+ return NO_EFFECT;
+ }
+ List<KmEffect> effects = kmContract.getEffects();
+ if (effects.isEmpty()) {
+ return NO_EFFECT;
+ }
+ ImmutableList.Builder<KotlinEffectInfo> builder = ImmutableList.builder();
+ for (KmEffect effect : effects) {
+ builder.add(KotlinEffectInfo.create(effect, factory, reporter));
+ }
+ return new KotlinContractInfo(builder.build());
+ }
+
+ @Override
+ public void trace(DexDefinitionSupplier definitionSupplier) {
+ forEachApply(effects, effect -> effect::trace, definitionSupplier);
+ }
+
+ public void rewrite(
+ KmVisitorProviders.KmContractVisitorProvider visitorProvider,
+ AppView<AppInfoWithLiveness> appView,
+ NamingLens namingLens) {
+ if (this == NO_EFFECT) {
+ return;
+ }
+ KmContractVisitor kmContractVisitor = visitorProvider.get();
+ for (KotlinEffectInfo effect : effects) {
+ effect.rewrite(kmContractVisitor::visitEffect, appView, namingLens);
+ }
+ kmContractVisitor.visitEnd();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinEffectExpressionInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinEffectExpressionInfo.java
new file mode 100644
index 0000000..d3ce846
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinEffectExpressionInfo.java
@@ -0,0 +1,112 @@
+// Copyright (c) 2020, 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;
+
+import static com.android.tools.r8.utils.FunctionUtils.forEachApply;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexDefinitionSupplier;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.kotlin.KmVisitorProviders.KmEffectExpressionVisitorProvider;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.EnqueuerMetadataTraceable;
+import com.android.tools.r8.utils.Reporter;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import kotlinx.metadata.KmConstantValue;
+import kotlinx.metadata.KmEffectExpression;
+import kotlinx.metadata.KmEffectExpressionVisitor;
+
+public class KotlinEffectExpressionInfo implements EnqueuerMetadataTraceable {
+
+ private static final List<KotlinEffectExpressionInfo> NO_EXPRESSIONS = ImmutableList.of();
+ private static final KotlinEffectExpressionInfo NO_EXPRESSION =
+ new KotlinEffectExpressionInfo(0, 0, null, null, NO_EXPRESSIONS, NO_EXPRESSIONS);
+
+ private final int flags;
+ private final Integer parameterIndex;
+ private final KmConstantValue constantValue;
+ private final KotlinTypeInfo isInstanceType;
+ private final List<KotlinEffectExpressionInfo> andArguments;
+ private final List<KotlinEffectExpressionInfo> orArguments;
+
+ private KotlinEffectExpressionInfo(
+ int flags,
+ Integer parameterIndex,
+ KmConstantValue constantValue,
+ KotlinTypeInfo isInstanceType,
+ List<KotlinEffectExpressionInfo> andArguments,
+ List<KotlinEffectExpressionInfo> orArguments) {
+ this.flags = flags;
+ this.parameterIndex = parameterIndex;
+ this.constantValue = constantValue;
+ this.isInstanceType = isInstanceType;
+ this.andArguments = andArguments;
+ this.orArguments = orArguments;
+ }
+
+ static KotlinEffectExpressionInfo create(
+ KmEffectExpression effectExpression, DexItemFactory factory, Reporter reporter) {
+ if (effectExpression == null) {
+ return NO_EXPRESSION;
+ }
+ return new KotlinEffectExpressionInfo(
+ effectExpression.getFlags(),
+ effectExpression.getParameterIndex(),
+ effectExpression.getConstantValue(),
+ KotlinTypeInfo.create(effectExpression.isInstanceType(), factory, reporter),
+ create(effectExpression.getAndArguments(), factory, reporter),
+ create(effectExpression.getOrArguments(), factory, reporter));
+ }
+
+ static List<KotlinEffectExpressionInfo> create(
+ List<KmEffectExpression> effectExpressions, DexItemFactory factory, Reporter reporter) {
+ if (effectExpressions.isEmpty()) {
+ return NO_EXPRESSIONS;
+ }
+ ImmutableList.Builder<KotlinEffectExpressionInfo> builder = ImmutableList.builder();
+ for (KmEffectExpression effectExpression : effectExpressions) {
+ builder.add(create(effectExpression, factory, reporter));
+ }
+ return builder.build();
+ }
+
+ @Override
+ public void trace(DexDefinitionSupplier definitionSupplier) {
+ if (this == NO_EXPRESSION) {
+ return;
+ }
+ if (isInstanceType != null) {
+ isInstanceType.trace(definitionSupplier);
+ }
+ forEachApply(andArguments, arg -> arg::trace, definitionSupplier);
+ forEachApply(orArguments, arg -> arg::trace, definitionSupplier);
+ }
+
+ public void rewrite(
+ KmEffectExpressionVisitorProvider provider,
+ AppView<AppInfoWithLiveness> appView,
+ NamingLens namingLens) {
+ if (this == NO_EXPRESSION) {
+ return;
+ }
+ KmEffectExpressionVisitor visitor = provider.get();
+ visitor.visit(flags, parameterIndex);
+ if (constantValue != null) {
+ visitor.visitConstantValue(constantValue.getValue());
+ }
+ if (isInstanceType != null) {
+ isInstanceType.rewrite(visitor::visitIsInstanceType, appView, namingLens);
+ }
+ for (KotlinEffectExpressionInfo andArgument : andArguments) {
+ andArgument.rewrite(visitor::visitAndArgument, appView, namingLens);
+ }
+ for (KotlinEffectExpressionInfo orArgument : orArguments) {
+ orArgument.rewrite(visitor::visitAndArgument, appView, namingLens);
+ }
+ visitor.visitEnd();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinEffectInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinEffectInfo.java
new file mode 100644
index 0000000..e38f158
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinEffectInfo.java
@@ -0,0 +1,66 @@
+// Copyright (c) 2020, 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;
+
+import static com.android.tools.r8.utils.FunctionUtils.forEachApply;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexDefinitionSupplier;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.kotlin.KmVisitorProviders.KmEffectVisitorProvider;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.EnqueuerMetadataTraceable;
+import com.android.tools.r8.utils.Reporter;
+import java.util.List;
+import kotlinx.metadata.KmEffect;
+import kotlinx.metadata.KmEffectInvocationKind;
+import kotlinx.metadata.KmEffectType;
+import kotlinx.metadata.KmEffectVisitor;
+
+public class KotlinEffectInfo implements EnqueuerMetadataTraceable {
+
+ private final KmEffectType type;
+ private final KmEffectInvocationKind invocationKind;
+ private final List<KotlinEffectExpressionInfo> constructorArguments;
+ private final KotlinEffectExpressionInfo conclusion;
+
+ public KotlinEffectInfo(
+ KmEffectType type,
+ KmEffectInvocationKind invocationKind,
+ List<KotlinEffectExpressionInfo> constructorArguments,
+ KotlinEffectExpressionInfo conclusion) {
+ this.type = type;
+ this.invocationKind = invocationKind;
+ this.constructorArguments = constructorArguments;
+ this.conclusion = conclusion;
+ }
+
+ static KotlinEffectInfo create(KmEffect effect, DexItemFactory factory, Reporter reporter) {
+ return new KotlinEffectInfo(
+ effect.getType(),
+ effect.getInvocationKind(),
+ KotlinEffectExpressionInfo.create(effect.getConstructorArguments(), factory, reporter),
+ KotlinEffectExpressionInfo.create(effect.getConclusion(), factory, reporter));
+ }
+
+ @Override
+ public void trace(DexDefinitionSupplier definitionSupplier) {
+ forEachApply(constructorArguments, arg -> arg::trace, definitionSupplier);
+ conclusion.trace(definitionSupplier);
+ }
+
+ void rewrite(
+ KmEffectVisitorProvider visitorProvider,
+ AppView<AppInfoWithLiveness> appView,
+ NamingLens namingLens) {
+ KmEffectVisitor kmEffectVisitor = visitorProvider.get(type, invocationKind);
+ conclusion.rewrite(kmEffectVisitor::visitConclusionOfConditionalEffect, appView, namingLens);
+ for (KotlinEffectExpressionInfo constructorArgument : constructorArguments) {
+ constructorArgument.rewrite(kmEffectVisitor::visitConstructorArgument, appView, namingLens);
+ }
+ kmEffectVisitor.visitEnd();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinFunctionInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinFunctionInfo.java
index d71d574..dfcbac5 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinFunctionInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinFunctionInfo.java
@@ -39,6 +39,8 @@
private final KotlinTypeReference lambdaClassOrigin;
// Information about version requirements.
private final KotlinVersionRequirementInfo versionRequirements;
+ // Kotlin contract information.
+ private final KotlinContractInfo contract;
// A value describing if any of the parameters are crossinline.
private final boolean crossInlineParameter;
@@ -52,6 +54,7 @@
KotlinJvmMethodSignatureInfo signature,
KotlinTypeReference lambdaClassOrigin,
KotlinVersionRequirementInfo versionRequirements,
+ KotlinContractInfo contract,
boolean crossInlineParameter) {
this.flags = flags;
this.name = name;
@@ -62,6 +65,7 @@
this.signature = signature;
this.lambdaClassOrigin = lambdaClassOrigin;
this.versionRequirements = versionRequirements;
+ this.contract = contract;
this.crossInlineParameter = crossInlineParameter;
}
@@ -90,6 +94,7 @@
KotlinJvmMethodSignatureInfo.create(JvmExtensionsKt.getSignature(kmFunction), factory),
getlambdaClassOrigin(kmFunction, factory),
KotlinVersionRequirementInfo.create(kmFunction.getVersionRequirements()),
+ KotlinContractInfo.create(kmFunction.getContract(), factory, reporter),
isCrossInline);
}
@@ -141,6 +146,7 @@
extensionVisitor.visitLambdaClassOriginName(lambdaClassOriginName);
}
}
+ contract.rewrite(kmFunction::visitContract, appView, namingLens);
}
@Override
@@ -175,5 +181,6 @@
if (lambdaClassOrigin != null) {
lambdaClassOrigin.trace(definitionSupplier);
}
+ contract.trace(definitionSupplier);
}
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinLocalDelegatedPropertyInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinLocalDelegatedPropertyInfo.java
new file mode 100644
index 0000000..cab770f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinLocalDelegatedPropertyInfo.java
@@ -0,0 +1,64 @@
+// Copyright (c) 2020, 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;
+
+import static com.android.tools.r8.utils.FunctionUtils.forEachApply;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexDefinitionSupplier;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.kotlin.KmVisitorProviders.KmPropertyVisitorProvider;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.EnqueuerMetadataTraceable;
+import com.android.tools.r8.utils.Reporter;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import kotlinx.metadata.KmProperty;
+
+public class KotlinLocalDelegatedPropertyInfo implements EnqueuerMetadataTraceable {
+
+ private static final KotlinLocalDelegatedPropertyInfo EMPTY_DELEGATED_PROPERTIES =
+ new KotlinLocalDelegatedPropertyInfo(ImmutableList.of());
+
+ private final List<KotlinPropertyInfo> propertyInfos;
+
+ private KotlinLocalDelegatedPropertyInfo(List<KotlinPropertyInfo> propertyInfos) {
+ this.propertyInfos = propertyInfos;
+ }
+
+ static KotlinLocalDelegatedPropertyInfo create(
+ List<KmProperty> kmProperties, DexItemFactory factory, Reporter reporter) {
+ if (kmProperties == null || kmProperties.size() == 0) {
+ return EMPTY_DELEGATED_PROPERTIES;
+ }
+ ImmutableList.Builder<KotlinPropertyInfo> builder = ImmutableList.builder();
+ for (KmProperty kmProperty : kmProperties) {
+ KotlinPropertyInfo kotlinPropertyInfo =
+ KotlinPropertyInfo.create(kmProperty, factory, reporter);
+ // For ordinary properties, we place these on the fields and methods, but these are hooked in,
+ // and do not have any jvm signatures:
+ assert kotlinPropertyInfo.getFieldSignature() == null;
+ assert kotlinPropertyInfo.getGetterSignature() == null;
+ assert kotlinPropertyInfo.getSetterSignature() == null;
+ builder.add(kotlinPropertyInfo);
+ }
+ return new KotlinLocalDelegatedPropertyInfo(builder.build());
+ }
+
+ @Override
+ public void trace(DexDefinitionSupplier definitionSupplier) {
+ forEachApply(propertyInfos, prop -> prop::trace, definitionSupplier);
+ }
+
+ public void rewrite(
+ KmPropertyVisitorProvider visitorProvider,
+ AppView<AppInfoWithLiveness> appView,
+ NamingLens namingLens) {
+ for (KotlinPropertyInfo propertyInfo : propertyInfos) {
+ propertyInfo.rewrite(visitorProvider, null, null, null, appView, namingLens);
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java
index ba814e9..680bd5c 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java
@@ -121,11 +121,6 @@
}
@Override
- public DexEncodedMethod definitionFor(DexMethod method) {
- throw new Unreachable("Should not be called");
- }
-
- @Override
public DexClass definitionFor(DexType type) {
// TODO(b/157700128) Metadata cannot at this point keep anything alive. Therefore, if a type
// has been pruned it may still be referenced, so we do an early check here to ensure it will
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
index ce0ba80..dfddc1d 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
@@ -83,27 +83,33 @@
KotlinClassHeader header, String packageName) {
List<DexAnnotationElement> elements = new ArrayList<>();
elements.add(
- new DexAnnotationElement(kotlin.metadata.kind, DexValueInt.create(header.getKind())));
- elements.add(
new DexAnnotationElement(
kotlin.metadata.metadataVersion, createIntArray(header.getMetadataVersion())));
elements.add(
new DexAnnotationElement(
kotlin.metadata.bytecodeVersion, createIntArray(header.getBytecodeVersion())));
elements.add(
+ new DexAnnotationElement(kotlin.metadata.kind, DexValueInt.create(header.getKind())));
+ elements.add(
new DexAnnotationElement(kotlin.metadata.data1, createStringArray(header.getData1())));
elements.add(
new DexAnnotationElement(kotlin.metadata.data2, createStringArray(header.getData2())));
- elements.add(
- new DexAnnotationElement(
- kotlin.metadata.extraString,
- new DexValueString(factory.createString(header.getExtraString()))));
- elements.add(
- new DexAnnotationElement(
- kotlin.metadata.packageName, new DexValueString(factory.createString(packageName))));
- elements.add(
- new DexAnnotationElement(
- kotlin.metadata.extraInt, DexValueInt.create(header.getExtraInt())));
+ if (packageName != null && !packageName.isEmpty()) {
+ elements.add(
+ new DexAnnotationElement(
+ kotlin.metadata.packageName, new DexValueString(factory.createString(packageName))));
+ }
+ if (!header.getExtraString().isEmpty()) {
+ elements.add(
+ new DexAnnotationElement(
+ kotlin.metadata.extraString,
+ new DexValueString(factory.createString(header.getExtraString()))));
+ }
+ if (header.getExtraInt() != 0) {
+ elements.add(
+ new DexAnnotationElement(
+ kotlin.metadata.extraInt, DexValueInt.create(header.getExtraInt())));
+ }
DexEncodedAnnotation encodedAnnotation =
new DexEncodedAnnotation(
factory.kotlinMetadataType, elements.toArray(DexAnnotationElement.EMPTY_ARRAY));
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataWriter.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataWriter.java
index bc124c6..1a51644 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataWriter.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataWriter.java
@@ -11,6 +11,7 @@
import com.android.tools.r8.utils.Action;
import com.android.tools.r8.utils.StringUtils;
import java.io.PrintStream;
+import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
@@ -20,9 +21,13 @@
import kotlinx.metadata.InconsistentKotlinMetadataException;
import kotlinx.metadata.KmAnnotation;
import kotlinx.metadata.KmAnnotationArgument;
+import kotlinx.metadata.KmAnnotationArgument.ArrayValue;
import kotlinx.metadata.KmClass;
import kotlinx.metadata.KmConstructor;
+import kotlinx.metadata.KmContract;
import kotlinx.metadata.KmDeclarationContainer;
+import kotlinx.metadata.KmEffect;
+import kotlinx.metadata.KmEffectExpression;
import kotlinx.metadata.KmFlexibleTypeUpperBound;
import kotlinx.metadata.KmFunction;
import kotlinx.metadata.KmLambda;
@@ -174,7 +179,7 @@
String indent,
String typeDescription,
StringBuilder sb,
- List<T> items,
+ Collection<T> items,
BiConsumer<String, T> appendItem) {
if (items.isEmpty()) {
sb.append(typeDescription).append("[]");
@@ -424,6 +429,18 @@
sb,
nextIndent -> appendValueParameters(nextIndent, sb, function.getValueParameters()));
appendKmVersionRequirement(newIndent, sb, function.getVersionRequirements());
+ KmContract contract = function.getContract();
+ if (contract == null) {
+ appendKeyValue(newIndent, "contract", sb, "null");
+ } else {
+ appendKeyValue(
+ newIndent,
+ "contract",
+ sb,
+ nextIndent -> {
+ appendKmContract(nextIndent, sb, contract);
+ });
+ }
JvmMethodSignature signature = JvmExtensionsKt.getSignature(function);
appendKeyValue(
newIndent, "signature", sb, signature != null ? signature.asString() : "null");
@@ -753,13 +770,47 @@
sb,
nextIndent -> {
Map<String, KmAnnotationArgument<?>> arguments = kmAnnotation.getArguments();
- for (String key : arguments.keySet()) {
- appendKeyValue(nextIndent, key, sb, arguments.get(key).toString());
- }
+ appendKmList(
+ nextIndent,
+ "{ key: String, value: KmAnnotationArgument<?> }",
+ sb,
+ arguments.keySet(),
+ (nextNextIndent, key) -> {
+ appendKmSection(
+ nextNextIndent,
+ "",
+ sb,
+ nextNextNextIndent -> {
+ appendKeyValue(
+ nextNextNextIndent,
+ key,
+ sb,
+ nextNextNextNextIndent -> {
+ appendKmArgument(nextNextNextIndent, sb, arguments.get(key));
+ });
+ });
+ });
});
});
}
+ private static void appendKmArgument(
+ String indent, StringBuilder sb, KmAnnotationArgument<?> annotationArgument) {
+ if (annotationArgument instanceof KmAnnotationArgument.ArrayValue) {
+ List<KmAnnotationArgument<?>> value = ((ArrayValue) annotationArgument).getValue();
+ appendKmList(
+ indent,
+ "ArrayValue",
+ sb,
+ value,
+ (newIndent, annoArg) -> {
+ appendKmArgument(newIndent, sb, annoArg);
+ });
+ } else {
+ sb.append(annotationArgument.toString());
+ }
+ }
+
private static void appendKmVersionRequirement(
String indent, StringBuilder sb, List<KmVersionRequirement> kmVersionRequirements) {
appendKeyValue(
@@ -799,4 +850,123 @@
});
});
}
+
+ private static void appendKmContract(String indent, StringBuilder sb, KmContract contract) {
+ appendKmSection(
+ indent,
+ "KmContract",
+ sb,
+ newIndent -> {
+ appendKeyValue(
+ newIndent,
+ "effects",
+ sb,
+ nextIndent ->
+ appendKmList(
+ nextIndent,
+ "KmEffect",
+ sb,
+ contract.getEffects(),
+ (nextNextIndent, effect) -> appendKmEffect(nextNextIndent, sb, effect)));
+ });
+ }
+
+ private static void appendKmEffect(String indent, StringBuilder sb, KmEffect effect) {
+ appendKmSection(
+ indent,
+ "KmEffect",
+ sb,
+ newIndent -> {
+ appendKeyValue(newIndent, "type", sb, effect.getType().toString());
+ appendKeyValue(
+ newIndent,
+ "invocationKind",
+ sb,
+ effect.getInvocationKind() == null ? "null" : effect.getInvocationKind().toString());
+ appendKeyValue(
+ newIndent,
+ "constructorArguments",
+ sb,
+ nextIndent -> {
+ appendKmList(
+ nextIndent,
+ "KmEffectExpression",
+ sb,
+ effect.getConstructorArguments(),
+ (nextNextIndent, expression) -> {
+ appendKmEffectExpression(nextNextIndent, sb, expression);
+ });
+ });
+ KmEffectExpression conclusion = effect.getConclusion();
+ if (conclusion == null) {
+ appendKeyValue(newIndent, "conclusion", sb, "null");
+ } else {
+ appendKeyValue(
+ newIndent,
+ "conclusion",
+ sb,
+ nextIndent -> appendKmEffectExpression(nextIndent, sb, conclusion));
+ }
+ });
+ }
+
+ private static void appendKmEffectExpression(
+ String indent, StringBuilder sb, KmEffectExpression expression) {
+ appendKmSection(
+ indent,
+ "KmEffectExpression",
+ sb,
+ newIndent -> {
+ appendKeyValue(newIndent, "flags", sb, expression.getFlags() + "");
+ appendKeyValue(
+ newIndent,
+ "foo",
+ sb,
+ expression.getParameterIndex() == null
+ ? "null"
+ : expression.getParameterIndex() + "");
+ appendKeyValue(
+ newIndent,
+ "constantValue",
+ sb,
+ expression.getConstantValue() == null
+ ? "null"
+ : expression.getConstantValue().toString());
+ appendKeyValue(
+ newIndent,
+ "isInstanceType",
+ sb,
+ nextIndent -> {
+ appendKmType(nextIndent, sb, expression.isInstanceType());
+ });
+ appendKeyValue(
+ newIndent,
+ "andArguments",
+ sb,
+ nextIndent -> {
+ appendKmList(
+ nextIndent,
+ "KmEffectExpression",
+ sb,
+ expression.getAndArguments(),
+ (nextNextIndent, expr) -> {
+ appendKmEffectExpression(nextNextIndent, sb, expr);
+ });
+ });
+ appendKeyValue(
+ newIndent,
+ "orArguments",
+ sb,
+ nextIndent -> {
+ appendKmList(
+ nextIndent,
+ "KmEffectExpression",
+ sb,
+ expression.getOrArguments(),
+ (nextNextIndent, expr) -> {
+ appendKmEffectExpression(nextNextIndent, sb, expr);
+ });
+ });
+ });
+ }
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinPackageInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinPackageInfo.java
index 8f0268a..3fab975 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinPackageInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinPackageInfo.java
@@ -22,16 +22,22 @@
import java.util.function.Consumer;
import kotlinx.metadata.KmPackage;
import kotlinx.metadata.jvm.JvmExtensionsKt;
+import kotlinx.metadata.jvm.JvmPackageExtensionVisitor;
// Holds information about a KmPackage object.
public class KotlinPackageInfo implements EnqueuerMetadataTraceable {
private final String moduleName;
private final KotlinDeclarationContainerInfo containerInfo;
+ private final KotlinLocalDelegatedPropertyInfo localDelegatedProperties;
- private KotlinPackageInfo(String moduleName, KotlinDeclarationContainerInfo containerInfo) {
+ private KotlinPackageInfo(
+ String moduleName,
+ KotlinDeclarationContainerInfo containerInfo,
+ KotlinLocalDelegatedPropertyInfo localDelegatedProperties) {
this.moduleName = moduleName;
this.containerInfo = containerInfo;
+ this.localDelegatedProperties = localDelegatedProperties;
}
public static KotlinPackageInfo create(
@@ -51,7 +57,9 @@
return new KotlinPackageInfo(
JvmExtensionsKt.getModuleName(kmPackage),
KotlinDeclarationContainerInfo.create(
- kmPackage, methodMap, fieldMap, factory, reporter, keepByteCode));
+ kmPackage, methodMap, fieldMap, factory, reporter, keepByteCode),
+ KotlinLocalDelegatedPropertyInfo.create(
+ JvmExtensionsKt.getLocalDelegatedProperties(kmPackage), factory, reporter));
}
public void rewrite(
@@ -66,11 +74,17 @@
clazz,
appView,
namingLens);
- JvmExtensionsKt.setModuleName(kmPackage, moduleName);
+ JvmPackageExtensionVisitor extensionVisitor =
+ (JvmPackageExtensionVisitor) kmPackage.visitExtensions(JvmPackageExtensionVisitor.TYPE);
+ localDelegatedProperties.rewrite(
+ extensionVisitor::visitLocalDelegatedProperty, appView, namingLens);
+ extensionVisitor.visitModuleName(moduleName);
+ extensionVisitor.visitEnd();
}
@Override
public void trace(DexDefinitionSupplier definitionSupplier) {
containerInfo.trace(definitionSupplier);
+ localDelegatedProperties.trace(definitionSupplier);
}
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinPropertyInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinPropertyInfo.java
index 4d14c67..c718992 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinPropertyInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinPropertyInfo.java
@@ -129,6 +129,18 @@
return this;
}
+ public KotlinJvmFieldSignatureInfo getFieldSignature() {
+ return fieldSignature;
+ }
+
+ public KotlinJvmMethodSignatureInfo getGetterSignature() {
+ return getterSignature;
+ }
+
+ public KotlinJvmMethodSignatureInfo getSetterSignature() {
+ return setterSignature;
+ }
+
void rewrite(
KmVisitorProviders.KmPropertyVisitorProvider visitorProvider,
DexEncodedField field,
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
index 0c223b1..9b659a0 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
@@ -17,13 +17,11 @@
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.InnerClassAttribute;
-import com.android.tools.r8.naming.signature.GenericSignatureRewriter;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.shaking.ProguardPackageNameList;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.InternalOptions.PackageObfuscationMode;
-import com.android.tools.r8.utils.IteratorUtils;
import com.android.tools.r8.utils.Timing;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
@@ -146,13 +144,6 @@
}
timing.end();
- timing.begin("rename-generic");
- new GenericSignatureRewriter(appView, renaming)
- .run(
- () -> IteratorUtils.filter(classes.iterator(), DexClass::isProgramClass),
- executorService);
- timing.end();
-
timing.begin("rename-arrays");
appView.dexItemFactory().forAllTypes(this::renameArrayTypeIfNeeded);
timing.end();
diff --git a/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java b/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java
index bbefa18..060f171 100644
--- a/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java
@@ -20,6 +20,7 @@
import com.google.common.base.Equivalence;
import com.google.common.base.Equivalence.Wrapper;
import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Collection;
@@ -110,16 +111,13 @@
this.iface = iface;
}
- DexString getReservedName(DexMethod method) {
+ DexString getReservedName(DexEncodedMethod method) {
// If an interface is kept and we are using applymapping, the renamed name for this method
// is tracked on this level.
if (appView.options().getProguardConfiguration().hasApplyMappingFile()) {
- DexEncodedMethod encodedMethod = appView.definitionFor(method);
- if (encodedMethod != null) {
- DexString reservedName = minifierState.getReservedName(encodedMethod, iface);
- if (reservedName != null) {
- return reservedName;
- }
+ DexString reservedName = minifierState.getReservedName(method, iface);
+ if (reservedName != null) {
+ return reservedName;
}
}
// Otherwise, we just search the hierarchy for the first identity reservation since
@@ -131,18 +129,18 @@
Set<DexString> reservedNamesFor =
minifierState
.getReservationState(reservationType)
- .getReservedNamesFor(method);
+ .getReservedNamesFor(method.getReference());
assert reservedNamesFor == null || !reservedNamesFor.isEmpty();
- if (reservedNamesFor != null && reservedNamesFor.contains(method.name)) {
+ if (reservedNamesFor != null && reservedNamesFor.contains(method.getName())) {
return true;
}
}
return null;
});
- return isReserved == null ? null : method.name;
+ return isReserved == null ? null : method.getName();
}
- void reserveName(DexString reservedName, DexMethod method) {
+ void reserveName(DexString reservedName, DexEncodedMethod method) {
forAll(
s -> {
s.reservationTypes.forEach(
@@ -153,13 +151,13 @@
});
}
- boolean isAvailable(DexString candidate, DexMethod method) {
+ boolean isAvailable(DexString candidate, DexEncodedMethod method) {
Boolean result =
forAny(
s -> {
for (DexType resType : s.reservationTypes) {
MethodNamingState<?> state = minifierState.getNamingState(resType);
- if (!state.isAvailable(candidate, method)) {
+ if (!state.isAvailable(candidate, method.getReference())) {
return false;
}
}
@@ -168,15 +166,11 @@
return result == null ? true : result;
}
- void addRenaming(DexString newName, DexMethod method) {
+ void addRenaming(DexString newName, DexEncodedMethod method) {
forAll(
- s -> {
- s.reservationTypes.forEach(
- resType -> {
- MethodNamingState<?> state = minifierState.getNamingState(resType);
- state.addRenaming(newName, method);
- });
- });
+ s ->
+ s.reservationTypes.forEach(
+ resType -> minifierState.getNamingState(resType).addRenaming(newName, method)));
}
<T> void forAll(Consumer<InterfaceReservationState> action) {
@@ -241,17 +235,18 @@
class InterfaceMethodGroupState implements Comparable<InterfaceMethodGroupState> {
private final Set<DexCallSite> callSites = new HashSet<>();
- private final Map<DexMethod, Set<InterfaceReservationState>> methodStates = new HashMap<>();
- private final List<DexMethod> callSiteCollidingMethods = new ArrayList<>();
+ private final Map<DexEncodedMethod, Set<InterfaceReservationState>> methodStates =
+ new HashMap<>();
+ private final List<DexEncodedMethod> callSiteCollidingMethods = new ArrayList<>();
- void addState(DexMethod method, InterfaceReservationState interfaceState) {
+ void addState(DexEncodedMethod method, InterfaceReservationState interfaceState) {
methodStates.computeIfAbsent(method, m -> new HashSet<>()).add(interfaceState);
}
void appendMethodGroupState(InterfaceMethodGroupState state) {
callSites.addAll(state.callSites);
callSiteCollidingMethods.addAll(state.callSiteCollidingMethods);
- for (DexMethod key : state.methodStates.keySet()) {
+ for (DexEncodedMethod key : state.methodStates.keySet()) {
methodStates.computeIfAbsent(key, k -> new HashSet<>()).addAll(state.methodStates.get(key));
}
}
@@ -269,14 +264,14 @@
// It is perfectly fine to have multiple reserved names inside a group. If we have an identity
// reservation, we have to prioritize that over the others, otherwise we just propose the
// first ordered reserved name since we do not allow overwriting the name.
- List<DexMethod> sortedMethods = Lists.newArrayList(methodStates.keySet());
- sortedMethods.sort(DexMethod::slowCompareTo);
+ List<DexEncodedMethod> sortedMethods = Lists.newArrayList(methodStates.keySet());
+ sortedMethods.sort((x, y) -> x.getReference().slowCompareTo(y.getReference()));
DexString reservedName = null;
- for (DexMethod method : sortedMethods) {
+ for (DexEncodedMethod method : sortedMethods) {
for (InterfaceReservationState state : methodStates.get(method)) {
DexString stateReserved = state.getReservedName(method);
- if (stateReserved == method.name) {
- return method.name;
+ if (stateReserved == method.getName()) {
+ return method.getName();
} else if (stateReserved != null) {
reservedName = stateReserved;
}
@@ -321,7 +316,7 @@
});
}
- void forEachState(BiConsumer<DexMethod, InterfaceReservationState> action) {
+ void forEachState(BiConsumer<DexEncodedMethod, InterfaceReservationState> action) {
forAnyState(
(s, i) -> {
action.accept(s, i);
@@ -329,9 +324,10 @@
});
}
- <T> T forAnyState(BiFunction<DexMethod, InterfaceReservationState, T> callback) {
+ <T> T forAnyState(BiFunction<DexEncodedMethod, InterfaceReservationState, T> callback) {
T returnValue;
- for (Map.Entry<DexMethod, Set<InterfaceReservationState>> entry : methodStates.entrySet()) {
+ for (Map.Entry<DexEncodedMethod, Set<InterfaceReservationState>> entry :
+ methodStates.entrySet()) {
for (InterfaceReservationState state : entry.getValue()) {
returnValue = callback.apply(entry.getKey(), state);
if (returnValue != null) {
@@ -342,7 +338,7 @@
return null;
}
- boolean containsReservation(DexMethod method, DexType reservationType) {
+ boolean containsReservation(DexEncodedMethod method, DexType reservationType) {
Set<InterfaceReservationState> states = methodStates.get(method);
if (states != null) {
for (InterfaceReservationState state : states) {
@@ -363,12 +359,14 @@
private final AppView<AppInfoWithLiveness> appView;
private final Equivalence<DexMethod> equivalence;
+ private final Equivalence<DexEncodedMethod> definitionEquivalence;
private final MethodNameMinifier.State minifierState;
private final Map<DexCallSite, DexString> callSiteRenamings = new IdentityHashMap<>();
/** A map from DexMethods to all the states linked to interfaces they appear in. */
- private final Map<Wrapper<DexMethod>, InterfaceMethodGroupState> globalStateMap = new HashMap<>();
+ private final Map<Wrapper<DexEncodedMethod>, InterfaceMethodGroupState> globalStateMap =
+ new HashMap<>();
/** A map for caching all interface states. */
private final Map<DexType, InterfaceReservationState> interfaceStateMap = new HashMap<>();
@@ -380,9 +378,21 @@
appView.options().getProguardConfiguration().isOverloadAggressively()
? MethodSignatureEquivalence.get()
: MethodJavaSignatureEquivalence.get();
+ this.definitionEquivalence =
+ new Equivalence<DexEncodedMethod>() {
+ @Override
+ protected boolean doEquivalent(DexEncodedMethod method, DexEncodedMethod other) {
+ return equivalence.equivalent(method.getReference(), other.getReference());
+ }
+
+ @Override
+ protected int doHash(DexEncodedMethod method) {
+ return equivalence.hash(method.getReference());
+ }
+ };
}
- private Comparator<Wrapper<DexMethod>> getDefaultInterfaceMethodOrdering() {
+ private Comparator<Wrapper<DexEncodedMethod>> getDefaultInterfaceMethodOrdering() {
return Comparator.comparing(globalStateMap::get);
}
@@ -419,10 +429,10 @@
InterfaceReservationState inheritanceState = interfaceStateMap.get(iface.type);
assert inheritanceState != null;
for (DexEncodedMethod method : iface.methods()) {
- Wrapper<DexMethod> key = equivalence.wrap(method.method);
+ Wrapper<DexEncodedMethod> key = definitionEquivalence.wrap(method);
globalStateMap
.computeIfAbsent(key, k -> new InterfaceMethodGroupState())
- .addState(method.method, inheritanceState);
+ .addState(method, inheritanceState);
}
}
timing.end();
@@ -439,11 +449,11 @@
// Note that if the input does not use multi-interface lambdas unificationParent will remain
// empty.
timing.begin("Union-find");
- DisjointSets<Wrapper<DexMethod>> unification = new DisjointSets<>();
+ DisjointSets<Wrapper<DexEncodedMethod>> unification = new DisjointSets<>();
liveCallSites.forEach(
callSite -> {
- Set<Wrapper<DexMethod>> callSiteMethods = new HashSet<>();
+ Set<Wrapper<DexEncodedMethod>> callSiteMethods = new HashSet<>();
// Don't report errors, as the set of call sites is a conservative estimate, and can
// refer to interfaces which has been removed.
Set<DexEncodedMethod> implementedMethods =
@@ -452,7 +462,7 @@
return;
}
for (DexEncodedMethod method : implementedMethods) {
- Wrapper<DexMethod> wrapped = equivalence.wrap(method.method);
+ Wrapper<DexEncodedMethod> wrapped = definitionEquivalence.wrap(method);
InterfaceMethodGroupState groupState = globalStateMap.get(wrapped);
assert groupState != null : wrapped;
groupState.addCallSite(callSite);
@@ -474,16 +484,15 @@
assert iface.isInterface();
for (DexEncodedMethod implementedMethod : implementedMethods) {
for (DexEncodedMethod virtualMethod : iface.virtualMethods()) {
- boolean differentName =
- !implementedMethod.method.name.equals(virtualMethod.method.name);
+ boolean differentName = implementedMethod.getName() != virtualMethod.getName();
if (differentName
&& MethodJavaSignatureEquivalence.getEquivalenceIgnoreName()
.equivalent(implementedMethod.method, virtualMethod.method)) {
InterfaceMethodGroupState interfaceMethodGroupState =
globalStateMap.computeIfAbsent(
- equivalence.wrap(implementedMethod.method),
+ definitionEquivalence.wrap(implementedMethod),
k -> new InterfaceMethodGroupState());
- interfaceMethodGroupState.callSiteCollidingMethods.add(virtualMethod.method);
+ interfaceMethodGroupState.callSiteCollidingMethods.add(virtualMethod);
}
}
}
@@ -491,9 +500,9 @@
}
if (callSiteMethods.size() > 1) {
// Implemented interfaces have different protos. Unify them.
- Wrapper<DexMethod> mainKey = callSiteMethods.iterator().next();
- Wrapper<DexMethod> representative = unification.findOrMakeSet(mainKey);
- for (Wrapper<DexMethod> key : callSiteMethods) {
+ Wrapper<DexEncodedMethod> mainKey = callSiteMethods.iterator().next();
+ Wrapper<DexEncodedMethod> representative = unification.findOrMakeSet(mainKey);
+ for (Wrapper<DexEncodedMethod> key : callSiteMethods) {
unification.unionWithMakeSet(representative, key);
}
}
@@ -504,14 +513,15 @@
// We now have roots for all unions. Add all of the states for the groups to the method state
// for the unions to allow consistent naming across different protos.
timing.begin("States for union");
- Map<Wrapper<DexMethod>, Set<Wrapper<DexMethod>>> unions = unification.collectSets();
+ Map<Wrapper<DexEncodedMethod>, Set<Wrapper<DexEncodedMethod>>> unions =
+ unification.collectSets();
- for (Wrapper<DexMethod> wrapped : unions.keySet()) {
+ for (Wrapper<DexEncodedMethod> wrapped : unions.keySet()) {
InterfaceMethodGroupState groupState = globalStateMap.get(wrapped);
assert groupState != null;
- for (Wrapper<DexMethod> groupedMethod : unions.get(wrapped)) {
- DexMethod method = groupedMethod.get();
+ for (Wrapper<DexEncodedMethod> groupedMethod : unions.get(wrapped)) {
+ DexEncodedMethod method = groupedMethod.get();
assert method != null;
groupState.appendMethodGroupState(globalStateMap.get(groupedMethod));
}
@@ -522,7 +532,7 @@
// Filter out the groups that is included both in the unification and in the map. We sort the
// methods by the number of dependent states, so that we use short names for method that are
// referenced in many places.
- List<Wrapper<DexMethod>> interfaceMethodGroups =
+ List<Wrapper<DexEncodedMethod>> interfaceMethodGroups =
globalStateMap.keySet().stream()
.filter(unification::isRepresentativeOrNotPresent)
.sorted(
@@ -540,8 +550,8 @@
timing.begin("Reserve in groups");
// It is important that this entire phase is run before given new names, to ensure all
// reservations are propagated to all naming states.
- List<Wrapper<DexMethod>> nonReservedMethodGroups = new ArrayList<>();
- for (Wrapper<DexMethod> interfaceMethodGroup : interfaceMethodGroups) {
+ List<Wrapper<DexEncodedMethod>> nonReservedMethodGroups = new ArrayList<>();
+ for (Wrapper<DexEncodedMethod> interfaceMethodGroup : interfaceMethodGroups) {
InterfaceMethodGroupState groupState = globalStateMap.get(interfaceMethodGroup);
assert groupState != null;
DexString reservedName = groupState.getReservedName();
@@ -559,7 +569,7 @@
timing.end();
timing.begin("Rename in groups");
- for (Wrapper<DexMethod> interfaceMethodGroup : nonReservedMethodGroups) {
+ for (Wrapper<DexEncodedMethod> interfaceMethodGroup : nonReservedMethodGroups) {
InterfaceMethodGroupState groupState = globalStateMap.get(interfaceMethodGroup);
assert groupState != null;
assert groupState.getReservedName() == null;
@@ -567,11 +577,11 @@
assert newName != null;
Set<String> loggingFilter = appView.options().extensiveInterfaceMethodMinifierLoggingFilter;
if (!loggingFilter.isEmpty()) {
- Set<DexMethod> sourceMethods = groupState.methodStates.keySet();
+ Set<DexEncodedMethod> sourceMethods = groupState.methodStates.keySet();
if (sourceMethods.stream()
- .map(DexMethod::toSourceString)
+ .map(DexEncodedMethod::toSourceString)
.anyMatch(loggingFilter::contains)) {
- print(interfaceMethodGroup.get(), sourceMethods, System.out);
+ print(interfaceMethodGroup.get().getReference(), sourceMethods, System.out);
}
}
for (DexCallSite callSite : groupState.callSites) {
@@ -582,20 +592,20 @@
// After all naming is completed for callsites, we must ensure to rename all interface methods
// that can collide with the callsite method name.
- for (Wrapper<DexMethod> interfaceMethodGroup : nonReservedMethodGroups) {
+ for (Wrapper<DexEncodedMethod> interfaceMethodGroup : nonReservedMethodGroups) {
InterfaceMethodGroupState groupState = globalStateMap.get(interfaceMethodGroup);
if (groupState.callSiteCollidingMethods.isEmpty()) {
continue;
}
- DexMethod key = interfaceMethodGroup.get();
- MethodNamingState<?> keyNamingState = minifierState.getNamingState(key.holder);
+ DexEncodedMethod key = interfaceMethodGroup.get();
+ MethodNamingState<?> keyNamingState = minifierState.getNamingState(key.getHolderType());
DexString existingRenaming = keyNamingState.newOrReservedNameFor(key);
assert existingRenaming != null;
- for (DexMethod collidingMethod : groupState.callSiteCollidingMethods) {
+ for (DexEncodedMethod collidingMethod : groupState.callSiteCollidingMethods) {
DexString newNameInGroup = newNameInGroup(collidingMethod, keyNamingState, groupState);
minifierState.putRenaming(collidingMethod, newNameInGroup);
MethodNamingState<?> methodNamingState =
- minifierState.getNamingState(collidingMethod.holder);
+ minifierState.getNamingState(collidingMethod.getReference().holder);
methodNamingState.addRenaming(newNameInGroup, collidingMethod);
keyNamingState.addRenaming(newNameInGroup, collidingMethod);
}
@@ -605,11 +615,11 @@
timing.end(); // end compute timing
}
- private DexString assignNewName(DexMethod method, InterfaceMethodGroupState groupState) {
+ private DexString assignNewName(DexEncodedMethod method, InterfaceMethodGroupState groupState) {
assert groupState.getReservedName() == null;
assert groupState.methodStates.containsKey(method);
- assert groupState.containsReservation(method, method.holder);
- MethodNamingState<?> namingState = minifierState.getNamingState(method.holder);
+ assert groupState.containsReservation(method, method.getHolderType());
+ MethodNamingState<?> namingState = minifierState.getNamingState(method.getHolderType());
// Check if the name is available in all states.
DexString newName =
namingState.newOrReservedNameFor(
@@ -619,7 +629,9 @@
}
private DexString newNameInGroup(
- DexMethod method, MethodNamingState<?> namingState, InterfaceMethodGroupState groupState) {
+ DexEncodedMethod method,
+ MethodNamingState<?> namingState,
+ InterfaceMethodGroupState groupState) {
// Check if the name is available in all states.
return namingState.nextName(method, (candidate, ignore) -> groupState.isAvailable(candidate));
}
@@ -655,11 +667,11 @@
});
}
- private boolean verifyAllCallSitesAreRepresentedIn(List<Wrapper<DexMethod>> groups) {
- Set<Wrapper<DexMethod>> unifiedMethods = new HashSet<>(groups);
+ private boolean verifyAllCallSitesAreRepresentedIn(List<Wrapper<DexEncodedMethod>> groups) {
+ Set<Wrapper<DexEncodedMethod>> unifiedMethods = new HashSet<>(groups);
Set<DexCallSite> unifiedSeen = new HashSet<>();
Set<DexCallSite> seen = new HashSet<>();
- for (Map.Entry<Wrapper<DexMethod>, InterfaceMethodGroupState> state :
+ for (Map.Entry<Wrapper<DexEncodedMethod>, InterfaceMethodGroupState> state :
globalStateMap.entrySet()) {
for (DexCallSite callSite : state.getValue().callSites) {
seen.add(callSite);
@@ -674,13 +686,13 @@
return true;
}
- private boolean verifyAllMethodsAreRepresentedIn(List<Wrapper<DexMethod>> groups) {
- Set<Wrapper<DexMethod>> unifiedMethods = new HashSet<>(groups);
- Set<DexMethod> unifiedSeen = new HashSet<>();
- Set<DexMethod> seen = new HashSet<>();
- for (Map.Entry<Wrapper<DexMethod>, InterfaceMethodGroupState> state :
+ private boolean verifyAllMethodsAreRepresentedIn(List<Wrapper<DexEncodedMethod>> groups) {
+ Set<Wrapper<DexEncodedMethod>> unifiedMethods = new HashSet<>(groups);
+ Set<DexEncodedMethod> unifiedSeen = Sets.newIdentityHashSet();
+ Set<DexEncodedMethod> seen = Sets.newIdentityHashSet();
+ for (Map.Entry<Wrapper<DexEncodedMethod>, InterfaceMethodGroupState> state :
globalStateMap.entrySet()) {
- for (DexMethod method : state.getValue().methodStates.keySet()) {
+ for (DexEncodedMethod method : state.getValue().methodStates.keySet()) {
seen.add(method);
if (unifiedMethods.contains(state.getKey())) {
boolean added = unifiedSeen.add(method);
@@ -693,12 +705,12 @@
return true;
}
- private void print(DexMethod method, Set<DexMethod> sourceMethods, PrintStream out) {
+ private void print(DexMethod method, Set<DexEncodedMethod> sourceMethods, PrintStream out) {
out.println("-----------------------------------------------------------------------");
out.println("assignNameToInterfaceMethod(`" + method.toSourceString() + "`)");
out.println("-----------------------------------------------------------------------");
out.println("Source methods:");
- for (DexMethod sourceMethod : sourceMethods) {
+ for (DexEncodedMethod sourceMethod : sourceMethods) {
out.println(" " + sourceMethod.toSourceString());
}
out.println("States:");
diff --git a/src/main/java/com/android/tools/r8/naming/MemberNamingStrategy.java b/src/main/java/com/android/tools/r8/naming/MemberNamingStrategy.java
index cddbea8..58a4dc6 100644
--- a/src/main/java/com/android/tools/r8/naming/MemberNamingStrategy.java
+++ b/src/main/java/com/android/tools/r8/naming/MemberNamingStrategy.java
@@ -15,7 +15,7 @@
public interface MemberNamingStrategy {
DexString next(
- DexMethod method,
+ DexEncodedMethod method,
InternalNamingState internalState,
BiPredicate<DexString, DexMethod> isAvailable);
diff --git a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
index cdb9166..7b3e763 100644
--- a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
@@ -89,8 +89,8 @@
// from the method name minifier to the interface method name minifier.
class State {
- void putRenaming(DexMethod key, DexString value) {
- renaming.put(key, value);
+ void putRenaming(DexEncodedMethod key, DexString value) {
+ renaming.put(key.getReference(), value);
}
MethodReservationState<?> getReservationState(DexType type) {
@@ -221,21 +221,21 @@
}
private void assignNameToMethod(
- DexClass holder, DexEncodedMethod encodedMethod, MethodNamingState<?> state) {
- if (encodedMethod.accessFlags.isConstructor()) {
+ DexClass holder, DexEncodedMethod method, MethodNamingState<?> state) {
+ if (method.isInitializer()) {
return;
}
// The strategy may have an explicit naming for this member which we query first. It may be that
// the strategy will return the identity name, for which we have to look into a previous
// renaming tracked by the state.
- DexString newName = strategy.getReservedName(encodedMethod, holder);
- if (newName == null || newName == encodedMethod.method.name) {
- newName = state.newOrReservedNameFor(encodedMethod.method);
+ DexString newName = strategy.getReservedName(method, holder);
+ if (newName == null || newName == method.getName()) {
+ newName = state.newOrReservedNameFor(method);
}
- if (encodedMethod.method.name != newName) {
- renaming.put(encodedMethod.method, newName);
+ if (method.getName() != newName) {
+ renaming.put(method.getReference(), newName);
}
- state.addRenaming(newName, encodedMethod.method);
+ state.addRenaming(newName, method);
}
private void reserveNamesInClasses() {
@@ -280,7 +280,7 @@
for (DexEncodedMethod method : shuffleMethods(holder.methods(), appView.options())) {
DexString reservedName = strategy.getReservedName(method, holder);
if (reservedName != null) {
- state.reserveName(reservedName, method.method);
+ state.reserveName(reservedName, method);
}
}
}
diff --git a/src/main/java/com/android/tools/r8/naming/MethodNamingState.java b/src/main/java/com/android/tools/r8/naming/MethodNamingState.java
index 85c2f0f..5aa368b 100644
--- a/src/main/java/com/android/tools/r8/naming/MethodNamingState.java
+++ b/src/main/java/com/android/tools/r8/naming/MethodNamingState.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.naming;
+import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.naming.MethodNamingState.InternalNewNameState;
@@ -44,37 +45,38 @@
this, this.keyTransform, this.namingStrategy, frontierReservationState);
}
- DexString newOrReservedNameFor(DexMethod method) {
+ DexString newOrReservedNameFor(DexEncodedMethod method) {
return newOrReservedNameFor(method, this::isAvailable);
}
- DexString newOrReservedNameFor(DexMethod method, BiPredicate<DexString, DexMethod> isAvailable) {
- DexString newName = getAssignedName(method);
+ DexString newOrReservedNameFor(
+ DexEncodedMethod method, BiPredicate<DexString, DexMethod> isAvailable) {
+ DexString newName = getAssignedName(method.getReference());
if (newName != null) {
return newName;
}
- Set<DexString> reservedNamesFor = reservationState.getReservedNamesFor(method);
+ Set<DexString> reservedNamesFor = reservationState.getReservedNamesFor(method.getReference());
// Reservations with applymapping can cause multiple reserved names added to the frontier. In
// that case, the strategy will return the correct one.
if (reservedNamesFor != null && reservedNamesFor.size() == 1) {
DexString candidate = reservedNamesFor.iterator().next();
- if (isAvailable(candidate, method)) {
+ if (isAvailable(candidate, method.getReference())) {
return candidate;
}
}
return nextName(method, isAvailable);
}
- DexString nextName(DexMethod method, BiPredicate<DexString, DexMethod> isAvailable) {
- InternalNewNameState internalState = getOrCreateInternalState(method);
+ DexString nextName(DexEncodedMethod method, BiPredicate<DexString, DexMethod> isAvailable) {
+ InternalNewNameState internalState = getOrCreateInternalState(method.getReference());
DexString newName = namingStrategy.next(method, internalState, isAvailable);
assert newName != null;
return newName;
}
- void addRenaming(DexString newName, DexMethod method) {
- InternalNewNameState internalState = getOrCreateInternalState(method);
- internalState.addRenaming(newName, method);
+ void addRenaming(DexString newName, DexEncodedMethod method) {
+ InternalNewNameState internalState = getOrCreateInternalState(method.getReference());
+ internalState.addRenaming(newName, method.getReference());
}
boolean isAvailable(DexString candidate, DexMethod method) {
diff --git a/src/main/java/com/android/tools/r8/naming/MethodReservationState.java b/src/main/java/com/android/tools/r8/naming/MethodReservationState.java
index 2ce35f9..b963b3f 100644
--- a/src/main/java/com/android/tools/r8/naming/MethodReservationState.java
+++ b/src/main/java/com/android/tools/r8/naming/MethodReservationState.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.naming;
+import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.naming.MethodReservationState.InternalReservationState;
@@ -36,9 +37,9 @@
return new MethodReservationState<>(this, this.keyTransform);
}
- void reserveName(DexString reservedName, DexMethod method) {
+ void reserveName(DexString reservedName, DexEncodedMethod method) {
try {
- getOrCreateInternalState(method).reserveName(method, reservedName);
+ getOrCreateInternalState(method.getReference()).reserveName(method, reservedName);
} catch (AssertionError err) {
throw new RuntimeException(
String.format(
@@ -91,13 +92,14 @@
return originalToReservedNames.get(MethodSignatureEquivalence.get().wrap(method));
}
- void reserveName(DexMethod method, DexString name) {
+ void reserveName(DexEncodedMethod method, DexString name) {
if (reservedNames == null) {
assert originalToReservedNames == null;
originalToReservedNames = new HashMap<>();
reservedNames = new HashSet<>();
}
- final Wrapper<DexMethod> wrapped = MethodSignatureEquivalence.get().wrap(method);
+ final Wrapper<DexMethod> wrapped =
+ MethodSignatureEquivalence.get().wrap(method.getReference());
originalToReservedNames.computeIfAbsent(wrapped, ignore -> new HashSet<>()).add(name);
reservedNames.add(name);
}
diff --git a/src/main/java/com/android/tools/r8/naming/Minifier.java b/src/main/java/com/android/tools/r8/naming/Minifier.java
index d0f0381..182de8d 100644
--- a/src/main/java/com/android/tools/r8/naming/Minifier.java
+++ b/src/main/java/com/android/tools/r8/naming/Minifier.java
@@ -225,16 +225,15 @@
@Override
public DexString next(
- DexMethod method,
+ DexEncodedMethod method,
InternalNamingState internalState,
BiPredicate<DexString, DexMethod> isAvailable) {
- assert checkAllowMemberRenaming(method.holder);
- DexEncodedMethod encodedMethod = appView.definitionFor(method);
- boolean isDirectOrStatic = encodedMethod.isDirectMethod() || encodedMethod.isStatic();
+ assert checkAllowMemberRenaming(method.getHolderType());
+ boolean isDirectOrStatic = method.isDirectMethod() || method.isStatic();
DexString candidate;
do {
candidate = getNextName(internalState, isDirectOrStatic);
- } while (!isAvailable.test(candidate, method));
+ } while (!isAvailable.test(candidate, method.getReference()));
return candidate;
}
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
index e720b30..515d21f 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
@@ -188,11 +188,11 @@
Set<DexReference> notMappedReferences,
SubtypingInfo subtypingInfo) {
ClassNamingForMapApplier classNaming = seedMapper.getClassNaming(type);
- DexClass dexClass = appView.definitionFor(type);
+ DexClass clazz = appView.definitionFor(type);
// Keep track of classes that needs to get renamed.
- if (dexClass != null && (classNaming != null || dexClass.isProgramClass())) {
- mappedClasses.add(dexClass);
+ if (clazz != null && (classNaming != null || clazz.isProgramClass())) {
+ mappedClasses.add(clazz);
}
Map<DexReference, MemberNaming> nonPrivateMembers = new IdentityHashMap<>();
@@ -205,7 +205,7 @@
memberNaming -> addMemberNamings(type, memberNaming, nonPrivateMembers, false));
} else {
// We have to ensure we do not rename to an existing member, that cannot be renamed.
- if (dexClass == null || !appView.options().isMinifying()) {
+ if (clazz == null || !appView.options().isMinifying()) {
notMappedReferences.add(type);
} else if (appView.options().isMinifying()
&& appView.rootSet().mayNotBeMinified(type, appView)) {
@@ -222,10 +222,10 @@
if (!memberNames.containsKey(parentReferenceOnCurrentType)) {
addMemberNaming(
parentReferenceOnCurrentType, parentMembers.get(key), additionalMethodNamings);
- } else {
- DexEncodedMethod encodedMethod = appView.definitionFor(parentReferenceOnCurrentType);
- assert encodedMethod == null
- || encodedMethod.accessFlags.isStatic()
+ } else if (clazz != null) {
+ DexEncodedMethod method = clazz.lookupMethod(parentReferenceOnCurrentType);
+ assert method == null
+ || method.isStatic()
|| memberNames
.get(parentReferenceOnCurrentType)
.getRenamedName()
@@ -245,12 +245,12 @@
}
}
- if (dexClass != null) {
+ if (clazz != null) {
// If a class is marked as abstract it is allowed to not implement methods from interfaces
// thus the map will not contain a mapping. Also, if an interface is defined in the library
// and the class is in the program, we have to build up the correct names to reserve them.
- if (dexClass.isProgramClass() || dexClass.isAbstract()) {
- addNonPrivateInterfaceMappings(type, nonPrivateMembers, dexClass.interfaces.values);
+ if (clazz.isProgramClass() || clazz.isAbstract()) {
+ addNonPrivateInterfaceMappings(type, nonPrivateMembers, clazz.interfaces.values);
}
}
@@ -293,8 +293,9 @@
DexMethod originalMethod = ((MethodSignature) signature).toDexMethod(factory, type);
addMemberNaming(
originalMethod, memberNaming, addToAdditionalMaps ? additionalMethodNamings : null);
- DexEncodedMethod encodedMethod = appView.definitionFor(originalMethod);
- if (encodedMethod == null || !encodedMethod.accessFlags.isPrivate()) {
+ DexClass holder = appView.definitionForHolder(originalMethod);
+ DexEncodedMethod definition = originalMethod.lookupOnClass(holder);
+ if (definition == null || !definition.accessFlags.isPrivate()) {
nonPrivateMembers.put(originalMethod, memberNaming);
}
} else {
@@ -466,12 +467,12 @@
@Override
public DexString next(
- DexMethod reference,
+ DexEncodedMethod method,
InternalNamingState internalState,
BiPredicate<DexString, DexMethod> isAvailable) {
+ DexMethod reference = method.getReference();
DexClass holder = appView.definitionForHolder(reference);
assert holder != null;
- DexEncodedMethod method = holder.lookupMethod(reference);
DexString reservedName = getReservedName(method, reference.name, holder);
DexString nextName;
if (reservedName != null) {
@@ -482,7 +483,7 @@
} else {
assert !mappedNames.containsKey(reference);
assert appView.rootSet().mayBeMinified(reference, appView);
- nextName = super.next(reference, internalState, isAvailable);
+ nextName = super.next(method, internalState, isAvailable);
}
assert nextName == reference.name || !method.isInitializer();
assert nextName == reference.name || !holder.isAnnotation();
diff --git a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
index eef8234..1f66108 100644
--- a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
+++ b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
@@ -13,14 +13,13 @@
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.naming.NamingLens;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.Reporter;
import com.android.tools.r8.utils.StringDiagnostic;
import com.android.tools.r8.utils.ThreadUtils;
-import com.google.common.collect.Maps;
import java.lang.reflect.GenericSignatureFormatError;
-import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.function.BiConsumer;
@@ -32,23 +31,24 @@
public class GenericSignatureRewriter {
private final AppView<?> appView;
- private final Map<DexType, DexString> renaming;
+ private final NamingLens namingLens;
private final InternalOptions options;
private final Reporter reporter;
- public GenericSignatureRewriter(AppView<?> appView) {
- this(appView, Maps.newIdentityHashMap());
- }
-
- public GenericSignatureRewriter(AppView<?> appView, Map<DexType, DexString> renaming) {
+ public GenericSignatureRewriter(AppView<?> appView, NamingLens namingLens) {
this.appView = appView;
- this.renaming = renaming;
+ this.namingLens = namingLens;
this.options = appView.options();
this.reporter = options.reporter;
}
public void run(Iterable<? extends DexProgramClass> classes, ExecutorService executorService)
throws ExecutionException {
+ // Rewrite signature annotations for applications that are minified or if we have liveness
+ // information, since we could have pruned types.
+ if (namingLens.isIdentityLens() && !appView.appInfo().hasLiveness()) {
+ return;
+ }
// Classes may not be the same as appInfo().classes() if applymapping is used on classpath
// arguments. If that is the case, the ProguardMapMinifier will pass in all classes that is
// either ProgramClass or has a mapping. This is then transitively called inside the
@@ -204,7 +204,7 @@
if (appView.appInfo().hasLiveness() && appView.withLiveness().appInfo().wasPruned(type)) {
type = appView.dexItemFactory().objectType;
}
- DexString renamedDescriptor = renaming.getOrDefault(type, type.descriptor);
+ DexString renamedDescriptor = namingLens.lookupDescriptor(type);
if (parserPosition == ParserPosition.CLASS_SUPER_OR_INTERFACE_ANNOTATION
&& currentClassContext != null) {
// We may have merged the type down to the current class type.
@@ -251,16 +251,14 @@
getClassBinaryNameFromDescriptor(enclosingDescriptor)
+ DescriptorUtils.INNER_CLASS_SEPARATOR
+ name));
- String enclosingRenamedBinaryName =
- getClassBinaryNameFromDescriptor(
- renaming.getOrDefault(enclosingType, enclosingType.descriptor).toString());
type = appView.graphLense().lookupType(type);
- DexString renamedDescriptor = renaming.get(type);
- if (renamedDescriptor != null) {
+ String renamedDescriptor = namingLens.lookupDescriptor(type).toString();
+ if (!renamedDescriptor.equals(type.toDescriptorString())) {
// TODO(b/147504070): If this is a merged class equal to the class context, do not add.
// Pick the renamed inner class from the fully renamed binary name.
- String fullRenamedBinaryName =
- getClassBinaryNameFromDescriptor(renamedDescriptor.toString());
+ String fullRenamedBinaryName = getClassBinaryNameFromDescriptor(renamedDescriptor);
+ String enclosingRenamedBinaryName =
+ getClassBinaryNameFromDescriptor(namingLens.lookupDescriptor(enclosingType).toString());
int innerClassPos = enclosingRenamedBinaryName.length() + 1;
if (innerClassPos < fullRenamedBinaryName.length()) {
renamedSignature.append(fullRenamedBinaryName.substring(innerClassPos));
diff --git a/src/main/java/com/android/tools/r8/relocator/Relocator.java b/src/main/java/com/android/tools/r8/relocator/Relocator.java
index aaf83b1..1e8420e 100644
--- a/src/main/java/com/android/tools/r8/relocator/Relocator.java
+++ b/src/main/java/com/android/tools/r8/relocator/Relocator.java
@@ -87,8 +87,7 @@
SimplePackagesRewritingMapper packageRemapper = new SimplePackagesRewritingMapper(appView);
NamingLens namingLens = packageRemapper.compute(command.getMapping());
- new GenericSignatureRewriter(appView, packageRemapper.getTypeMappings())
- .run(appInfo.classes(), executor);
+ new GenericSignatureRewriter(appView, namingLens).run(appInfo.classes(), executor);
new CfApplicationWriter(
app,
diff --git a/src/main/java/com/android/tools/r8/shaking/AnnotationMatchResult.java b/src/main/java/com/android/tools/r8/shaking/AnnotationMatchResult.java
index f5a3bbf..5b97077 100644
--- a/src/main/java/com/android/tools/r8/shaking/AnnotationMatchResult.java
+++ b/src/main/java/com/android/tools/r8/shaking/AnnotationMatchResult.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.shaking;
import com.android.tools.r8.graph.DexAnnotation;
+import java.util.List;
public abstract class AnnotationMatchResult {
@@ -30,14 +31,14 @@
static class ConcreteAnnotationMatchResult extends AnnotationMatchResult {
- private final DexAnnotation matchedAnnotation;
+ private final List<DexAnnotation> matchedAnnotations;
- public ConcreteAnnotationMatchResult(DexAnnotation matchedAnnotation) {
- this.matchedAnnotation = matchedAnnotation;
+ public ConcreteAnnotationMatchResult(List<DexAnnotation> matchedAnnotations) {
+ this.matchedAnnotations = matchedAnnotations;
}
- public DexAnnotation getMatchedAnnotation() {
- return matchedAnnotation;
+ public List<DexAnnotation> getMatchedAnnotations() {
+ return matchedAnnotations;
}
@Override
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 87bfd9a..f617c31 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -12,6 +12,7 @@
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexClasspathClass;
+import com.android.tools.r8.graph.DexDefinition;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
@@ -1338,22 +1339,17 @@
return isInstantiatedDirectly(clazz) || isPinned(clazz.type) || isInstantiatedInterface(clazz);
}
- public boolean isPinnedNotProgramOrLibraryOverride(DexReference reference) {
- if (isPinned(reference)) {
+ public boolean isPinnedNotProgramOrLibraryOverride(DexDefinition definition) {
+ if (isPinned(definition.toReference())) {
return true;
}
- if (reference.isDexMethod()) {
- DexEncodedMethod method = definitionFor(reference.asDexMethod());
- return method == null
- || !method.isProgramMethod(this)
- || method.isLibraryMethodOverride().isPossiblyTrue();
- } else {
- assert reference.isDexType();
- DexClass clazz = definitionFor(reference.asDexType());
- return clazz == null
- || clazz.isNotProgramClass()
- || isInstantiatedInterface(clazz.asProgramClass());
+ if (definition.isDexEncodedMethod()) {
+ DexEncodedMethod method = definition.asDexEncodedMethod();
+ return !method.isProgramMethod(this) || method.isLibraryMethodOverride().isPossiblyTrue();
}
+ assert definition.isDexClass();
+ DexClass clazz = definition.asDexClass();
+ return clazz.isNotProgramClass() || isInstantiatedInterface(clazz.asProgramClass());
}
public SubtypingInfo computeSubtypingInfo() {
diff --git a/src/main/java/com/android/tools/r8/shaking/ClassInlineRule.java b/src/main/java/com/android/tools/r8/shaking/ClassInlineRule.java
index 4e69cd2..ccda8e3 100644
--- a/src/main/java/com/android/tools/r8/shaking/ClassInlineRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ClassInlineRule.java
@@ -39,13 +39,13 @@
origin,
getPosition(),
source,
- classAnnotation,
+ buildClassAnnotations(),
classAccessFlags,
negatedClassAccessFlags,
classTypeNegated,
classType,
classNames,
- inheritanceAnnotation,
+ buildInheritanceAnnotations(),
inheritanceClassName,
inheritanceIsExtends,
memberRules,
@@ -59,13 +59,13 @@
Origin origin,
Position position,
String source,
- ProguardTypeMatcher classAnnotation,
+ List<ProguardTypeMatcher> classAnnotations,
ProguardAccessFlags classAccessFlags,
ProguardAccessFlags negatedClassAccessFlags,
boolean classTypeNegated,
ProguardClassType classType,
ProguardClassNameList classNames,
- ProguardTypeMatcher inheritanceAnnotation,
+ List<ProguardTypeMatcher> inheritanceAnnotations,
ProguardTypeMatcher inheritanceClassName,
boolean inheritanceIsExtends,
List<ProguardMemberRule> memberRules,
@@ -74,13 +74,13 @@
origin,
position,
source,
- classAnnotation,
+ classAnnotations,
classAccessFlags,
negatedClassAccessFlags,
classTypeNegated,
classType,
classNames,
- inheritanceAnnotation,
+ inheritanceAnnotations,
inheritanceClassName,
inheritanceIsExtends,
memberRules);
diff --git a/src/main/java/com/android/tools/r8/shaking/ClassMergingRule.java b/src/main/java/com/android/tools/r8/shaking/ClassMergingRule.java
index 43bfe4b..ee57043 100644
--- a/src/main/java/com/android/tools/r8/shaking/ClassMergingRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ClassMergingRule.java
@@ -34,9 +34,21 @@
@Override
public ClassMergingRule build() {
- return new ClassMergingRule(origin, getPosition(), source, classAnnotation, classAccessFlags,
- negatedClassAccessFlags, classTypeNegated, classType, classNames, inheritanceAnnotation,
- inheritanceClassName, inheritanceIsExtends, memberRules, type);
+ return new ClassMergingRule(
+ origin,
+ getPosition(),
+ source,
+ buildClassAnnotations(),
+ classAccessFlags,
+ negatedClassAccessFlags,
+ classTypeNegated,
+ classType,
+ classNames,
+ buildInheritanceAnnotations(),
+ inheritanceClassName,
+ inheritanceIsExtends,
+ memberRules,
+ type);
}
}
@@ -46,20 +58,31 @@
Origin origin,
Position position,
String source,
- ProguardTypeMatcher classAnnotation,
+ List<ProguardTypeMatcher> classAnnotations,
ProguardAccessFlags classAccessFlags,
ProguardAccessFlags negatedClassAccessFlags,
boolean classTypeNegated,
ProguardClassType classType,
ProguardClassNameList classNames,
- ProguardTypeMatcher inheritanceAnnotation,
+ List<ProguardTypeMatcher> inheritanceAnnotations,
ProguardTypeMatcher inheritanceClassName,
boolean inheritanceIsExtends,
List<ProguardMemberRule> memberRules,
Type type) {
- super(origin, position, source, classAnnotation, classAccessFlags, negatedClassAccessFlags,
- classTypeNegated, classType, classNames, inheritanceAnnotation, inheritanceClassName,
- inheritanceIsExtends, memberRules);
+ super(
+ origin,
+ position,
+ source,
+ classAnnotations,
+ classAccessFlags,
+ negatedClassAccessFlags,
+ classTypeNegated,
+ classType,
+ classNames,
+ inheritanceAnnotations,
+ inheritanceClassName,
+ inheritanceIsExtends,
+ memberRules);
this.type = type;
}
diff --git a/src/main/java/com/android/tools/r8/shaking/ConsequentRootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/ConsequentRootSetBuilder.java
index 8288bde..540db63 100644
--- a/src/main/java/com/android/tools/r8/shaking/ConsequentRootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/ConsequentRootSetBuilder.java
@@ -24,7 +24,7 @@
if (enqueuer.getMode().isInitialTreeShaking()
&& annotationMatchResult.isConcreteAnnotationMatchResult()) {
enqueuer.retainAnnotationForFinalTreeShaking(
- annotationMatchResult.asConcreteAnnotationMatchResult().getMatchedAnnotation());
+ annotationMatchResult.asConcreteAnnotationMatchResult().getMatchedAnnotations());
}
}
}
diff --git a/src/main/java/com/android/tools/r8/shaking/ConstantArgumentRule.java b/src/main/java/com/android/tools/r8/shaking/ConstantArgumentRule.java
index 6d6f424..bc33778 100644
--- a/src/main/java/com/android/tools/r8/shaking/ConstantArgumentRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ConstantArgumentRule.java
@@ -27,13 +27,13 @@
origin,
getPosition(),
source,
- classAnnotation,
+ buildClassAnnotations(),
classAccessFlags,
negatedClassAccessFlags,
classTypeNegated,
classType,
classNames,
- inheritanceAnnotation,
+ buildInheritanceAnnotations(),
inheritanceClassName,
inheritanceIsExtends,
memberRules);
@@ -44,13 +44,13 @@
Origin origin,
Position position,
String source,
- ProguardTypeMatcher classAnnotation,
+ List<ProguardTypeMatcher> classAnnotations,
ProguardAccessFlags classAccessFlags,
ProguardAccessFlags negatedClassAccessFlags,
boolean classTypeNegated,
ProguardClassType classType,
ProguardClassNameList classNames,
- ProguardTypeMatcher inheritanceAnnotation,
+ List<ProguardTypeMatcher> inheritanceAnnotations,
ProguardTypeMatcher inheritanceClassName,
boolean inheritanceIsExtends,
List<ProguardMemberRule> memberRules) {
@@ -58,13 +58,13 @@
origin,
position,
source,
- classAnnotation,
+ classAnnotations,
classAccessFlags,
negatedClassAccessFlags,
classTypeNegated,
classType,
classNames,
- inheritanceAnnotation,
+ inheritanceAnnotations,
inheritanceClassName,
inheritanceIsExtends,
memberRules);
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 a0a1924..76c9e4c 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -2496,7 +2496,7 @@
(type, subTypeConsumer, lambdaConsumer) ->
objectAllocationInfoCollection.forEachInstantiatedSubType(
type, subTypeConsumer, lambdaConsumer, appInfo),
- reference -> keepInfo.isPinned(reference, appInfo))
+ definition -> keepInfo.isPinned(definition.toReference(), appInfo))
.forEach(
target ->
markVirtualDispatchTargetAsLive(
@@ -3376,10 +3376,10 @@
desugaredLambdaImplementationMethods.clear();
}
- void retainAnnotationForFinalTreeShaking(DexAnnotation annotation) {
+ void retainAnnotationForFinalTreeShaking(List<DexAnnotation> annotations) {
assert mode.isInitialTreeShaking();
if (annotationRemoverBuilder != null) {
- annotationRemoverBuilder.retainAnnotation(annotation);
+ annotations.forEach(annotationRemoverBuilder::retainAnnotation);
}
}
@@ -4161,12 +4161,6 @@
this.enqueuer = enqueuer;
}
- @Deprecated
- @Override
- public DexEncodedMethod definitionFor(DexMethod method) {
- return enqueuer.definitionFor(method);
- }
-
@Override
public DexClass definitionFor(DexType type) {
return enqueuer.definitionFor(type);
diff --git a/src/main/java/com/android/tools/r8/shaking/IfRuleClassPartEquivalence.java b/src/main/java/com/android/tools/r8/shaking/IfRuleClassPartEquivalence.java
index aa37771..527565f 100644
--- a/src/main/java/com/android/tools/r8/shaking/IfRuleClassPartEquivalence.java
+++ b/src/main/java/com/android/tools/r8/shaking/IfRuleClassPartEquivalence.java
@@ -12,7 +12,7 @@
@Override
protected boolean doEquivalent(ProguardIfRule p1, ProguardIfRule p2) {
- if (!Objects.equals(p1.getClassAnnotation(), p2.getClassAnnotation())) {
+ if (!p1.getClassAnnotations().equals(p2.getClassAnnotations())) {
return false;
}
if (!p1.getClassAccessFlags().equals(p2.getClassAccessFlags())
@@ -26,7 +26,7 @@
if (p1.getInheritanceIsExtends() != p2.getInheritanceIsExtends()) {
return false;
}
- if (!Objects.equals(p1.getInheritanceAnnotation(), p2.getInheritanceAnnotation())) {
+ if (!p1.getInheritanceAnnotations().equals(p2.getInheritanceAnnotations())) {
return false;
}
if (!Objects.equals(p1.getInheritanceClassName(), p2.getInheritanceClassName())) {
@@ -40,17 +40,13 @@
@Override
protected int doHash(ProguardIfRule rule) {
- int result = (rule.getClassAnnotation() != null ? rule.getClassAnnotation().hashCode() : 0);
+ int result = rule.getClassAnnotations().hashCode();
result = 3 * result + rule.getClassAccessFlags().hashCode();
result = 3 * result + rule.getNegatedClassAccessFlags().hashCode();
result = 3 * result + (rule.getClassTypeNegated() ? 1 : 0);
result = 3 * result + (rule.getClassType() != null ? rule.getClassType().hashCode() : 0);
result = 3 * result + rule.getClassNames().hashCode();
- result =
- 3 * result
- + (rule.getInheritanceAnnotation() != null
- ? rule.getInheritanceAnnotation().hashCode()
- : 0);
+ result = 3 * result + rule.getInheritanceAnnotations().hashCode();
result =
3 * result
+ (rule.getInheritanceClassName() != null
diff --git a/src/main/java/com/android/tools/r8/shaking/InlineRule.java b/src/main/java/com/android/tools/r8/shaking/InlineRule.java
index ca746a1..41b6b33 100644
--- a/src/main/java/com/android/tools/r8/shaking/InlineRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/InlineRule.java
@@ -41,9 +41,21 @@
@Override
public InlineRule build() {
- return new InlineRule(origin, getPosition(), source, classAnnotation, classAccessFlags,
- negatedClassAccessFlags, classTypeNegated, classType, classNames, inheritanceAnnotation,
- inheritanceClassName, inheritanceIsExtends, memberRules, type);
+ return new InlineRule(
+ origin,
+ getPosition(),
+ source,
+ buildClassAnnotations(),
+ classAccessFlags,
+ negatedClassAccessFlags,
+ classTypeNegated,
+ classType,
+ classNames,
+ buildInheritanceAnnotations(),
+ inheritanceClassName,
+ inheritanceIsExtends,
+ memberRules,
+ type);
}
}
@@ -53,20 +65,31 @@
Origin origin,
Position position,
String source,
- ProguardTypeMatcher classAnnotation,
+ List<ProguardTypeMatcher> classAnnotations,
ProguardAccessFlags classAccessFlags,
ProguardAccessFlags negatedClassAccessFlags,
boolean classTypeNegated,
ProguardClassType classType,
ProguardClassNameList classNames,
- ProguardTypeMatcher inheritanceAnnotation,
+ List<ProguardTypeMatcher> inheritanceAnnotations,
ProguardTypeMatcher inheritanceClassName,
boolean inheritanceIsExtends,
List<ProguardMemberRule> memberRules,
Type type) {
- super(origin, position, source, classAnnotation, classAccessFlags, negatedClassAccessFlags,
- classTypeNegated, classType, classNames, inheritanceAnnotation, inheritanceClassName,
- inheritanceIsExtends, memberRules);
+ super(
+ origin,
+ position,
+ source,
+ classAnnotations,
+ classAccessFlags,
+ negatedClassAccessFlags,
+ classTypeNegated,
+ classType,
+ classNames,
+ inheritanceAnnotations,
+ inheritanceClassName,
+ inheritanceIsExtends,
+ memberRules);
this.type = type;
}
@@ -83,13 +106,13 @@
ProguardCheckDiscardRule.Builder builder = ProguardCheckDiscardRule.builder();
builder.setOrigin(checkDiscardOrigin);
builder.setSource(null);
- builder.setClassAnnotation(getClassAnnotation());
+ builder.addClassAnnotations(getClassAnnotations());
builder.setClassAccessFlags(getClassAccessFlags());
builder.setNegatedClassAccessFlags(getNegatedClassAccessFlags());
builder.setClassTypeNegated(getClassTypeNegated());
builder.setClassType(getClassType());
builder.setClassNames(getClassNames());
- builder.setInheritanceAnnotation(getInheritanceAnnotation());
+ builder.addInheritanceAnnotations(getInheritanceAnnotations());
builder.setInheritanceIsExtends(getInheritanceIsExtends());
builder.setMemberRules(getMemberRules());
return builder.build();
diff --git a/src/main/java/com/android/tools/r8/shaking/MemberValuePropagationRule.java b/src/main/java/com/android/tools/r8/shaking/MemberValuePropagationRule.java
index dde4631..c26832b 100644
--- a/src/main/java/com/android/tools/r8/shaking/MemberValuePropagationRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/MemberValuePropagationRule.java
@@ -35,9 +35,21 @@
@Override
public MemberValuePropagationRule build() {
- return new MemberValuePropagationRule(origin, getPosition(), source, classAnnotation,
- classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType, classNames,
- inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules, type);
+ return new MemberValuePropagationRule(
+ origin,
+ getPosition(),
+ source,
+ buildClassAnnotations(),
+ classAccessFlags,
+ negatedClassAccessFlags,
+ classTypeNegated,
+ classType,
+ classNames,
+ buildInheritanceAnnotations(),
+ inheritanceClassName,
+ inheritanceIsExtends,
+ memberRules,
+ type);
}
}
@@ -47,20 +59,31 @@
Origin origin,
Position position,
String source,
- ProguardTypeMatcher classAnnotation,
+ List<ProguardTypeMatcher> classAnnotations,
ProguardAccessFlags classAccessFlags,
ProguardAccessFlags negatedClassAccessFlags,
boolean classTypeNegated,
ProguardClassType classType,
ProguardClassNameList classNames,
- ProguardTypeMatcher inheritanceAnnotation,
+ List<ProguardTypeMatcher> inheritanceAnnotations,
ProguardTypeMatcher inheritanceClassName,
boolean inheritanceIsExtends,
List<ProguardMemberRule> memberRules,
Type type) {
- super(origin, position, source, classAnnotation, classAccessFlags, negatedClassAccessFlags,
- classTypeNegated, classType, classNames, inheritanceAnnotation, inheritanceClassName,
- inheritanceIsExtends, memberRules);
+ super(
+ origin,
+ position,
+ source,
+ classAnnotations,
+ classAccessFlags,
+ negatedClassAccessFlags,
+ classTypeNegated,
+ classType,
+ classNames,
+ inheritanceAnnotations,
+ inheritanceClassName,
+ inheritanceIsExtends,
+ memberRules);
this.type = type;
}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardAssumeMayHaveSideEffectsRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardAssumeMayHaveSideEffectsRule.java
index 01b7a5e..654bc3e 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardAssumeMayHaveSideEffectsRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardAssumeMayHaveSideEffectsRule.java
@@ -27,13 +27,13 @@
origin,
getPosition(),
source,
- classAnnotation,
+ buildClassAnnotations(),
classAccessFlags,
negatedClassAccessFlags,
classTypeNegated,
classType,
classNames,
- inheritanceAnnotation,
+ buildInheritanceAnnotations(),
inheritanceClassName,
inheritanceIsExtends,
memberRules);
@@ -44,13 +44,13 @@
Origin origin,
Position position,
String source,
- ProguardTypeMatcher classAnnotation,
+ List<ProguardTypeMatcher> classAnnotations,
ProguardAccessFlags classAccessFlags,
ProguardAccessFlags negatedClassAccessFlags,
boolean classTypeNegated,
ProguardClassType classType,
ProguardClassNameList classNames,
- ProguardTypeMatcher inheritanceAnnotation,
+ List<ProguardTypeMatcher> inheritanceAnnotations,
ProguardTypeMatcher inheritanceClassName,
boolean inheritanceIsExtends,
List<ProguardMemberRule> memberRules) {
@@ -58,13 +58,13 @@
origin,
position,
source,
- classAnnotation,
+ classAnnotations,
classAccessFlags,
negatedClassAccessFlags,
classTypeNegated,
classType,
classNames,
- inheritanceAnnotation,
+ inheritanceAnnotations,
inheritanceClassName,
inheritanceIsExtends,
memberRules);
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardAssumeNoSideEffectRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardAssumeNoSideEffectRule.java
index 5f8e860..ed151aa 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardAssumeNoSideEffectRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardAssumeNoSideEffectRule.java
@@ -23,9 +23,20 @@
@Override
public ProguardAssumeNoSideEffectRule build() {
- return new ProguardAssumeNoSideEffectRule(origin, getPosition(), source, classAnnotation,
- classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType, classNames,
- inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
+ return new ProguardAssumeNoSideEffectRule(
+ origin,
+ getPosition(),
+ source,
+ buildClassAnnotations(),
+ classAccessFlags,
+ negatedClassAccessFlags,
+ classTypeNegated,
+ classType,
+ classNames,
+ buildInheritanceAnnotations(),
+ inheritanceClassName,
+ inheritanceIsExtends,
+ memberRules);
}
}
@@ -33,19 +44,30 @@
Origin origin,
Position position,
String source,
- ProguardTypeMatcher classAnnotation,
+ List<ProguardTypeMatcher> classAnnotations,
ProguardAccessFlags classAccessFlags,
ProguardAccessFlags negatedClassAccessFlags,
boolean classTypeNegated,
ProguardClassType classType,
ProguardClassNameList classNames,
- ProguardTypeMatcher inheritanceAnnotation,
+ List<ProguardTypeMatcher> inheritanceAnnotations,
ProguardTypeMatcher inheritanceClassName,
boolean inheritanceIsExtends,
List<ProguardMemberRule> memberRules) {
- super(origin, position, source, classAnnotation, classAccessFlags, negatedClassAccessFlags,
- classTypeNegated, classType, classNames, inheritanceAnnotation, inheritanceClassName,
- inheritanceIsExtends, memberRules);
+ super(
+ origin,
+ position,
+ source,
+ classAnnotations,
+ classAccessFlags,
+ negatedClassAccessFlags,
+ classTypeNegated,
+ classType,
+ classNames,
+ inheritanceAnnotations,
+ inheritanceClassName,
+ inheritanceIsExtends,
+ memberRules);
}
/**
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardAssumeValuesRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardAssumeValuesRule.java
index 8b8f4dd..97d901e 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardAssumeValuesRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardAssumeValuesRule.java
@@ -23,9 +23,20 @@
@Override
public ProguardAssumeValuesRule build() {
- return new ProguardAssumeValuesRule(origin, getPosition(), source, classAnnotation,
- classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType, classNames,
- inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
+ return new ProguardAssumeValuesRule(
+ origin,
+ getPosition(),
+ source,
+ buildClassAnnotations(),
+ classAccessFlags,
+ negatedClassAccessFlags,
+ classTypeNegated,
+ classType,
+ classNames,
+ buildInheritanceAnnotations(),
+ inheritanceClassName,
+ inheritanceIsExtends,
+ memberRules);
}
}
@@ -33,19 +44,30 @@
Origin origin,
Position position,
String source,
- ProguardTypeMatcher classAnnotation,
+ List<ProguardTypeMatcher> classAnnotations,
ProguardAccessFlags classAccessFlags,
ProguardAccessFlags negatedClassAccessFlags,
boolean classTypeNegated,
ProguardClassType classType,
ProguardClassNameList classNames,
- ProguardTypeMatcher inheritanceAnnotation,
+ List<ProguardTypeMatcher> inheritanceAnnotations,
ProguardTypeMatcher inheritanceClassName,
boolean inheritanceIsExtends,
List<ProguardMemberRule> memberRules) {
- super(origin, position, source, classAnnotation, classAccessFlags, negatedClassAccessFlags,
- classTypeNegated, classType, classNames, inheritanceAnnotation, inheritanceClassName,
- inheritanceIsExtends, memberRules);
+ super(
+ origin,
+ position,
+ source,
+ classAnnotations,
+ classAccessFlags,
+ negatedClassAccessFlags,
+ classTypeNegated,
+ classType,
+ classNames,
+ inheritanceAnnotations,
+ inheritanceClassName,
+ inheritanceIsExtends,
+ memberRules);
}
/**
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardCheckDiscardRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardCheckDiscardRule.java
index 664cb25..82b81a4 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardCheckDiscardRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardCheckDiscardRule.java
@@ -23,9 +23,20 @@
@Override
public ProguardCheckDiscardRule build() {
- return new ProguardCheckDiscardRule(origin, getPosition(), source, classAnnotation,
- classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType, classNames,
- inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
+ return new ProguardCheckDiscardRule(
+ origin,
+ getPosition(),
+ source,
+ buildClassAnnotations(),
+ classAccessFlags,
+ negatedClassAccessFlags,
+ classTypeNegated,
+ classType,
+ classNames,
+ buildInheritanceAnnotations(),
+ inheritanceClassName,
+ inheritanceIsExtends,
+ memberRules);
}
}
@@ -33,19 +44,30 @@
Origin origin,
Position position,
String source,
- ProguardTypeMatcher classAnnotation,
+ List<ProguardTypeMatcher> classAnnotations,
ProguardAccessFlags classAccessFlags,
ProguardAccessFlags negatedClassAccessFlags,
boolean classTypeNegated,
ProguardClassType classType,
ProguardClassNameList classNames,
- ProguardTypeMatcher inheritanceAnnotation,
+ List<ProguardTypeMatcher> inheritanceAnnotations,
ProguardTypeMatcher inheritanceClassName,
boolean inheritanceIsExtends,
List<ProguardMemberRule> memberRules) {
- super(origin, position, source, classAnnotation, classAccessFlags, negatedClassAccessFlags,
- classTypeNegated, classType, classNames, inheritanceAnnotation, inheritanceClassName,
- inheritanceIsExtends, memberRules);
+ super(
+ origin,
+ position,
+ source,
+ classAnnotations,
+ classAccessFlags,
+ negatedClassAccessFlags,
+ classTypeNegated,
+ classType,
+ classNames,
+ inheritanceAnnotations,
+ inheritanceClassName,
+ inheritanceIsExtends,
+ memberRules);
}
public static Builder builder() {
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardClassSpecification.java b/src/main/java/com/android/tools/r8/shaking/ProguardClassSpecification.java
index 9ae4986..2bf270a 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardClassSpecification.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardClassSpecification.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.position.TextRange;
import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.ImmutableList;
+import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
@@ -22,13 +23,15 @@
protected Position start;
protected Position end;
protected String source;
- protected ProguardTypeMatcher classAnnotation;
+ private final ImmutableList.Builder<ProguardTypeMatcher> classAnnotations =
+ ImmutableList.builder();
protected ProguardAccessFlags classAccessFlags = new ProguardAccessFlags();
protected ProguardAccessFlags negatedClassAccessFlags = new ProguardAccessFlags();
protected boolean classTypeNegated = false;
protected ProguardClassType classType = ProguardClassType.UNSPECIFIED;
protected ProguardClassNameList classNames;
- protected ProguardTypeMatcher inheritanceAnnotation;
+ private final ImmutableList.Builder<ProguardTypeMatcher> inheritanceAnnotations =
+ ImmutableList.builder();
protected ProguardTypeMatcher inheritanceClassName;
protected boolean inheritanceIsExtends = false;
protected List<ProguardMemberRule> memberRules = new LinkedList<>();
@@ -105,12 +108,13 @@
this.inheritanceClassName = inheritanceClassName;
}
- public ProguardTypeMatcher getInheritanceAnnotation() {
- return inheritanceAnnotation;
+ public void addInheritanceAnnotations(List<ProguardTypeMatcher> inheritanceAnnotations) {
+ assert inheritanceAnnotations != null;
+ this.inheritanceAnnotations.addAll(inheritanceAnnotations);
}
- public void setInheritanceAnnotation(ProguardTypeMatcher inheritanceAnnotation) {
- this.inheritanceAnnotation = inheritanceAnnotation;
+ public List<ProguardTypeMatcher> buildInheritanceAnnotations() {
+ return inheritanceAnnotations.build();
}
public ProguardClassNameList getClassNames() {
@@ -155,12 +159,17 @@
negatedClassAccessFlags = flags;
}
- public ProguardTypeMatcher getClassAnnotation() {
- return classAnnotation;
+ public void addClassAnnotation(ProguardTypeMatcher classAnnotation) {
+ classAnnotations.add(classAnnotation);
}
- public void setClassAnnotation(ProguardTypeMatcher classAnnotation) {
- this.classAnnotation = classAnnotation;
+ public void addClassAnnotations(List<ProguardTypeMatcher> classAnnotations) {
+ assert classAnnotations != null;
+ this.classAnnotations.addAll(classAnnotations);
+ }
+
+ public List<ProguardTypeMatcher> buildClassAnnotations() {
+ return classAnnotations.build();
}
protected void matchAllSpecification() {
@@ -172,13 +181,13 @@
private final Origin origin;
private final Position position;
private final String source;
- private final ProguardTypeMatcher classAnnotation;
+ private final List<ProguardTypeMatcher> classAnnotations;
private final ProguardAccessFlags classAccessFlags;
private final ProguardAccessFlags negatedClassAccessFlags;
private final boolean classTypeNegated;
private final ProguardClassType classType;
private final ProguardClassNameList classNames;
- private final ProguardTypeMatcher inheritanceAnnotation;
+ private final List<ProguardTypeMatcher> inheritanceAnnotations;
private final ProguardTypeMatcher inheritanceClassName;
private final boolean inheritanceIsExtends;
private final List<ProguardMemberRule> memberRules;
@@ -187,13 +196,13 @@
Origin origin,
Position position,
String source,
- ProguardTypeMatcher classAnnotation,
+ List<ProguardTypeMatcher> classAnnotations,
ProguardAccessFlags classAccessFlags,
ProguardAccessFlags negatedClassAccessFlags,
boolean classTypeNegated,
ProguardClassType classType,
ProguardClassNameList classNames,
- ProguardTypeMatcher inheritanceAnnotation,
+ List<ProguardTypeMatcher> inheritanceAnnotations,
ProguardTypeMatcher inheritanceClassName,
boolean inheritanceIsExtends,
List<ProguardMemberRule> memberRules) {
@@ -202,15 +211,15 @@
assert source != null || origin != Origin.unknown();
this.origin = origin;
this.position = position;
- this.source =source;
- this.classAnnotation = classAnnotation;
+ this.source = source;
+ this.classAnnotations = classAnnotations;
this.classAccessFlags = classAccessFlags;
this.negatedClassAccessFlags = negatedClassAccessFlags;
this.classTypeNegated = classTypeNegated;
this.classType = classType;
assert classType != null;
this.classNames = classNames;
- this.inheritanceAnnotation = inheritanceAnnotation;
+ this.inheritanceAnnotations = inheritanceAnnotations;
this.inheritanceClassName = inheritanceClassName;
this.inheritanceIsExtends = inheritanceIsExtends;
this.memberRules = memberRules;
@@ -248,8 +257,8 @@
return inheritanceClassName;
}
- public ProguardTypeMatcher getInheritanceAnnotation() {
- return inheritanceAnnotation;
+ public List<ProguardTypeMatcher> getInheritanceAnnotations() {
+ return inheritanceAnnotations;
}
public ProguardClassNameList getClassNames() {
@@ -272,8 +281,8 @@
return negatedClassAccessFlags;
}
- public ProguardTypeMatcher getClassAnnotation() {
- return classAnnotation;
+ public List<ProguardTypeMatcher> getClassAnnotations() {
+ return classAnnotations;
}
@Override
@@ -289,7 +298,7 @@
if (inheritanceIsExtends != that.inheritanceIsExtends) {
return false;
}
- if (!Objects.equals(classAnnotation, that.classAnnotation)) {
+ if (!Objects.equals(classAnnotations, that.classAnnotations)) {
return false;
}
if (!classAccessFlags.equals(that.classAccessFlags)) {
@@ -304,7 +313,7 @@
if (!classNames.equals(that.classNames)) {
return false;
}
- if (!Objects.equals(inheritanceAnnotation, that.inheritanceAnnotation)) {
+ if (!Objects.equals(inheritanceAnnotations, that.inheritanceAnnotations)) {
return false;
}
if (!Objects.equals(inheritanceClassName, that.inheritanceClassName)) {
@@ -316,13 +325,13 @@
@Override
public int hashCode() {
// Used multiplier 3 to avoid too much overflow when computing hashCode.
- int result = (classAnnotation != null ? classAnnotation.hashCode() : 0);
+ int result = classAnnotations.hashCode();
result = 3 * result + classAccessFlags.hashCode();
result = 3 * result + negatedClassAccessFlags.hashCode();
result = 3 * result + (classTypeNegated ? 1 : 0);
result = 3 * result + (classType != null ? classType.hashCode() : 0);
result = 3 * result + classNames.hashCode();
- result = 3 * result + (inheritanceAnnotation != null ? inheritanceAnnotation.hashCode() : 0);
+ result = 3 * result + inheritanceAnnotations.hashCode();
result = 3 * result + (inheritanceClassName != null ? inheritanceClassName.hashCode() : 0);
result = 3 * result + (inheritanceIsExtends ? 1 : 0);
result = 3 * result + memberRules.hashCode();
@@ -330,9 +339,9 @@
}
protected StringBuilder append(StringBuilder builder, boolean includeMemberRules) {
+ appendAnnotations(classAnnotations, builder);
boolean needsSpaceBeforeClassType =
- StringUtils.appendNonEmpty(builder, "@", classAnnotation, null)
- | StringUtils.appendNonEmpty(builder, "", classAccessFlags, null)
+ StringUtils.appendNonEmpty(builder, null, classAccessFlags, null)
| StringUtils.appendNonEmpty(
builder, "!", negatedClassAccessFlags.toString().replace(" ", " !"), null);
if (needsSpaceBeforeClassType) {
@@ -345,9 +354,8 @@
builder.append(' ');
classNames.writeTo(builder);
if (hasInheritanceClassName()) {
- builder.append(' ').append(inheritanceIsExtends ? "extends" : "implements");
- StringUtils.appendNonEmpty(builder, "@", inheritanceAnnotation, null);
- builder.append(' ');
+ builder.append(' ').append(inheritanceIsExtends ? "extends" : "implements").append(' ');
+ appendAnnotations(inheritanceAnnotations, builder);
builder.append(inheritanceClassName);
}
if (includeMemberRules && !memberRules.isEmpty()) {
@@ -362,6 +370,18 @@
return builder;
}
+ private static void appendAnnotations(
+ List<ProguardTypeMatcher> annotations, StringBuilder builder) {
+ if (!annotations.isEmpty()) {
+ Iterator<ProguardTypeMatcher> annotationIterator = annotations.iterator();
+ builder.append('@').append(annotationIterator.next());
+ while (annotationIterator.hasNext()) {
+ builder.append(" @").append(annotationIterator.next());
+ }
+ builder.append(' ');
+ }
+ }
+
/**
* Short String representation without member rules.
*/
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index aee4a83..58bbdae 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -897,7 +897,7 @@
ProguardClassSpecification.Builder<C, B> builder,
boolean allowValueSpecification)
throws ProguardRuleParserException {
- parseClassFlagsAndAnnotations(builder);
+ parseClassAnnotationsAndFlags(builder);
parseClassType(builder);
builder.setClassNames(parseClassNames());
parseInheritance(builder);
@@ -965,6 +965,18 @@
}
}
+ private List<ProguardTypeMatcher> parseAnnotationList() throws ProguardRuleParserException {
+ List<ProguardTypeMatcher> annotations = null;
+ ProguardTypeMatcher current;
+ while ((current = parseAnnotation()) != null) {
+ if (annotations == null) {
+ annotations = new ArrayList<>(2);
+ }
+ annotations.add(current);
+ }
+ return annotations != null ? annotations : Collections.emptyList();
+ }
+
private ProguardTypeMatcher parseAnnotation() throws ProguardRuleParserException {
skipWhitespace();
int startPosition = position;
@@ -988,16 +1000,14 @@
return acceptChar('!');
}
- private void parseClassFlagsAndAnnotations(ProguardClassSpecification.Builder builder)
+ private void parseClassAnnotationsAndFlags(ProguardClassSpecification.Builder<?, ?> builder)
throws ProguardRuleParserException {
+ // We allow interleaving the class annotations and class flags for compatibility with
+ // Proguard, although this should not be possible according to the grammar.
while (true) {
- skipWhitespace();
ProguardTypeMatcher annotation = parseAnnotation();
if (annotation != null) {
- // TODO(ager): Should we only allow one annotation? It looks that way from the
- // proguard keep rule description, but that seems like a strange restriction?
- assert builder.getClassAnnotation() == null;
- builder.setClassAnnotation(annotation);
+ builder.addClassAnnotation(annotation);
} else {
int start = position;
ProguardAccessFlags flags =
@@ -1025,7 +1035,7 @@
"Expected [!]interface|@interface|class|enum", origin, getPosition(start));
}
- private void parseClassType(ProguardClassSpecification.Builder builder) {
+ private void parseClassType(ProguardClassSpecification.Builder<?, ?> builder) {
skipWhitespace();
TextPosition start = getPosition();
if (acceptChar('!')) {
@@ -1049,7 +1059,8 @@
}
}
- private void parseInheritance(ProguardClassSpecification.Builder classSpecificationBuilder)
+ private void parseInheritance(
+ ProguardClassSpecification.Builder<?, ?> classSpecificationBuilder)
throws ProguardRuleParserException {
skipWhitespace();
if (acceptString("implements")) {
@@ -1059,7 +1070,7 @@
} else {
return;
}
- classSpecificationBuilder.setInheritanceAnnotation(parseAnnotation());
+ classSpecificationBuilder.addInheritanceAnnotations(parseAnnotationList());
classSpecificationBuilder.setInheritanceClassName(ProguardTypeMatcher.create(parseClassName(),
ClassOrType.CLASS, dexItemFactory));
}
@@ -1084,8 +1095,7 @@
private ProguardMemberRule parseMemberRule(boolean allowValueSpecification)
throws ProguardRuleParserException {
ProguardMemberRule.Builder ruleBuilder = ProguardMemberRule.builder();
- skipWhitespace();
- ruleBuilder.setAnnotation(parseAnnotation());
+ ruleBuilder.setAnnotations(parseAnnotationList());
parseMemberAccessFlags(ruleBuilder);
parseMemberPattern(ruleBuilder, allowValueSpecification);
return ruleBuilder.isValid() ? ruleBuilder.build() : null;
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
index 0e21e66..4a19c6f 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
@@ -27,19 +27,30 @@
Origin origin,
Position position,
String source,
- ProguardTypeMatcher classAnnotation,
+ List<ProguardTypeMatcher> classAnnotations,
ProguardAccessFlags classAccessFlags,
ProguardAccessFlags negatedClassAccessFlags,
boolean classTypeNegated,
ProguardClassType classType,
ProguardClassNameList classNames,
- ProguardTypeMatcher inheritanceAnnotation,
+ List<ProguardTypeMatcher> inheritanceAnnotations,
ProguardTypeMatcher inheritanceClassName,
boolean inheritanceIsExtends,
List<ProguardMemberRule> memberRules) {
- super(origin, position, source, classAnnotation, classAccessFlags, negatedClassAccessFlags,
- classTypeNegated, classType, classNames, inheritanceAnnotation, inheritanceClassName,
- inheritanceIsExtends, memberRules);
+ super(
+ origin,
+ position,
+ source,
+ classAnnotations,
+ classAccessFlags,
+ negatedClassAccessFlags,
+ classTypeNegated,
+ classType,
+ classNames,
+ inheritanceAnnotations,
+ inheritanceClassName,
+ inheritanceIsExtends,
+ memberRules);
}
public boolean isUsed() {
@@ -124,17 +135,16 @@
protected Iterable<ProguardWildcard> getWildcards() {
List<ProguardMemberRule> memberRules = getMemberRules();
return Iterables.concat(
- ProguardTypeMatcher.getWildcardsOrEmpty(getClassAnnotation()),
+ ProguardTypeMatcher.getWildcardsOrEmpty(getClassAnnotations()),
ProguardClassNameList.getWildcardsOrEmpty(getClassNames()),
- ProguardTypeMatcher.getWildcardsOrEmpty(getInheritanceAnnotation()),
+ ProguardTypeMatcher.getWildcardsOrEmpty(getInheritanceAnnotations()),
ProguardTypeMatcher.getWildcardsOrEmpty(getInheritanceClassName()),
memberRules != null
? memberRules.stream()
- .map(ProguardMemberRule::getWildcards)
- .flatMap(it -> StreamSupport.stream(it.spliterator(), false))
+ .map(ProguardMemberRule::getWildcards)
+ .flatMap(it -> StreamSupport.stream(it.spliterator(), false))
::iterator
- : Collections::emptyIterator
- );
+ : Collections::emptyIterator);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardIdentifierNameStringRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardIdentifierNameStringRule.java
index 8ff7670..37c75a1 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardIdentifierNameStringRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardIdentifierNameStringRule.java
@@ -22,9 +22,20 @@
@Override
public ProguardIdentifierNameStringRule build() {
- return new ProguardIdentifierNameStringRule(origin, getPosition(), source, classAnnotation,
- classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType, classNames,
- inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
+ return new ProguardIdentifierNameStringRule(
+ origin,
+ getPosition(),
+ source,
+ buildClassAnnotations(),
+ classAccessFlags,
+ negatedClassAccessFlags,
+ classTypeNegated,
+ classType,
+ classNames,
+ buildInheritanceAnnotations(),
+ inheritanceClassName,
+ inheritanceIsExtends,
+ memberRules);
}
}
@@ -32,19 +43,30 @@
Origin origin,
Position position,
String source,
- ProguardTypeMatcher classAnnotation,
+ List<ProguardTypeMatcher> classAnnotations,
ProguardAccessFlags classAccessFlags,
ProguardAccessFlags negatedClassAccessFlags,
boolean classTypeNegated,
ProguardClassType classType,
ProguardClassNameList classNames,
- ProguardTypeMatcher inheritanceAnnotation,
+ List<ProguardTypeMatcher> inheritanceAnnotations,
ProguardTypeMatcher inheritanceClassName,
boolean inheritanceIsExtends,
List<ProguardMemberRule> memberRules) {
- super(origin, position, source, classAnnotation, classAccessFlags, negatedClassAccessFlags,
- classTypeNegated, classType, classNames, inheritanceAnnotation, inheritanceClassName,
- inheritanceIsExtends, memberRules);
+ super(
+ origin,
+ position,
+ source,
+ classAnnotations,
+ classAccessFlags,
+ negatedClassAccessFlags,
+ classTypeNegated,
+ classType,
+ classNames,
+ inheritanceAnnotations,
+ inheritanceClassName,
+ inheritanceIsExtends,
+ memberRules);
}
public static Builder builder() {
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java
index ba9ec8a..c8e185f 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java
@@ -58,13 +58,13 @@
origin,
getPosition(),
source,
- classAnnotation,
+ buildClassAnnotations(),
classAccessFlags,
negatedClassAccessFlags,
classTypeNegated,
classType,
classNames,
- inheritanceAnnotation,
+ buildInheritanceAnnotations(),
inheritanceClassName,
inheritanceIsExtends,
memberRules,
@@ -77,22 +77,34 @@
Origin origin,
Position position,
String source,
- ProguardTypeMatcher classAnnotation,
+ List<ProguardTypeMatcher> classAnnotations,
ProguardAccessFlags classAccessFlags,
ProguardAccessFlags negatedClassAccessFlags,
boolean classTypeNegated,
ProguardClassType classType,
ProguardClassNameList classNames,
- ProguardTypeMatcher inheritanceAnnotation,
+ List<ProguardTypeMatcher> inheritanceAnnotations,
ProguardTypeMatcher inheritanceClassName,
boolean inheritanceIsExtends,
List<ProguardMemberRule> memberRules,
ProguardKeepRule subsequentRule,
Set<DexReference> preconditions) {
- super(origin, position, source, classAnnotation, classAccessFlags, negatedClassAccessFlags,
- classTypeNegated, classType, classNames, inheritanceAnnotation, inheritanceClassName,
- inheritanceIsExtends, memberRules,
- ProguardKeepRuleType.CONDITIONAL, ProguardKeepRuleModifiers.builder().build());
+ super(
+ origin,
+ position,
+ source,
+ classAnnotations,
+ classAccessFlags,
+ negatedClassAccessFlags,
+ classTypeNegated,
+ classType,
+ classNames,
+ inheritanceAnnotations,
+ inheritanceClassName,
+ inheritanceIsExtends,
+ memberRules,
+ ProguardKeepRuleType.CONDITIONAL,
+ ProguardKeepRuleModifiers.builder().build());
this.subsequentRule = subsequentRule;
this.preconditions = preconditions;
}
@@ -122,15 +134,13 @@
getOrigin(),
getPosition(),
getSource(),
- getClassAnnotation() == null ? null : getClassAnnotation().materialize(dexItemFactory),
+ ProguardTypeMatcher.materializeList(getClassAnnotations(), dexItemFactory),
getClassAccessFlags(),
getNegatedClassAccessFlags(),
getClassTypeNegated(),
getClassType(),
getClassNames().materialize(dexItemFactory),
- getInheritanceAnnotation() == null
- ? null
- : getInheritanceAnnotation().materialize(dexItemFactory),
+ ProguardTypeMatcher.materializeList(getInheritanceAnnotations(), dexItemFactory),
getInheritanceClassName() == null
? null
: getInheritanceClassName().materialize(dexItemFactory),
@@ -149,15 +159,13 @@
neverInlineOrigin,
Position.UNKNOWN,
null,
- getClassAnnotation() == null ? null : getClassAnnotation().materialize(dexItemFactory),
+ ProguardTypeMatcher.materializeList(getClassAnnotations(), dexItemFactory),
getClassAccessFlags(),
getNegatedClassAccessFlags(),
getClassTypeNegated(),
getClassType(),
getClassNames().materialize(dexItemFactory),
- getInheritanceAnnotation() == null
- ? null
- : getInheritanceAnnotation().materialize(dexItemFactory),
+ ProguardTypeMatcher.materializeList(getInheritanceAnnotations(), dexItemFactory),
getInheritanceClassName() == null
? null
: getInheritanceClassName().materialize(dexItemFactory),
@@ -196,15 +204,13 @@
neverInlineOrigin,
Position.UNKNOWN,
null,
- getClassAnnotation() == null ? null : getClassAnnotation().materialize(dexItemFactory),
+ ProguardTypeMatcher.materializeList(getClassAnnotations(), dexItemFactory),
getClassAccessFlags(),
getNegatedClassAccessFlags(),
getClassTypeNegated(),
getClassType(),
getClassNames().materialize(dexItemFactory),
- getInheritanceAnnotation() == null
- ? null
- : getInheritanceAnnotation().materialize(dexItemFactory),
+ ProguardTypeMatcher.materializeList(getInheritanceAnnotations(), dexItemFactory),
getInheritanceClassName() == null
? null
: getInheritanceClassName().materialize(dexItemFactory),
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardKeepRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardKeepRule.java
index 4633882..305c108 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardKeepRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardKeepRule.java
@@ -25,9 +25,22 @@
@Override
public ProguardKeepRule build() {
- return new ProguardKeepRule(origin, getPosition(), source, classAnnotation, classAccessFlags,
- negatedClassAccessFlags, classTypeNegated, classType, classNames, inheritanceAnnotation,
- inheritanceClassName, inheritanceIsExtends, memberRules, type, modifiersBuilder.build());
+ return new ProguardKeepRule(
+ origin,
+ getPosition(),
+ source,
+ buildClassAnnotations(),
+ classAccessFlags,
+ negatedClassAccessFlags,
+ classTypeNegated,
+ classType,
+ classNames,
+ buildInheritanceAnnotations(),
+ inheritanceClassName,
+ inheritanceIsExtends,
+ memberRules,
+ type,
+ modifiersBuilder.build());
}
}
@@ -35,21 +48,34 @@
Origin origin,
Position position,
String source,
- ProguardTypeMatcher classAnnotation,
+ List<ProguardTypeMatcher> classAnnotations,
ProguardAccessFlags classAccessFlags,
ProguardAccessFlags negatedClassAccessFlags,
boolean classTypeNegated,
ProguardClassType classType,
ProguardClassNameList classNames,
- ProguardTypeMatcher inheritanceAnnotation,
+ List<ProguardTypeMatcher> inheritanceAnnotations,
ProguardTypeMatcher inheritanceClassName,
boolean inheritanceIsExtends,
List<ProguardMemberRule> memberRules,
ProguardKeepRuleType type,
ProguardKeepRuleModifiers modifiers) {
- super(origin, position, source, classAnnotation, classAccessFlags, negatedClassAccessFlags,
- classTypeNegated, classType, classNames, inheritanceAnnotation, inheritanceClassName,
- inheritanceIsExtends, memberRules, type, modifiers);
+ super(
+ origin,
+ position,
+ source,
+ classAnnotations,
+ classAccessFlags,
+ negatedClassAccessFlags,
+ classTypeNegated,
+ classType,
+ classNames,
+ inheritanceAnnotations,
+ inheritanceClassName,
+ inheritanceIsExtends,
+ memberRules,
+ type,
+ modifiers);
}
/**
@@ -64,15 +90,13 @@
getOrigin(),
getPosition(),
getSource(),
- getClassAnnotation() == null ? null : getClassAnnotation().materialize(dexItemFactory),
+ ProguardTypeMatcher.materializeList(getClassAnnotations(), dexItemFactory),
getClassAccessFlags(),
getNegatedClassAccessFlags(),
getClassTypeNegated(),
getClassType(),
getClassNames() == null ? null : getClassNames().materialize(dexItemFactory),
- getInheritanceAnnotation() == null
- ? null
- : getInheritanceAnnotation().materialize(dexItemFactory),
+ ProguardTypeMatcher.materializeList(getInheritanceAnnotations(), dexItemFactory),
getInheritanceClassName() == null
? null
: getInheritanceClassName().materialize(dexItemFactory),
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleBase.java b/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleBase.java
index 7455469..b87e5a6 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleBase.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleBase.java
@@ -43,21 +43,32 @@
Origin origin,
Position position,
String source,
- ProguardTypeMatcher classAnnotation,
+ List<ProguardTypeMatcher> classAnnotations,
ProguardAccessFlags classAccessFlags,
ProguardAccessFlags negatedClassAccessFlags,
boolean classTypeNegated,
ProguardClassType classType,
ProguardClassNameList classNames,
- ProguardTypeMatcher inheritanceAnnotation,
+ List<ProguardTypeMatcher> inheritanceAnnotations,
ProguardTypeMatcher inheritanceClassName,
boolean inheritanceIsExtends,
List<ProguardMemberRule> memberRules,
ProguardKeepRuleType type,
ProguardKeepRuleModifiers modifiers) {
- super(origin, position, source, classAnnotation, classAccessFlags, negatedClassAccessFlags,
- classTypeNegated, classType, classNames, inheritanceAnnotation, inheritanceClassName,
- inheritanceIsExtends, memberRules);
+ super(
+ origin,
+ position,
+ source,
+ classAnnotations,
+ classAccessFlags,
+ negatedClassAccessFlags,
+ classTypeNegated,
+ classType,
+ classNames,
+ inheritanceAnnotations,
+ inheritanceClassName,
+ inheritanceIsExtends,
+ memberRules);
this.type = type;
this.modifiers = modifiers;
}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
index 1a894d3..3034f6d 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
@@ -24,7 +24,7 @@
public static class Builder {
- private ProguardTypeMatcher annotation;
+ private List<ProguardTypeMatcher> annotations = Collections.emptyList();
private ProguardAccessFlags accessFlags = new ProguardAccessFlags();
private ProguardAccessFlags negatedAccessFlags = new ProguardAccessFlags();
private ProguardMemberType ruleType;
@@ -35,8 +35,9 @@
private Builder() {}
- public void setAnnotation(ProguardTypeMatcher annotation) {
- this.annotation = annotation;
+ public void setAnnotations(List<ProguardTypeMatcher> annotations) {
+ assert annotations != null;
+ this.annotations = annotations;
}
public ProguardAccessFlags getAccessFlags() {
@@ -91,12 +92,19 @@
public ProguardMemberRule build() {
assert isValid();
- return new ProguardMemberRule(annotation, accessFlags, negatedAccessFlags, ruleType, type,
- name, arguments, returnValue);
+ return new ProguardMemberRule(
+ annotations,
+ accessFlags,
+ negatedAccessFlags,
+ ruleType,
+ type,
+ name,
+ arguments,
+ returnValue);
}
}
- private final ProguardTypeMatcher annotation;
+ private final List<ProguardTypeMatcher> annotations;
private final ProguardAccessFlags accessFlags;
private final ProguardAccessFlags negatedAccessFlags;
private final ProguardMemberType ruleType;
@@ -106,7 +114,7 @@
private final ProguardMemberRuleReturnValue returnValue;
private ProguardMemberRule(
- ProguardTypeMatcher annotation,
+ List<ProguardTypeMatcher> annotations,
ProguardAccessFlags accessFlags,
ProguardAccessFlags negatedAccessFlags,
ProguardMemberType ruleType,
@@ -114,7 +122,7 @@
ProguardNameMatcher name,
List<ProguardTypeMatcher> arguments,
ProguardMemberRuleReturnValue returnValue) {
- this.annotation = annotation;
+ this.annotations = annotations;
this.accessFlags = accessFlags;
this.negatedAccessFlags = negatedAccessFlags;
this.ruleType = ruleType;
@@ -131,8 +139,8 @@
return new Builder();
}
- public ProguardTypeMatcher getAnnotation() {
- return annotation;
+ public List<ProguardTypeMatcher> getAnnotations() {
+ return annotations;
}
public ProguardAccessFlags getAccessFlags() {
@@ -187,7 +195,8 @@
break;
}
// Annotations check.
- return RootSetBuilder.containsAnnotation(annotation, field, matchedAnnotationsConsumer);
+ return RootSetBuilder.containsAllAnnotations(
+ annotations, field, matchedAnnotationsConsumer);
}
case FIELD:
@@ -207,7 +216,8 @@
break;
}
// Annotations check
- return RootSetBuilder.containsAnnotation(annotation, field, matchedAnnotationsConsumer);
+ return RootSetBuilder.containsAllAnnotations(
+ annotations, field, matchedAnnotationsConsumer);
}
case ALL_METHODS:
@@ -241,7 +251,8 @@
break;
}
// Annotations check.
- return RootSetBuilder.containsAnnotation(annotation, method, matchedAnnotationsConsumer);
+ return RootSetBuilder.containsAllAnnotations(
+ annotations, method, matchedAnnotationsConsumer);
}
case METHOD:
@@ -266,7 +277,8 @@
break;
}
// Annotations check.
- if (!RootSetBuilder.containsAnnotation(annotation, method, matchedAnnotationsConsumer)) {
+ if (!RootSetBuilder.containsAllAnnotations(
+ annotations, method, matchedAnnotationsConsumer)) {
return false;
}
// Parameter types check.
@@ -309,21 +321,20 @@
Iterable<ProguardWildcard> getWildcards() {
return Iterables.concat(
- ProguardTypeMatcher.getWildcardsOrEmpty(annotation),
+ ProguardTypeMatcher.getWildcardsOrEmpty(annotations),
ProguardTypeMatcher.getWildcardsOrEmpty(type),
ProguardNameMatcher.getWildcardsOrEmpty(name),
arguments != null
? arguments.stream()
- .map(ProguardTypeMatcher::getWildcards)
- .flatMap(it -> StreamSupport.stream(it.spliterator(), false))
+ .map(ProguardTypeMatcher::getWildcards)
+ .flatMap(it -> StreamSupport.stream(it.spliterator(), false))
::iterator
- : Collections::emptyIterator
- );
+ : Collections::emptyIterator);
}
ProguardMemberRule materialize(DexItemFactory dexItemFactory) {
return new ProguardMemberRule(
- getAnnotation() == null ? null : getAnnotation().materialize(dexItemFactory),
+ ProguardTypeMatcher.materializeList(getAnnotations(), dexItemFactory),
getAccessFlags(),
getNegatedAccessFlags(),
getRuleType(),
@@ -345,7 +356,7 @@
ProguardMemberRule that = (ProguardMemberRule) o;
- if (annotation != null ? !annotation.equals(that.annotation) : that.annotation != null) {
+ if (!annotations.equals(that.annotations)) {
return false;
}
if (!accessFlags.equals(that.accessFlags)) {
@@ -368,7 +379,7 @@
@Override
public int hashCode() {
- int result = annotation != null ? annotation.hashCode() : 0;
+ int result = annotations.hashCode();
result = 31 * result + accessFlags.hashCode();
result = 31 * result + negatedAccessFlags.hashCode();
result = 31 * result + (ruleType != null ? ruleType.hashCode() : 0);
@@ -381,7 +392,9 @@
@Override
public String toString() {
StringBuilder result = new StringBuilder();
- ProguardKeepRule.appendNonEmpty(result, "@", annotation, " ");
+ for (ProguardTypeMatcher annotation : annotations) {
+ ProguardKeepRule.appendNonEmpty(result, "@", annotation, " ");
+ }
ProguardKeepRule.appendNonEmpty(result, null, accessFlags, " ");
ProguardKeepRule
.appendNonEmpty(result, null, negatedAccessFlags.toString().replace(" ", " !"), " ");
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardTypeMatcher.java b/src/main/java/com/android/tools/r8/shaking/ProguardTypeMatcher.java
index a4db6a2..19bd876 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardTypeMatcher.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardTypeMatcher.java
@@ -36,6 +36,10 @@
TYPE
}
+ public MatchSpecificType asSpecificTypeMatcher() {
+ return null;
+ }
+
// Evaluates this matcher on the given type.
public abstract boolean matches(DexType type);
@@ -59,10 +63,30 @@
return typeMatcher == null ? Collections::emptyIterator : typeMatcher.getWildcards();
}
+ static Iterable<ProguardWildcard> getWildcardsOrEmpty(List<ProguardTypeMatcher> typeMatchers) {
+ List<ProguardWildcard> result = new ArrayList<>();
+ for (ProguardTypeMatcher typeMatcher : typeMatchers) {
+ typeMatcher.getWildcards().forEach(result::add);
+ }
+ return result;
+ }
+
protected ProguardTypeMatcher materialize(DexItemFactory dexItemFactory) {
return this;
}
+ public static List<ProguardTypeMatcher> materializeList(
+ List<ProguardTypeMatcher> matchers, DexItemFactory dexItemFactory) {
+ if (matchers.isEmpty()) {
+ return Collections.emptyList();
+ }
+ ImmutableList.Builder<ProguardTypeMatcher> builder = ImmutableList.builder();
+ for (ProguardTypeMatcher matcher : matchers) {
+ builder.add(matcher.materialize(dexItemFactory));
+ }
+ return builder.build();
+ }
+
@Override
public abstract String toString();
@@ -312,6 +336,11 @@
}
@Override
+ public MatchSpecificType asSpecificTypeMatcher() {
+ return this;
+ }
+
+ @Override
public boolean matches(DexType type) {
return this.type == type;
}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardWhyAreYouKeepingRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardWhyAreYouKeepingRule.java
index 87200fb..4083ff5 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardWhyAreYouKeepingRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardWhyAreYouKeepingRule.java
@@ -23,9 +23,20 @@
@Override
public ProguardWhyAreYouKeepingRule build() {
- return new ProguardWhyAreYouKeepingRule(origin, getPosition(), source, classAnnotation,
- classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType, classNames,
- inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
+ return new ProguardWhyAreYouKeepingRule(
+ origin,
+ getPosition(),
+ source,
+ buildClassAnnotations(),
+ classAccessFlags,
+ negatedClassAccessFlags,
+ classTypeNegated,
+ classType,
+ classNames,
+ buildInheritanceAnnotations(),
+ inheritanceClassName,
+ inheritanceIsExtends,
+ memberRules);
}
}
@@ -33,19 +44,30 @@
Origin origin,
Position position,
String source,
- ProguardTypeMatcher classAnnotation,
+ List<ProguardTypeMatcher> classAnnotations,
ProguardAccessFlags classAccessFlags,
ProguardAccessFlags negatedClassAccessFlags,
boolean classTypeNegated,
ProguardClassType classType,
ProguardClassNameList classNames,
- ProguardTypeMatcher inheritanceAnnotation,
+ List<ProguardTypeMatcher> inheritanceAnnotations,
ProguardTypeMatcher inheritanceClassName,
boolean inheritanceIsExtends,
List<ProguardMemberRule> memberRules) {
- super(origin, position, source, classAnnotation, classAccessFlags, negatedClassAccessFlags,
- classTypeNegated, classType, classNames, inheritanceAnnotation, inheritanceClassName,
- inheritanceIsExtends, memberRules);
+ super(
+ origin,
+ position,
+ source,
+ classAnnotations,
+ classAccessFlags,
+ negatedClassAccessFlags,
+ classTypeNegated,
+ classType,
+ classNames,
+ inheritanceAnnotations,
+ inheritanceClassName,
+ inheritanceIsExtends,
+ memberRules);
}
public static Builder builder() {
diff --git a/src/main/java/com/android/tools/r8/shaking/ReprocessClassInitializerRule.java b/src/main/java/com/android/tools/r8/shaking/ReprocessClassInitializerRule.java
index 58e03d9..cfcd1fc 100644
--- a/src/main/java/com/android/tools/r8/shaking/ReprocessClassInitializerRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ReprocessClassInitializerRule.java
@@ -40,13 +40,13 @@
origin,
getPosition(),
source,
- classAnnotation,
+ buildClassAnnotations(),
classAccessFlags,
negatedClassAccessFlags,
classTypeNegated,
classType,
classNames,
- inheritanceAnnotation,
+ buildInheritanceAnnotations(),
inheritanceClassName,
inheritanceIsExtends,
memberRules,
@@ -60,13 +60,13 @@
Origin origin,
Position position,
String source,
- ProguardTypeMatcher classAnnotation,
+ List<ProguardTypeMatcher> classAnnotations,
ProguardAccessFlags classAccessFlags,
ProguardAccessFlags negatedClassAccessFlags,
boolean classTypeNegated,
ProguardClassType classType,
ProguardClassNameList classNames,
- ProguardTypeMatcher inheritanceAnnotation,
+ List<ProguardTypeMatcher> inheritanceAnnotations,
ProguardTypeMatcher inheritanceClassName,
boolean inheritanceIsExtends,
List<ProguardMemberRule> memberRules,
@@ -75,13 +75,13 @@
origin,
position,
source,
- classAnnotation,
+ classAnnotations,
classAccessFlags,
negatedClassAccessFlags,
classTypeNegated,
classType,
classNames,
- inheritanceAnnotation,
+ inheritanceAnnotations,
inheritanceClassName,
inheritanceIsExtends,
memberRules);
diff --git a/src/main/java/com/android/tools/r8/shaking/ReprocessMethodRule.java b/src/main/java/com/android/tools/r8/shaking/ReprocessMethodRule.java
index 8e8e288..2c7d19a 100644
--- a/src/main/java/com/android/tools/r8/shaking/ReprocessMethodRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ReprocessMethodRule.java
@@ -40,13 +40,13 @@
origin,
getPosition(),
source,
- classAnnotation,
+ buildClassAnnotations(),
classAccessFlags,
negatedClassAccessFlags,
classTypeNegated,
classType,
classNames,
- inheritanceAnnotation,
+ buildInheritanceAnnotations(),
inheritanceClassName,
inheritanceIsExtends,
memberRules,
@@ -60,13 +60,13 @@
Origin origin,
Position position,
String source,
- ProguardTypeMatcher classAnnotation,
+ List<ProguardTypeMatcher> classAnnotations,
ProguardAccessFlags classAccessFlags,
ProguardAccessFlags negatedClassAccessFlags,
boolean classTypeNegated,
ProguardClassType classType,
ProguardClassNameList classNames,
- ProguardTypeMatcher inheritanceAnnotation,
+ List<ProguardTypeMatcher> inheritanceAnnotations,
ProguardTypeMatcher inheritanceClassName,
boolean inheritanceIsExtends,
List<ProguardMemberRule> memberRules,
@@ -75,13 +75,13 @@
origin,
position,
source,
- classAnnotation,
+ classAnnotations,
classAccessFlags,
negatedClassAccessFlags,
classTypeNegated,
classType,
classNames,
- inheritanceAnnotation,
+ inheritanceAnnotations,
inheritanceClassName,
inheritanceIsExtends,
memberRules);
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 9abf328..b38d077 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -5,6 +5,7 @@
import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+import com.android.tools.r8.Diagnostic;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppInfo;
@@ -763,7 +764,7 @@
}
static AnnotationMatchResult satisfyAnnotation(ProguardConfigurationRule rule, DexClass clazz) {
- return containsAnnotation(rule.getClassAnnotation(), clazz);
+ return containsAllAnnotations(rule.getClassAnnotations(), clazz);
}
boolean satisfyInheritanceRule(DexClass clazz, ProguardConfigurationRule rule) {
@@ -795,7 +796,7 @@
// annotations of `class`.
if (rule.getInheritanceClassName().matches(clazz.type, appView)) {
AnnotationMatchResult annotationMatchResult =
- containsAnnotation(rule.getInheritanceAnnotation(), clazz);
+ containsAllAnnotations(rule.getInheritanceAnnotations(), clazz);
if (annotationMatchResult != null) {
handleMatchedAnnotation(annotationMatchResult);
return true;
@@ -832,7 +833,7 @@
// annotations of `ifaceClass`.
if (rule.getInheritanceClassName().matches(iface, appView)) {
AnnotationMatchResult annotationMatchResult =
- containsAnnotation(rule.getInheritanceAnnotation(), ifaceClass);
+ containsAllAnnotations(rule.getInheritanceAnnotations(), ifaceClass);
if (annotationMatchResult != null) {
handleMatchedAnnotation(annotationMatchResult);
return true;
@@ -908,17 +909,18 @@
return false;
}
- static AnnotationMatchResult containsAnnotation(
- ProguardTypeMatcher classAnnotation, DexClass clazz) {
- return containsAnnotation(classAnnotation, clazz.annotations());
+ static AnnotationMatchResult containsAllAnnotations(
+ List<ProguardTypeMatcher> annotationMatchers, DexClass clazz) {
+ return containsAllAnnotations(annotationMatchers, clazz.annotations());
}
- static <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>> boolean containsAnnotation(
- ProguardTypeMatcher classAnnotation,
- DexEncodedMember<D, R> member,
- Consumer<AnnotationMatchResult> matchedAnnotationsConsumer) {
+ static <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>>
+ boolean containsAllAnnotations(
+ List<ProguardTypeMatcher> annotationMatchers,
+ DexEncodedMember<D, R> member,
+ Consumer<AnnotationMatchResult> matchedAnnotationsConsumer) {
AnnotationMatchResult annotationMatchResult =
- containsAnnotation(classAnnotation, member.annotations());
+ containsAllAnnotations(annotationMatchers, member.annotations());
if (annotationMatchResult != null) {
matchedAnnotationsConsumer.accept(annotationMatchResult);
return true;
@@ -927,7 +929,7 @@
DexEncodedMethod method = member.asDexEncodedMethod();
for (int i = 0; i < method.parameterAnnotationsList.size(); i++) {
annotationMatchResult =
- containsAnnotation(classAnnotation, method.parameterAnnotationsList.get(i));
+ containsAllAnnotations(annotationMatchers, method.parameterAnnotationsList.get(i));
if (annotationMatchResult != null) {
matchedAnnotationsConsumer.accept(annotationMatchResult);
return true;
@@ -937,14 +939,28 @@
return false;
}
- private static AnnotationMatchResult containsAnnotation(
- ProguardTypeMatcher classAnnotation, DexAnnotationSet annotations) {
- if (classAnnotation == null) {
+ private static AnnotationMatchResult containsAllAnnotations(
+ List<ProguardTypeMatcher> annotationMatchers, DexAnnotationSet annotations) {
+ if (annotationMatchers.isEmpty()) {
return AnnotationsIgnoredMatchResult.getInstance();
}
+ List<DexAnnotation> matchedAnnotations = new ArrayList<>();
+ for (ProguardTypeMatcher annotationMatcher : annotationMatchers) {
+ DexAnnotation matchedAnnotation =
+ getFirstAnnotationThatMatches(annotationMatcher, annotations);
+ if (matchedAnnotation == null) {
+ return null;
+ }
+ matchedAnnotations.add(matchedAnnotation);
+ }
+ return new ConcreteAnnotationMatchResult(matchedAnnotations);
+ }
+
+ private static DexAnnotation getFirstAnnotationThatMatches(
+ ProguardTypeMatcher annotationMatcher, DexAnnotationSet annotations) {
for (DexAnnotation annotation : annotations.annotations) {
- if (classAnnotation.matches(annotation.annotation.type)) {
- return new ConcreteAnnotationMatchResult(annotation);
+ if (annotationMatcher.matches(annotation.getAnnotationType())) {
+ return annotation;
}
}
return null;
@@ -1656,38 +1672,50 @@
options.getProguardConfiguration() != null
? options.getProguardConfiguration().getDontWarnPatterns()
: ProguardClassFilter.empty();
- if (dontWarnPatterns.matches(options.itemFactory.objectType)) {
- return;
- }
assumeNoSideEffectsWarnings.forEach(
(originWithPosition, methods) -> {
boolean waitOrNotifyMethods = methods.stream().anyMatch(this::isWaitOrNotifyMethod);
+ boolean dontWarnObject = dontWarnPatterns.matches(options.itemFactory.objectType);
StringBuilder message = new StringBuilder();
message.append(
"The -assumenosideeffects rule matches methods on `java.lang.Object` with wildcards");
- if (waitOrNotifyMethods) {
- message.append(" including the method(s) ");
- for (int i = 0; i < methods.size(); i++) {
- if (i > 0) {
- message.append(i < methods.size() - 1 ? ", " : " and ");
- }
- message.append("`");
- message.append(methods.get(i).toSourceStringWithoutHolder());
- message.append("`");
+ message.append(" including the method(s) ");
+ for (int i = 0; i < methods.size(); i++) {
+ if (i > 0) {
+ message.append(i < methods.size() - 1 ? ", " : " and ");
}
- message.append(". ");
- message.append("This will most likely cause problems. ");
- } else {
- message.append(". ");
- message.append("This is most likely not intended. ");
+ message.append("`");
+ message.append(methods.get(i).toSourceStringWithoutHolder());
+ message.append("`.");
}
- message.append("Consider specifying the methods more precisely.");
- options.reporter.warning(
+ if (waitOrNotifyMethods) {
+ message.append(" This will most likely cause problems.");
+ } else {
+ message.append(" This is most likely not intended.");
+ }
+ if (waitOrNotifyMethods && !dontWarnObject) {
+ message.append(" Specify the methods more precisely.");
+ } else {
+ message.append(" Consider specifying the methods more precisely.");
+ }
+ Diagnostic diagnostic =
new StringDiagnostic(
message.toString(),
originWithPosition.getOrigin(),
- originWithPosition.getPosition()));
+ originWithPosition.getPosition());
+ if (waitOrNotifyMethods) {
+ if (!dontWarnObject) {
+ options.reporter.error(diagnostic);
+ } else {
+ options.reporter.warning(diagnostic);
+ }
+
+ } else {
+ if (!dontWarnObject) {
+ options.reporter.warning(diagnostic);
+ }
+ }
});
}
diff --git a/src/main/java/com/android/tools/r8/shaking/UnusedArgumentRule.java b/src/main/java/com/android/tools/r8/shaking/UnusedArgumentRule.java
index 1098bcd..dd537bc 100644
--- a/src/main/java/com/android/tools/r8/shaking/UnusedArgumentRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/UnusedArgumentRule.java
@@ -27,13 +27,13 @@
origin,
getPosition(),
source,
- classAnnotation,
+ buildClassAnnotations(),
classAccessFlags,
negatedClassAccessFlags,
classTypeNegated,
classType,
classNames,
- inheritanceAnnotation,
+ buildInheritanceAnnotations(),
inheritanceClassName,
inheritanceIsExtends,
memberRules);
@@ -44,13 +44,13 @@
Origin origin,
Position position,
String source,
- ProguardTypeMatcher classAnnotation,
+ List<ProguardTypeMatcher> classAnnotations,
ProguardAccessFlags classAccessFlags,
ProguardAccessFlags negatedClassAccessFlags,
boolean classTypeNegated,
ProguardClassType classType,
ProguardClassNameList classNames,
- ProguardTypeMatcher inheritanceAnnotation,
+ List<ProguardTypeMatcher> inheritanceAnnotations,
ProguardTypeMatcher inheritanceClassName,
boolean inheritanceIsExtends,
List<ProguardMemberRule> memberRules) {
@@ -58,13 +58,13 @@
origin,
position,
source,
- classAnnotation,
+ classAnnotations,
classAccessFlags,
negatedClassAccessFlags,
classTypeNegated,
classType,
classNames,
- inheritanceAnnotation,
+ inheritanceAnnotations,
inheritanceClassName,
inheritanceIsExtends,
memberRules);
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 bd3b4f8..79d472d 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -997,9 +997,7 @@
&& !methodPoolForTarget.hasSeen(
MethodSignatureEquivalence.get().wrap(method)),
Rename.ALWAYS,
- appView
- .dexItemFactory()
- .prependTypeToProto(virtualMethod.holder(), virtualMethod.method.proto));
+ appView.dexItemFactory().prependHolderToProto(virtualMethod.getReference()));
makeStatic(resultingDirectMethod);
// Update method pool collection now that we are adding a new public method.
diff --git a/src/main/java/com/android/tools/r8/shaking/WhyAreYouNotInliningRule.java b/src/main/java/com/android/tools/r8/shaking/WhyAreYouNotInliningRule.java
index aa52ca3..0f2f684 100644
--- a/src/main/java/com/android/tools/r8/shaking/WhyAreYouNotInliningRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/WhyAreYouNotInliningRule.java
@@ -27,13 +27,13 @@
origin,
getPosition(),
source,
- classAnnotation,
+ buildClassAnnotations(),
classAccessFlags,
negatedClassAccessFlags,
classTypeNegated,
classType,
classNames,
- inheritanceAnnotation,
+ buildInheritanceAnnotations(),
inheritanceClassName,
inheritanceIsExtends,
memberRules);
@@ -44,13 +44,13 @@
Origin origin,
Position position,
String source,
- ProguardTypeMatcher classAnnotation,
+ List<ProguardTypeMatcher> classAnnotations,
ProguardAccessFlags classAccessFlags,
ProguardAccessFlags negatedClassAccessFlags,
boolean classTypeNegated,
ProguardClassType classType,
ProguardClassNameList classNames,
- ProguardTypeMatcher inheritanceAnnotation,
+ List<ProguardTypeMatcher> inheritanceAnnotations,
ProguardTypeMatcher inheritanceClassName,
boolean inheritanceIsExtends,
List<ProguardMemberRule> memberRules) {
@@ -58,13 +58,13 @@
origin,
position,
source,
- classAnnotation,
+ classAnnotations,
classAccessFlags,
negatedClassAccessFlags,
classTypeNegated,
classType,
classNames,
- inheritanceAnnotation,
+ inheritanceAnnotations,
inheritanceClassName,
inheritanceIsExtends,
memberRules);
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 00a2136..35e4614 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -867,7 +867,7 @@
DexType libraryType,
DexType invalidSuperType,
String message,
- Set<DexMethod> retarget) {
+ Set<DexEncodedMethod> retarget) {
if (invalidLibraryClasses.add(invalidSuperType)) {
reporter.warning(
new InvalidLibrarySuperclassDiagnostic(
@@ -875,7 +875,7 @@
Reference.classFromDescriptor(libraryType.toDescriptorString()),
Reference.classFromDescriptor(invalidSuperType.toDescriptorString()),
message,
- ListUtils.map(retarget, DexMethod::asMethodReference)));
+ ListUtils.map(retarget, method -> method.getReference().asMethodReference())));
}
}
@@ -1210,10 +1210,11 @@
public Comparator<DexMethod> interfaceMethodOrdering = null;
- public Comparator<Wrapper<DexMethod>> getInterfaceMethodOrderingOrDefault(
- Comparator<Wrapper<DexMethod>> comparator) {
+ public Comparator<Wrapper<DexEncodedMethod>> getInterfaceMethodOrderingOrDefault(
+ Comparator<Wrapper<DexEncodedMethod>> comparator) {
if (interfaceMethodOrdering != null) {
- return (a, b) -> interfaceMethodOrdering.compare(a.get(), b.get());
+ return (a, b) ->
+ interfaceMethodOrdering.compare(a.get().getReference(), b.get().getReference());
}
return comparator;
}
diff --git a/src/main/java/com/android/tools/r8/utils/WorkList.java b/src/main/java/com/android/tools/r8/utils/WorkList.java
index 3e35802..d8bd420 100644
--- a/src/main/java/com/android/tools/r8/utils/WorkList.java
+++ b/src/main/java/com/android/tools/r8/utils/WorkList.java
@@ -65,6 +65,14 @@
return !workingList.isEmpty();
}
+ public void markAsSeen(T item) {
+ seen.add(item);
+ }
+
+ public void markAsSeen(Iterable<T> items) {
+ items.forEach(this::markAsSeen);
+ }
+
public T next() {
assert hasNext();
return workingList.removeFirst();
diff --git a/src/test/java/com/android/tools/r8/D8TestBuilder.java b/src/test/java/com/android/tools/r8/D8TestBuilder.java
index edfb00c..1496847 100644
--- a/src/test/java/com/android/tools/r8/D8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/D8TestBuilder.java
@@ -74,12 +74,9 @@
@Override
public D8TestBuilder enableCoreLibraryDesugaring(
- AndroidApiLevel minAPILevel, KeepRuleConsumer keepRuleConsumer) {
- if (minAPILevel.getLevel() < AndroidApiLevel.O.getLevel()) {
- // Use P to mimic current Android Studio.
- builder.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P));
- builder.addDesugaredLibraryConfiguration(
- StringResource.fromFile(ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING));
+ AndroidApiLevel minApiLevel, KeepRuleConsumer keepRuleConsumer) {
+ if (minApiLevel.getLevel() < AndroidApiLevel.O.getLevel()) {
+ super.enableCoreLibraryDesugaring(minApiLevel, keepRuleConsumer);
builder.setDesugaredLibraryKeepRuleConsumer(keepRuleConsumer);
}
return self();
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index 9d0684d..4fc59c6 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -561,12 +561,9 @@
@Override
public T enableCoreLibraryDesugaring(
- AndroidApiLevel minAPILevel, KeepRuleConsumer keepRuleConsumer) {
- if (minAPILevel.getLevel() < AndroidApiLevel.O.getLevel()) {
- // Use P to mimic current Android Studio.
- builder.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P));
- builder.addDesugaredLibraryConfiguration(
- StringResource.fromFile(ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING));
+ AndroidApiLevel minApiLevel, KeepRuleConsumer keepRuleConsumer) {
+ if (minApiLevel.getLevel() < AndroidApiLevel.O.getLevel()) {
+ super.enableCoreLibraryDesugaring(minApiLevel, keepRuleConsumer);
builder.setDesugaredLibraryKeepRuleConsumer(keepRuleConsumer);
}
return self();
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index 490cf54..ce6c9fb 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -10,7 +10,6 @@
import com.android.tools.r8.TestBase.Backend;
import com.android.tools.r8.debug.DebugTestConfig;
import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase.KeepRuleConsumer;
-import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.testing.AndroidBuildVersion;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.AndroidApp;
@@ -93,8 +92,12 @@
return self();
}
- public T allowCheckDiscardedErrors() {
- return addOptionsModification(options -> options.testing.allowCheckDiscardedErrors = true);
+ public T allowCheckDiscardedErrors(boolean skipReporting) {
+ return addOptionsModification(
+ options -> {
+ options.testing.allowCheckDiscardedErrors = true;
+ options.testing.dontReportFailingCheckDiscarded = skipReporting;
+ });
}
public CR compile() throws CompilationFailedException {
@@ -390,8 +393,16 @@
}
public T enableCoreLibraryDesugaring(
- AndroidApiLevel minAPILevel, KeepRuleConsumer keepRuleConsumer) {
- throw new Unreachable("Should be overridden or is unsupported.");
+ AndroidApiLevel minApiLevel, KeepRuleConsumer keepRuleConsumer) {
+ assert minApiLevel.getLevel() < AndroidApiLevel.O.getLevel();
+ builder.addDesugaredLibraryConfiguration(
+ StringResource.fromFile(ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING));
+ // TODO(b/158543446): This should not be setting an implicit library file. Doing so causes
+ // inconsistent library setup depending on the api level and makes tests hard to read and
+ // reason about.
+ // Use P to mimic current Android Studio.
+ builder.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P));
+ return self();
}
@Override
diff --git a/src/test/java/com/android/tools/r8/desugar/DesugarLambdaWithAnonymousClass.java b/src/test/java/com/android/tools/r8/desugar/DesugarLambdaWithAnonymousClass.java
new file mode 100644
index 0000000..7192449
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/DesugarLambdaWithAnonymousClass.java
@@ -0,0 +1,251 @@
+// Copyright (c) 2020, 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.desugar;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.D8TestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class DesugarLambdaWithAnonymousClass extends TestBase {
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+ }
+
+ private final TestParameters parameters;
+
+ public DesugarLambdaWithAnonymousClass(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ static class Counter {
+ private int count = 0;
+
+ void increment() {
+ count++;
+ }
+
+ int getCount() {
+ return count;
+ }
+ }
+
+ private void checkEnclosingMethod(CodeInspector inspector) {
+ Counter counter = new Counter();
+ inspector.forAllClasses(
+ clazz -> {
+ if (clazz.getFinalName().endsWith("$TestClass$1")
+ || clazz.getFinalName().endsWith("$TestClass$2")) {
+ counter.increment();
+ assertTrue(clazz.isAnonymousClass());
+ DexMethod enclosingMethod = clazz.getFinalEnclosingMethod();
+ ClassSubject testClassSubject =
+ inspector.clazz(DesugarLambdaWithAnonymousClass.TestClass.class);
+ assertEquals(
+ testClassSubject, inspector.clazz(enclosingMethod.holder.toSourceString()));
+ assertThat(
+ testClassSubject.uniqueMethodWithName(enclosingMethod.name.toString()),
+ isPresent());
+ }
+ });
+ assertEquals(2, counter.getCount());
+ }
+
+ // TODO(158752316): There should be no use of this check.
+ private void checkEnclosingMethodWrong(CodeInspector inspector) {
+ Counter counter = new Counter();
+ inspector.forAllClasses(
+ clazz -> {
+ if (clazz.getFinalName().endsWith("$TestClass$1")
+ || clazz.getFinalName().endsWith("$TestClass$2")) {
+ counter.increment();
+ assertTrue(clazz.isAnonymousClass());
+ DexMethod enclosingMethod = clazz.getFinalEnclosingMethod();
+ ClassSubject testClassSubject =
+ inspector.clazz(DesugarLambdaWithAnonymousClass.TestClass.class);
+ assertEquals(
+ testClassSubject, inspector.clazz(enclosingMethod.holder.toSourceString()));
+ if (enclosingMethod.name.toString().contains("Static")) {
+ assertThat(
+ testClassSubject.uniqueMethodWithName(enclosingMethod.name.toString()),
+ isPresent());
+ } else {
+ assertThat(
+ testClassSubject.uniqueMethodWithName(enclosingMethod.name.toString()),
+ not(isPresent()));
+ }
+ }
+ });
+ assertEquals(2, counter.getCount());
+ }
+
+ private void checkArtResult(D8TestRunResult result) {
+ // TODO(158752316): This should neither return null nor fail.
+ if (parameters.getRuntime().asDex().getVm().getVersion().isOlderThanOrEqual(Version.V4_4_4)
+ || parameters.getRuntime().asDex().getVm().getVersion().isNewerThan(Version.V6_0_1)) {
+ result.assertSuccessWithOutputLines(
+ "Hello from inside <null>", "Hello from inside lambda$testStatic$1");
+ } else {
+ result.assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class);
+ }
+ }
+
+ @BeforeClass
+ public static void checkExpectedJavacNames() throws Exception {
+ CodeInspector inspector =
+ new CodeInspector(
+ ToolHelper.getClassFilesForInnerClasses(DesugarLambdaWithLocalClass.class));
+ String outer = DesugarLambdaWithLocalClass.class.getTypeName();
+ ClassSubject testClass = inspector.clazz(outer + "$TestClass");
+ assertThat(testClass, isPresent());
+ assertThat(testClass.uniqueMethodWithName("lambda$test$0"), isPresent());
+ assertThat(testClass.uniqueMethodWithName("lambda$testStatic$1"), isPresent());
+ assertThat(inspector.clazz(outer + "$TestClass$1MyConsumerImpl"), isPresent());
+ assertThat(inspector.clazz(outer + "$TestClass$2MyConsumerImpl"), isPresent());
+ }
+
+ @Test
+ public void testDefault() throws Exception {
+ if (parameters.getRuntime().isCf()) {
+ // Run on the JVM.
+ testForJvm()
+ .addInnerClasses(DesugarLambdaWithAnonymousClass.class)
+ .run(parameters.getRuntime(), TestClass.class)
+ .inspect(this::checkEnclosingMethod)
+ .assertSuccessWithOutputLines(
+ "Hello from inside lambda$test$0", "Hello from inside lambda$testStatic$1");
+ } else {
+ assert parameters.getRuntime().isDex();
+ // Run on Art.
+ checkArtResult(
+ testForD8()
+ .addInnerClasses(DesugarLambdaWithAnonymousClass.class)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::checkEnclosingMethodWrong)
+ .run(parameters.getRuntime(), TestClass.class));
+ }
+ }
+
+ @Test
+ public void testCfToCf() throws Exception {
+ // Use D8 to desugar with Java classfile output.
+ Path jar =
+ testForD8(Backend.CF)
+ .addInnerClasses(DesugarLambdaWithAnonymousClass.class)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::checkEnclosingMethodWrong)
+ .writeToZip();
+
+ if (parameters.getRuntime().isCf()) {
+ // Run on the JVM.
+ testForJvm()
+ .addProgramFiles(jar)
+ .run(parameters.getRuntime(), TestClass.class)
+ // TODO(158752316): This should not fail.
+ .assertFailureWithErrorThatThrows(InternalError.class);
+ } else {
+ assert parameters.getRuntime().isDex();
+ // Compile to DEX without desugaring and run on Art.
+ checkArtResult(
+ testForD8()
+ .addProgramFiles(jar)
+ .setMinApi(parameters.getApiLevel())
+ .disableDesugaring()
+ .compile()
+ .inspect(this::checkEnclosingMethodWrong)
+ .run(parameters.getRuntime(), TestClass.class));
+ }
+ }
+
+ public interface MyConsumer<T> {
+ void accept(T s);
+ }
+
+ public static class StringList extends ArrayList<String> {
+ public void forEachString(MyConsumer<String> consumer) {
+ for (String s : this) {
+ consumer.accept(s);
+ }
+ }
+ }
+
+ public static class TestClass {
+
+ public void test() {
+ StringList list = new StringList();
+
+ list.add("Hello ");
+ list.add("from ");
+ list.add("inside ");
+
+ list.forEachString(
+ s -> {
+ new MyConsumer<String>() {
+ public void accept(String s) {
+ System.out.print(s);
+ if (s.startsWith("inside")) {
+ if (getClass().getEnclosingMethod() == null) {
+ System.out.println("<null>");
+ } else {
+ System.out.println(getClass().getEnclosingMethod().getName());
+ }
+ }
+ }
+ }.accept(s);
+ });
+ }
+
+ public static void testStatic() {
+ StringList list = new StringList();
+
+ list.add("Hello ");
+ list.add("from ");
+ list.add("inside ");
+
+ list.forEachString(
+ s -> {
+ new MyConsumer<String>() {
+ public void accept(String s) {
+ System.out.print(s);
+ if (s.startsWith("inside")) {
+ if (getClass().getEnclosingMethod() == null) {
+ System.out.println("<null>");
+ } else {
+ System.out.println(getClass().getEnclosingMethod().getName());
+ }
+ }
+ }
+ }.accept(s);
+ });
+ }
+
+ public static void main(String[] args) {
+ new TestClass().test();
+ TestClass.testStatic();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/DesugarLambdaWithLocalClass.java b/src/test/java/com/android/tools/r8/desugar/DesugarLambdaWithLocalClass.java
new file mode 100644
index 0000000..68fd89e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/DesugarLambdaWithLocalClass.java
@@ -0,0 +1,250 @@
+// Copyright (c) 2020, 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.desugar;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.D8TestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class DesugarLambdaWithLocalClass extends TestBase {
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+ }
+
+ private final TestParameters parameters;
+
+ public DesugarLambdaWithLocalClass(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ static class Counter {
+ private int count = 0;
+
+ void increment() {
+ count++;
+ }
+
+ int getCount() {
+ return count;
+ }
+ }
+
+ private void checkEnclosingMethod(CodeInspector inspector) {
+ Counter counter = new Counter();
+ inspector.forAllClasses(
+ clazz -> {
+ if (clazz.getFinalName().endsWith("MyConsumerImpl")) {
+ counter.increment();
+ assertTrue(clazz.isLocalClass());
+ DexMethod enclosingMethod = clazz.getFinalEnclosingMethod();
+ ClassSubject testClassSubject = inspector.clazz(TestClass.class);
+ assertEquals(
+ testClassSubject, inspector.clazz(enclosingMethod.holder.toSourceString()));
+ assertThat(
+ testClassSubject.uniqueMethodWithName(enclosingMethod.name.toString()),
+ isPresent());
+ }
+ });
+ assertEquals(2, counter.getCount());
+ }
+
+ // TODO(158752316): There should be no use of this check.
+ private void checkEnclosingMethodWrong(CodeInspector inspector) {
+ Counter counter = new Counter();
+ inspector.forAllClasses(
+ clazz -> {
+ if (clazz.getFinalName().endsWith("$TestClass$1MyConsumerImpl")
+ || clazz.getFinalName().endsWith("$TestClass$2MyConsumerImpl")) {
+ counter.increment();
+ assertTrue(clazz.isLocalClass());
+ DexMethod enclosingMethod = clazz.getFinalEnclosingMethod();
+ ClassSubject testClassSubject = inspector.clazz(TestClass.class);
+ assertEquals(
+ testClassSubject, inspector.clazz(enclosingMethod.holder.toSourceString()));
+ if (enclosingMethod.name.toString().contains("Static")) {
+ assertThat(
+ testClassSubject.uniqueMethodWithName(enclosingMethod.name.toString()),
+ isPresent());
+ } else {
+ assertThat(
+ testClassSubject.uniqueMethodWithName(enclosingMethod.name.toString()),
+ not(isPresent()));
+ }
+ }
+ });
+ assertEquals(2, counter.getCount());
+ }
+
+ private void checkArtResult(D8TestRunResult result) {
+ // TODO(158752316): This should neither return null nor fail.
+ if (parameters.getRuntime().asDex().getVm().getVersion().isOlderThanOrEqual(Version.V4_4_4)
+ || parameters.getRuntime().asDex().getVm().getVersion().isNewerThan(Version.V6_0_1)) {
+ result.assertSuccessWithOutputLines(
+ "Hello from inside <null>", "Hello from inside lambda$testStatic$1");
+ } else {
+ result.assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class);
+ }
+ }
+
+ @BeforeClass
+ public static void checkExpectedJavacNames() throws Exception {
+ CodeInspector inspector =
+ new CodeInspector(
+ ToolHelper.getClassFilesForInnerClasses(DesugarLambdaWithAnonymousClass.class));
+ String outer = DesugarLambdaWithAnonymousClass.class.getTypeName();
+ ClassSubject testClass = inspector.clazz(outer + "$TestClass");
+ assertThat(testClass, isPresent());
+ assertThat(testClass.uniqueMethodWithName("lambda$test$0"), isPresent());
+ assertThat(testClass.uniqueMethodWithName("lambda$testStatic$1"), isPresent());
+ assertThat(inspector.clazz(outer + "$TestClass$1"), isPresent());
+ assertThat(inspector.clazz(outer + "$TestClass$2"), isPresent());
+ }
+
+ @Test
+ public void testDefault() throws Exception {
+ if (parameters.getRuntime().isCf()) {
+ // Run on the JVM.
+ testForJvm()
+ .addInnerClasses(DesugarLambdaWithLocalClass.class)
+ .run(parameters.getRuntime(), TestClass.class)
+ .inspect(this::checkEnclosingMethod)
+ .assertSuccessWithOutputLines(
+ "Hello from inside lambda$test$0", "Hello from inside lambda$testStatic$1");
+ } else {
+ assert parameters.getRuntime().isDex();
+ // Run on Art.
+ checkArtResult(
+ testForD8()
+ .addInnerClasses(DesugarLambdaWithLocalClass.class)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::checkEnclosingMethodWrong)
+ .run(parameters.getRuntime(), TestClass.class));
+ }
+ }
+
+ @Test
+ public void testCfToCf() throws Exception {
+ // Use D8 to desugar with Java classfile output.
+ Path jar =
+ testForD8(Backend.CF)
+ .addInnerClasses(DesugarLambdaWithLocalClass.class)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::checkEnclosingMethodWrong)
+ .writeToZip();
+
+ if (parameters.getRuntime().isCf()) {
+ // Run on the JVM.
+ testForJvm()
+ .addProgramFiles(jar)
+ .run(parameters.getRuntime(), TestClass.class)
+ // TODO(158752316): This should not fail.
+ .assertFailureWithErrorThatThrows(InternalError.class);
+ } else {
+ assert parameters.getRuntime().isDex();
+ // Compile to DEX without desugaring and run on Art.
+ checkArtResult(
+ testForD8()
+ .addProgramFiles(jar)
+ .setMinApi(parameters.getApiLevel())
+ .disableDesugaring()
+ .compile()
+ .inspect(this::checkEnclosingMethodWrong)
+ .run(parameters.getRuntime(), TestClass.class));
+ }
+ }
+
+ public interface MyConsumer<T> {
+ void accept(T s);
+ }
+
+ public static class StringList extends ArrayList<String> {
+ public void forEachString(MyConsumer<String> consumer) {
+ for (String s : this) {
+ consumer.accept(s);
+ }
+ }
+ }
+
+ public static class TestClass {
+
+ public void test() {
+ StringList list = new StringList();
+
+ list.add("Hello ");
+ list.add("from ");
+ list.add("inside ");
+
+ list.forEachString(
+ s -> {
+ class MyConsumerImpl implements MyConsumer<String> {
+ public void accept(String s) {
+ System.out.print(s);
+ if (s.startsWith("inside")) {
+ if (getClass().getEnclosingMethod() == null) {
+ System.out.println("<null>");
+ } else {
+ System.out.println(getClass().getEnclosingMethod().getName());
+ }
+ }
+ }
+ }
+ new MyConsumerImpl().accept(s);
+ });
+ }
+
+ public static void testStatic() {
+ StringList list = new StringList();
+
+ list.add("Hello ");
+ list.add("from ");
+ list.add("inside ");
+
+ list.forEachString(
+ s -> {
+ class MyConsumerImpl implements MyConsumer<String> {
+ public void accept(String s) {
+ System.out.print(s);
+ if (s.startsWith("inside")) {
+ if (getClass().getEnclosingMethod() == null) {
+ System.out.println("<null>");
+ } else {
+ System.out.println(getClass().getEnclosingMethod().getName());
+ }
+ }
+ }
+ }
+ new MyConsumerImpl().accept(s);
+ });
+ }
+
+ public static void main(String[] args) {
+ new TestClass().test();
+ TestClass.testStatic();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/InvokeSuperToEmulatedDefaultMethodTest.java b/src/test/java/com/android/tools/r8/desugar/InvokeSuperToEmulatedDefaultMethodTest.java
new file mode 100644
index 0000000..9d3d498
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/InvokeSuperToEmulatedDefaultMethodTest.java
@@ -0,0 +1,155 @@
+// Copyright (c) 2020, 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.desugar;
+
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class InvokeSuperToEmulatedDefaultMethodTest extends DesugaredLibraryTestBase {
+
+ private static final String EXPECTED = StringUtils.lines("bar", "bar");
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ private final TestParameters parameters;
+
+ public InvokeSuperToEmulatedDefaultMethodTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ private boolean needsDefaultInterfaceMethodDesugaring() {
+ return parameters.isDexRuntime()
+ && parameters.getApiLevel().isLessThan(apiLevelWithDefaultInterfaceMethodsSupport());
+ }
+
+ @Test
+ public void testReference() throws Exception {
+ assumeFalse(needsDefaultInterfaceMethodDesugaring());
+ testForRuntime(parameters)
+ .addInnerClasses(InvokeSuperToEmulatedDefaultMethodTest.class)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testDesugaring() throws Exception {
+ assumeTrue(needsDefaultInterfaceMethodDesugaring());
+ testForD8()
+ .addInnerClasses(InvokeSuperToEmulatedDefaultMethodTest.class)
+ .setMinApi(parameters.getApiLevel())
+ .enableCoreLibraryDesugaring(parameters.getApiLevel())
+ .compile()
+ .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, parameters.getApiLevel())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ public interface StringMap extends Map<String, String> {
+
+ @Override
+ default String getOrDefault(Object key, String defaultValue) {
+ // Simple forward that targets the desugared library.
+ return Map.super.getOrDefault(key + "oo", defaultValue);
+ }
+ }
+
+ public interface StringMapIndirection extends StringMap {
+
+ @Override
+ default String getOrDefault(Object key, String defaultValue) {
+ // Simple forward to a program defined default method.
+ return StringMap.super.getOrDefault("f", defaultValue);
+ }
+ }
+
+ public static class StringHashMap implements StringMapIndirection {
+ HashMap<String, String> delegate = new HashMap<>();
+
+ @Override
+ public int size() {
+ return delegate.size();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return delegate.isEmpty();
+ }
+
+ @Override
+ public boolean containsKey(Object key) {
+ return delegate.containsKey(key);
+ }
+
+ @Override
+ public boolean containsValue(Object value) {
+ return delegate.containsValue(value);
+ }
+
+ @Override
+ public String get(Object key) {
+ return delegate.get(key);
+ }
+
+ @Override
+ public String put(String key, String value) {
+ return delegate.put(key, value);
+ }
+
+ @Override
+ public String remove(Object key) {
+ return delegate.remove(key);
+ }
+
+ @Override
+ public void putAll(Map<? extends String, ? extends String> m) {
+ delegate.putAll(m);
+ }
+
+ @Override
+ public void clear() {
+ delegate.clear();
+ }
+
+ @Override
+ public Set<String> keySet() {
+ return delegate.keySet();
+ }
+
+ @Override
+ public Collection<String> values() {
+ return delegate.values();
+ }
+
+ @Override
+ public Set<Entry<String, String>> entrySet() {
+ return delegate.entrySet();
+ }
+ }
+
+ public static class TestClass {
+
+ public static void main(String[] args) {
+ StringHashMap map = new StringHashMap();
+ map.put("foo", "bar");
+ System.out.println(map.getOrDefault("foo", "not found"));
+ System.out.println(map.getOrDefault("bar", "not found"));
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/InvokeSuperToRewrittenDefaultMethodTest.java b/src/test/java/com/android/tools/r8/desugar/InvokeSuperToRewrittenDefaultMethodTest.java
new file mode 100644
index 0000000..f63c4d9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/InvokeSuperToRewrittenDefaultMethodTest.java
@@ -0,0 +1,139 @@
+// Copyright (c) 2020, 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.desugar;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.StringResource;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.ExceptionDiagnostic;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.function.IntConsumer;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class InvokeSuperToRewrittenDefaultMethodTest extends DesugaredLibraryTestBase {
+
+ private static final String EXPECTED = StringUtils.lines("Y", "89");
+
+ @Parameterized.Parameters(name = "{0}, old-rt:{1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
+ }
+
+ private final TestParameters parameters;
+ private final boolean rtWithoutConsumer;
+
+ public InvokeSuperToRewrittenDefaultMethodTest(
+ TestParameters parameters, boolean rtWithoutConsumer) {
+ this.parameters = parameters;
+ this.rtWithoutConsumer = rtWithoutConsumer;
+ }
+
+ private boolean needsDefaultInterfaceMethodDesugaring() {
+ return parameters.isDexRuntime()
+ && parameters.getApiLevel().isLessThan(apiLevelWithDefaultInterfaceMethodsSupport());
+ }
+
+ @Test
+ public void testReference() throws Exception {
+ assumeFalse(needsDefaultInterfaceMethodDesugaring());
+ testForRuntime(parameters)
+ .addInnerClasses(InvokeSuperToRewrittenDefaultMethodTest.class)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testDesugaring() throws Exception {
+ assumeTrue(needsDefaultInterfaceMethodDesugaring());
+ try {
+ testForD8()
+ .addInnerClasses(InvokeSuperToRewrittenDefaultMethodTest.class)
+ .setMinApi(parameters.getApiLevel())
+ .apply(
+ b -> {
+ if (rtWithoutConsumer) {
+ b.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.B));
+ // TODO(b/158543446): Remove this once enableCoreLibraryDesugaring is fixed.
+ b.getBuilder()
+ .addDesugaredLibraryConfiguration(
+ StringResource.fromFile(ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING));
+ } else {
+ // TODO(b/158543446): Move this out to the shared builder once
+ // enableCoreLibraryDesugaring is fixed.
+ b.enableCoreLibraryDesugaring(parameters.getApiLevel());
+ }
+ })
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ if (rtWithoutConsumer) {
+ diagnostics.assertOnlyErrors();
+ // TODO(b/158543011): Should fail with a nice user error for invalid library.
+ diagnostics.assertErrorsMatch(
+ allOf(
+ diagnosticType(ExceptionDiagnostic.class),
+ diagnosticMessage(containsString("AssertionError"))));
+ } else {
+ diagnostics.assertNoMessages();
+ }
+ })
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibrary, parameters.getApiLevel())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ assertFalse(rtWithoutConsumer);
+ } catch (CompilationFailedException e) {
+ assertTrue(rtWithoutConsumer);
+ }
+ }
+
+ @FunctionalInterface
+ public interface CharConsumer extends Consumer<Character>, IntConsumer {
+
+ void accept(char c);
+
+ @Override
+ default void accept(int value) {
+ accept((char) value);
+ }
+
+ @Override
+ default void accept(Character character) {
+ accept(character.charValue());
+ }
+
+ @Override
+ default Consumer<Character> andThen(Consumer<? super Character> after) {
+ // Simple forward to the default method of the parent.
+ // Must be rewritten to target the companion class of the rewritten Consumer type.
+ return Consumer.super.andThen(after);
+ }
+ }
+
+ public static class TestClass {
+
+ public static void main(String[] args) {
+ CharConsumer consumer = System.out::println;
+ consumer.andThen((Consumer<? super Character>) c -> System.out.println((int) c)).accept('Y');
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredGenericSignatureTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredGenericSignatureTest.java
new file mode 100644
index 0000000..ff07b24
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredGenericSignatureTest.java
@@ -0,0 +1,129 @@
+// Copyright (c) 2020, 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.desugar.desugaredlibrary;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.time.LocalDate;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class DesugaredGenericSignatureTest extends DesugaredLibraryTestBase {
+
+ private final TestParameters parameters;
+ private final boolean shrinkDesugaredLibrary;
+ private static final String EXPECTED = StringUtils.lines("1970", "1", "2");
+
+ @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withDexRuntimes().withAllApiLevels().build(), BooleanUtils.values());
+ }
+
+ public DesugaredGenericSignatureTest(TestParameters parameters, boolean shrinkDesugaredLibrary) {
+ this.parameters = parameters;
+ this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+ testForD8()
+ .addInnerClasses(DesugaredGenericSignatureTest.class)
+ .setMinApi(parameters.getApiLevel())
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+ .setIncludeClassesChecksum(true)
+ .compile()
+ .inspect(this::checkRewrittenSignature)
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibrary,
+ parameters.getApiLevel(),
+ keepRuleConsumer.get(),
+ shrinkDesugaredLibrary)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+ testForR8(parameters.getBackend())
+ .addInnerClasses(DesugaredGenericSignatureTest.class)
+ .addKeepMainRule(Main.class)
+ .addKeepAllClassesRuleWithAllowObfuscation()
+ .addKeepAttributes(
+ ProguardKeepAttributes.SIGNATURE,
+ ProguardKeepAttributes.INNER_CLASSES,
+ ProguardKeepAttributes.ENCLOSING_METHOD)
+ .enableInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+ .compile()
+ .inspect(this::checkRewrittenSignature)
+ .addDesugaredCoreLibraryRunClassPath(
+ this::buildDesugaredLibrary,
+ parameters.getApiLevel(),
+ keepRuleConsumer.get(),
+ shrinkDesugaredLibrary)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ private void checkRewrittenSignature(CodeInspector inspector) {
+ if (!requiresEmulatedInterfaceCoreLibDesugaring(parameters)) {
+ return;
+ }
+ ClassSubject javaTimeBox = inspector.clazz(JavaTimeDateBox.class);
+ assertThat(javaTimeBox, isPresent());
+ ClassSubject box = inspector.clazz(Box.class);
+ assertThat(box, isPresent());
+ String finalBoxDescriptor = box.getFinalDescriptor();
+ assertEquals(
+ "Ljava/lang/Object;"
+ + finalBoxDescriptor.substring(0, finalBoxDescriptor.length() - 1)
+ + "<Lj$/time/LocalDate;>;",
+ javaTimeBox.getFinalSignatureAttribute());
+ }
+
+ public interface Box<T> {
+ T addOne(T t);
+ }
+
+ public static class JavaTimeDateBox implements Box<java.time.LocalDate> {
+
+ @Override
+ @NeverInline
+ public LocalDate addOne(LocalDate localDate) {
+ return localDate.plusDays(1);
+ }
+ }
+
+ public static class Main {
+
+ public static Box<java.time.LocalDate> bar() {
+ return new JavaTimeDateBox();
+ }
+
+ public static void main(String[] args) {
+ LocalDate localDate = bar().addOne(LocalDate.of(1970, 1, 1));
+ System.out.println(localDate.getYear());
+ System.out.println(localDate.getMonthValue());
+ System.out.println(localDate.getDayOfMonth());
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DontKeepBootstrapClassesTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DontKeepBootstrapClassesTest.java
new file mode 100644
index 0000000..1d66c7f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DontKeepBootstrapClassesTest.java
@@ -0,0 +1,60 @@
+// Copyright (c) 2020, 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.desugar.desugaredlibrary;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.NoneRuntime;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.util.Arrays;
+import java.util.function.Consumer;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class DontKeepBootstrapClassesTest extends DesugaredLibraryTestBase {
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withNoneRuntime().build();
+ }
+
+ final AndroidApiLevel minApiLevel = AndroidApiLevel.B;
+
+ public DontKeepBootstrapClassesTest(TestParameters parameters) {
+ assertEquals(NoneRuntime.getInstance(), parameters.getRuntime());
+ }
+
+ @Test
+ public void test() throws Exception {
+ KeepRuleConsumer keepRuleConsumer = new PresentKeepRuleConsumer();
+ testForD8()
+ .addProgramClasses(TestClass.class)
+ .setMinApi(minApiLevel)
+ .addLibraryClasses(CustomLibClass.class)
+ .enableCoreLibraryDesugaring(minApiLevel, keepRuleConsumer)
+ .compile();
+ assertThat(keepRuleConsumer.get(), containsString("-keep class j$.util.function.Consumer"));
+ // TODO(b/158635415): Don't generate keep rules targeting items outside desugared library.
+ assertThat(keepRuleConsumer.get(), containsString("-keep class java.util"));
+ }
+
+ static class CustomLibClass {
+ public static <T> Consumer<T> id(Consumer<T> fn) {
+ return fn;
+ }
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ Arrays.asList("A", "B", "C").forEach(CustomLibClass.id(System.out::println));
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialTest.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialTest.java
deleted file mode 100644
index 642c263..0000000
--- a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialTest.java
+++ /dev/null
@@ -1,80 +0,0 @@
-// 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.graph.invokespecial;
-
-import static com.android.tools.r8.utils.DescriptorUtils.javaTypeToDescriptor;
-import static org.hamcrest.CoreMatchers.containsString;
-import static org.junit.Assume.assumeTrue;
-
-import com.android.tools.r8.AsmTestBase;
-import com.android.tools.r8.ByteDataView;
-import com.android.tools.r8.ClassFileConsumer;
-import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.DexVm.Version;
-import com.android.tools.r8.utils.StringUtils;
-import java.nio.file.Path;
-import org.junit.BeforeClass;
-import org.junit.ClassRule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-
-public class InvokeSpecialTest extends AsmTestBase {
-
- @ClassRule
- public static TemporaryFolder tempFolder = ToolHelper.getTemporaryFolderForTest();
-
- private static Path inputJar;
-
- @BeforeClass
- public static void setup() throws Exception {
- inputJar = tempFolder.getRoot().toPath().resolve("input.jar");
- ClassFileConsumer consumer = new ClassFileConsumer.ArchiveConsumer(inputJar);
- consumer.accept(
- ByteDataView.of(ToolHelper.getClassAsBytes(Main.class)),
- javaTypeToDescriptor(Main.class.getTypeName()),
- null);
- consumer.accept(
- ByteDataView.of(TestClassDump.dump()),
- javaTypeToDescriptor(TestClass.class.getTypeName()),
- null);
- consumer.finished(null);
- }
-
- @Test
- public void testExpectedBehavior() throws Exception {
- testForJvm()
- .addProgramClasses(Main.class, TestClass.class)
- .run(Main.class)
- .assertSuccessWithOutput(StringUtils.lines("true", "false"));
- }
-
- @Test(expected = CompilationFailedException.class)
- public void testD8Behavior() throws Exception {
- // TODO(b/110175213): Should succeed with output "true\nfalse\n".
- testForD8()
- .addProgramFiles(inputJar)
- .compileWithExpectedDiagnostics(
- testDiagnosticMessages ->
- testDiagnosticMessages.assertErrorMessageThatMatches(
- containsString("Failed to compile unsupported use of invokespecial")));
- }
-
- @Test
- public void testDXBehavior() throws Exception {
- assumeTrue(ToolHelper.artSupported());
- testForDX()
- .addProgramFiles(inputJar)
- .run(Main.class)
- .assertFailureWithErrorThatMatches(containsString(getExpectedOutput()));
- }
-
- private static String getExpectedOutput() {
- if (ToolHelper.getDexVm().getVersion().isOlderThanOrEqual(Version.V4_4_4)) {
- return "VFY: unable to resolve direct method";
- }
- return "was expected to be of type direct but instead was found to be of type virtual";
- }
-}
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToVirtualMethodTest.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToVirtualMethodTest.java
new file mode 100644
index 0000000..730ae81
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToVirtualMethodTest.java
@@ -0,0 +1,138 @@
+// Copyright (c) 2020, 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.graph.invokespecial;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.utils.DescriptorUtils.getBinaryNameFromJavaType;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.utils.StringUtils;
+import java.io.IOException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class InvokeSpecialToVirtualMethodTest extends TestBase {
+
+ static final String EXPECTED = StringUtils.lines("Bar::foo", "Foo::foo", "Foo::foo", "Foo::foo");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+ }
+
+ public InvokeSpecialToVirtualMethodTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testJvm() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForJvm()
+ .addProgramClasses(Base.class, Bar.class, TestClass.class)
+ .addProgramClassFileData(getFooTransform())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test(expected = CompilationFailedException.class)
+ public void testD8() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testForD8()
+ .addProgramClasses(Base.class, Bar.class, TestClass.class)
+ .addProgramClassFileData(getFooTransform())
+ .setMinApi(parameters.getApiLevel())
+ .compileWithExpectedDiagnostics(
+ diagnostics ->
+ diagnostics
+ .assertOnlyErrors()
+ .assertErrorsMatch(
+ diagnosticMessage(containsString("unsupported use of invokespecial"))));
+ }
+
+ @Test
+ public void testDX() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testForDX()
+ .addProgramClasses(Base.class, Bar.class, TestClass.class)
+ .addProgramClassFileData(getFooTransform())
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertFailureWithErrorThatMatches(containsString(getExpectedOutput()));
+ }
+
+ private String getExpectedOutput() {
+ if (parameters.getRuntime().asDex().getVm().getVersion().isOlderThanOrEqual(Version.V4_4_4)) {
+ return "VFY: unable to resolve direct method";
+ }
+ return "was expected to be of type direct but instead was found to be of type virtual";
+ }
+
+ private byte[] getFooTransform() throws IOException {
+ return transformer(Foo.class)
+ .transformMethodInsnInMethod(
+ "foo",
+ (opcode, owner, name, descriptor, isInterface, visitor) -> {
+ if (Opcodes.INVOKESPECIAL == opcode) {
+ assertEquals(getBinaryNameFromJavaType(Base.class.getName()), owner);
+ assertEquals("foo", name);
+ visitor.visitMethodInsn(
+ opcode,
+ getBinaryNameFromJavaType(Foo.class.getName()),
+ name,
+ descriptor,
+ isInterface);
+ } else {
+ visitor.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+ }
+ })
+ .transform();
+ }
+
+ static class Base {
+ // Base method is never hit.
+ public void foo(int i) {
+ System.out.println("Base::foo");
+ }
+ }
+
+ static class Foo extends Base {
+
+ public void foo(int i) {
+ if (i > 0) {
+ System.out.println("Foo::foo");
+ // Will be converted to invoke-special Foo.foo.
+ super.foo(i - 1);
+ }
+ }
+ }
+
+ static class Bar extends Foo {
+
+ // Subclass override is only hit initially.
+ @Override
+ public void foo(int i) {
+ System.out.println("Bar::foo");
+ super.foo(3);
+ }
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ new Bar().foo(0);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/Main.java b/src/test/java/com/android/tools/r8/graph/invokespecial/Main.java
deleted file mode 100644
index deb316d..0000000
--- a/src/test/java/com/android/tools/r8/graph/invokespecial/Main.java
+++ /dev/null
@@ -1,13 +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.graph.invokespecial;
-
-public class Main {
-
- public static void main(String[] args) {
- TestClass x = new TestClass();
- x.m(true);
- }
-}
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/TestClass.java b/src/test/java/com/android/tools/r8/graph/invokespecial/TestClass.java
deleted file mode 100644
index c08091e..0000000
--- a/src/test/java/com/android/tools/r8/graph/invokespecial/TestClass.java
+++ /dev/null
@@ -1,15 +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.graph.invokespecial;
-
-public class TestClass {
-
- public void m(boolean recurse) {
- System.out.println(recurse);
- if (recurse) {
- m(false);
- }
- }
-}
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/TestClassDump.java b/src/test/java/com/android/tools/r8/graph/invokespecial/TestClassDump.java
deleted file mode 100644
index ae336834..0000000
--- a/src/test/java/com/android/tools/r8/graph/invokespecial/TestClassDump.java
+++ /dev/null
@@ -1,67 +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.graph.invokespecial;
-
-import org.objectweb.asm.AnnotationVisitor;
-import org.objectweb.asm.ClassWriter;
-import org.objectweb.asm.FieldVisitor;
-import org.objectweb.asm.Label;
-import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Opcodes;
-
-// Generated by running ./tools/asmifier.py build/classes/test/com/android/tools/r8/graph/-
-// invokespecial/TestClass.class, and changing the invoke-virtual TestClass.m() instruction to
-// an invoke-special instruction.
-public class TestClassDump implements Opcodes {
-
- public static byte[] dump() throws Exception {
-
- ClassWriter cw = new ClassWriter(0);
- FieldVisitor fv;
- MethodVisitor mv;
- AnnotationVisitor av0;
-
- cw.visit(
- V1_8,
- ACC_PUBLIC + ACC_SUPER,
- "com/android/tools/r8/graph/invokespecial/TestClass",
- null,
- "java/lang/Object",
- null);
-
- {
- mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
- mv.visitCode();
- mv.visitVarInsn(ALOAD, 0);
- mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
- mv.visitInsn(RETURN);
- mv.visitMaxs(1, 1);
- mv.visitEnd();
- }
- {
- mv = cw.visitMethod(ACC_PUBLIC, "m", "(Z)V", null, null);
- mv.visitCode();
- mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
- mv.visitVarInsn(ILOAD, 1);
- mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Z)V", false);
- mv.visitVarInsn(ILOAD, 1);
- Label l0 = new Label();
- mv.visitJumpInsn(IFEQ, l0);
- mv.visitVarInsn(ALOAD, 0);
- mv.visitInsn(ICONST_0);
- // Note: Changed from INVOKEVIRTUAL to INVOKESPECIAL.
- mv.visitMethodInsn(
- INVOKESPECIAL, "com/android/tools/r8/graph/invokespecial/TestClass", "m", "(Z)V", false);
- mv.visitLabel(l0);
- mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
- mv.visitInsn(RETURN);
- mv.visitMaxs(2, 2);
- mv.visitEnd();
- }
- cw.visitEnd();
-
- return cw.toByteArray();
- }
-}
diff --git a/src/test/java/com/android/tools/r8/internal/ChromeCompilationBase.java b/src/test/java/com/android/tools/r8/internal/ChromeCompilationBase.java
new file mode 100644
index 0000000..b80b932
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/internal/ChromeCompilationBase.java
@@ -0,0 +1,71 @@
+// Copyright (c) 2016, 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.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.utils.FileUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import org.hamcrest.Matcher;
+
+public abstract class ChromeCompilationBase extends CompilationTestBase {
+
+ static final String LIBRARY_JAR = "library.jar";
+ static final String PROGRAM_JAR = "program.jar";
+ static final String PROGUARD_CONFIG = "proguard.config";
+
+ final String base;
+
+ public ChromeCompilationBase(int version, boolean minimal) {
+ this.base =
+ "third_party/chrome/"
+ + (minimal ? "monochrome_public_minimal_apks/" : "")
+ + "chrome_"
+ + version
+ + "/";
+ }
+
+ protected List<Path> getKeepRuleFiles() {
+ ImmutableList.Builder<Path> builder = ImmutableList.builder();
+ builder.add(Paths.get(base).resolve(PROGUARD_CONFIG));
+ return builder.build();
+ }
+
+ protected List<Path> getLibraryFiles() throws IOException {
+ assert verifyNoLibraryJarsInProguardConfig();
+ List<Path> result = ImmutableList.of(Paths.get(base).resolve(LIBRARY_JAR));
+ assert result.stream().allMatch(path -> path.toFile().exists());
+ return result;
+ }
+
+ protected List<Path> getProgramFiles() throws IOException {
+ assert verifyNoInJarsInProguardConfig();
+ List<Path> result = ImmutableList.of(Paths.get(base).resolve(PROGRAM_JAR));
+ assert result.stream().allMatch(path -> path.toFile().exists());
+ return result;
+ }
+
+ private boolean verifyNoInJarsInProguardConfig() throws IOException {
+ return verifyAllLinesInKeepRuleFilesMatch(not(containsString("-injars")));
+ }
+
+ private boolean verifyNoLibraryJarsInProguardConfig() throws IOException {
+ return verifyAllLinesInKeepRuleFilesMatch(not(containsString("-libraryjars")));
+ }
+
+ private boolean verifyAllLinesInKeepRuleFilesMatch(Matcher<String> matcher) throws IOException {
+ for (Path keepRuleFile : getKeepRuleFiles()) {
+ for (String line : FileUtils.readAllLines(keepRuleFile)) {
+ assertThat(line, matcher);
+ }
+ }
+ return 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
index c4509c4..4a256bb 100644
--- a/src/test/java/com/android/tools/r8/internal/LibrarySanitizer.java
+++ b/src/test/java/com/android/tools/r8/internal/LibrarySanitizer.java
@@ -17,7 +17,7 @@
import java.util.List;
import org.junit.rules.TemporaryFolder;
-class LibrarySanitizer {
+public class LibrarySanitizer {
private final Path sanitizedLibrary;
private final Path sanitizedPgConf;
@@ -26,12 +26,12 @@
private final List<Path> programFiles = new ArrayList<>();
private final List<Path> proguardConfigurationFiles = new ArrayList<>();
- LibrarySanitizer(TemporaryFolder temp) {
+ public LibrarySanitizer(TemporaryFolder temp) {
this.sanitizedLibrary = temp.getRoot().toPath().resolve("sanitized_lib.jar");
this.sanitizedPgConf = temp.getRoot().toPath().resolve("sanitized.config");
}
- LibrarySanitizer assertSanitizedProguardConfigurationIsEmpty() throws IOException {
+ public LibrarySanitizer assertSanitizedProguardConfigurationIsEmpty() throws IOException {
if (sanitizedPgConf.toFile().exists()) {
List<String> lines = FileUtils.readAllLines(sanitizedPgConf);
for (String line : lines) {
@@ -41,12 +41,12 @@
return this;
}
- LibrarySanitizer addLibraryFiles(List<Path> libraryFiles) {
+ public LibrarySanitizer addLibraryFiles(List<Path> libraryFiles) {
this.libraryFiles.addAll(libraryFiles);
return this;
}
- LibrarySanitizer addProgramFiles(List<Path> programFiles) {
+ public LibrarySanitizer addProgramFiles(List<Path> programFiles) {
this.programFiles.addAll(programFiles);
return this;
}
@@ -56,7 +56,7 @@
return this;
}
- Path getSanitizedLibrary() {
+ public Path getSanitizedLibrary() {
return sanitizedLibrary;
}
@@ -64,7 +64,7 @@
return sanitizedPgConf;
}
- LibrarySanitizer sanitize() throws IOException {
+ public LibrarySanitizer sanitize() throws IOException {
ImmutableList.Builder<String> command =
new ImmutableList.Builder<String>()
.add("tools/sanitize_libraries.py")
diff --git a/src/test/java/com/android/tools/r8/internal/YouTubeV1508TreeShakeJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/YouTubeV1508TreeShakeJarVerificationTest.java
index e63b126..b48bec3 100644
--- a/src/test/java/com/android/tools/r8/internal/YouTubeV1508TreeShakeJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/YouTubeV1508TreeShakeJarVerificationTest.java
@@ -59,7 +59,6 @@
.addLibraryFiles(librarySanitizer.getSanitizedLibrary())
.addKeepRuleFiles(getKeepRuleFiles())
.addMainDexRuleFiles(getMainDexRuleFiles())
- .allowCheckDiscardedErrors()
.allowDiagnosticMessages()
.allowUnusedProguardConfigurationRules()
.setMinApi(AndroidApiLevel.H_MR2)
diff --git a/src/test/java/com/android/tools/r8/internal/proto/YouTubeProtoRewritingTest.java b/src/test/java/com/android/tools/r8/internal/proto/ChromeProtoRewritingTest.java
similarity index 62%
rename from src/test/java/com/android/tools/r8/internal/proto/YouTubeProtoRewritingTest.java
rename to src/test/java/com/android/tools/r8/internal/proto/ChromeProtoRewritingTest.java
index 6088600..ddaf85f 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/YouTubeProtoRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/ChromeProtoRewritingTest.java
@@ -12,44 +12,45 @@
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.internal.YouTubeCompilationBase;
+import com.android.tools.r8.internal.ChromeCompilationBase;
+import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import org.junit.Ignore;
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 YouTubeProtoRewritingTest extends YouTubeCompilationBase {
-
- private final TestParameters parameters;
+public class ChromeProtoRewritingTest extends ChromeCompilationBase {
@Parameters(name = "{0}")
public static TestParametersCollection data() {
- return getTestParameters().withDexRuntimes().build();
+ return getTestParameters().withNoneRuntime().build();
}
- public YouTubeProtoRewritingTest(TestParameters parameters) {
- super(14, 19);
- this.parameters = parameters;
+ public ChromeProtoRewritingTest(TestParameters parameters) {
+ super(200430, false);
}
- @Ignore
@Test
public void test() throws Exception {
assumeTrue(shouldRunSlowTests());
-
- testForR8(parameters.getBackend())
+ testForR8(Backend.DEX)
+ .addProgramFiles(getProgramFiles())
+ .addLibraryFiles(getLibraryFiles())
.addKeepRuleFiles(getKeepRuleFiles())
- // Retain all protos.
- .addKeepRules(keepAllProtosRule())
- // Retain the signature of dynamicMethod() and newMessageInfo().
- .addKeepRules(keepDynamicMethodSignatureRule(), keepNewMessageInfoSignatureRule())
+ .addKeepRules(
+ keepAllProtosRule(),
+ keepDynamicMethodSignatureRule(),
+ keepNewMessageInfoSignatureRule())
.allowUnusedProguardConfigurationRules()
+ .enableProtoShrinking(false)
+ .setMinApi(AndroidApiLevel.N)
.compile()
- .inspect(
- inspector ->
- assertRewrittenProtoSchemasMatch(new CodeInspector(getProgramFiles()), inspector));
+ .inspect(this::inspect);
+ }
+
+ private void inspect(CodeInspector inspector) throws Exception {
+ assertRewrittenProtoSchemasMatch(new CodeInspector(getProgramFiles()), inspector);
}
}
diff --git a/src/test/java/com/android/tools/r8/internal/proto/ProtoShrinkingTestBase.java b/src/test/java/com/android/tools/r8/internal/proto/ProtoShrinkingTestBase.java
index a1ecab4..79ce685 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/ProtoShrinkingTestBase.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/ProtoShrinkingTestBase.java
@@ -79,7 +79,8 @@
static String keepAllProtosRule() {
return StringUtils.lines(
- "-keep class * extends com.google.protobuf.GeneratedMessageLite { *; }");
+ "-if class * extends com.google.protobuf.GeneratedMessageLite",
+ "-keep,allowobfuscation class <1> { <init>(...); <fields>; }");
}
static String keepDynamicMethodSignatureRule() {
diff --git a/src/test/java/com/android/tools/r8/internal/proto/YouTubeV1508ProtoRewritingTest.java b/src/test/java/com/android/tools/r8/internal/proto/YouTubeV1508ProtoRewritingTest.java
new file mode 100644
index 0000000..5596fc7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/internal/proto/YouTubeV1508ProtoRewritingTest.java
@@ -0,0 +1,67 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.internal.proto;
+
+import static com.android.tools.r8.ToolHelper.shouldRunSlowTests;
+import static com.android.tools.r8.internal.proto.ProtoShrinkingTestBase.assertRewrittenProtoSchemasMatch;
+import static com.android.tools.r8.internal.proto.ProtoShrinkingTestBase.keepAllProtosRule;
+import static com.android.tools.r8.internal.proto.ProtoShrinkingTestBase.keepDynamicMethodSignatureRule;
+import static com.android.tools.r8.internal.proto.ProtoShrinkingTestBase.keepNewMessageInfoSignatureRule;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.internal.LibrarySanitizer;
+import com.android.tools.r8.internal.YouTubeCompilationBase;
+import com.android.tools.r8.utils.AndroidApiLevel;
+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 YouTubeV1508ProtoRewritingTest extends YouTubeCompilationBase {
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withNoneRuntime().build();
+ }
+
+ public YouTubeV1508ProtoRewritingTest(TestParameters parameters) {
+ super(15, 8);
+ }
+
+ @Test
+ public void test() throws Exception {
+ assumeTrue(shouldRunSlowTests());
+
+ LibrarySanitizer librarySanitizer =
+ new LibrarySanitizer(temp)
+ .addProgramFiles(getProgramFiles())
+ .addLibraryFiles(getLibraryFiles())
+ .sanitize()
+ .assertSanitizedProguardConfigurationIsEmpty();
+
+ testForR8(Backend.DEX)
+ .addProgramFiles(getProgramFiles())
+ .addLibraryFiles(librarySanitizer.getSanitizedLibrary())
+ .addKeepRuleFiles(getKeepRuleFiles())
+ .addKeepRules(
+ keepAllProtosRule(),
+ keepDynamicMethodSignatureRule(),
+ keepNewMessageInfoSignatureRule())
+ .addMainDexRuleFiles(getMainDexRuleFiles())
+ .allowCheckDiscardedErrors(true)
+ .allowDiagnosticMessages()
+ .allowUnusedProguardConfigurationRules()
+ .setMinApi(AndroidApiLevel.H_MR2)
+ .compile()
+ .inspect(this::inspect);
+ }
+
+ private void inspect(CodeInspector inspector) throws Exception {
+ assertRewrittenProtoSchemasMatch(new CodeInspector(getProgramFiles()), inspector);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/ArrayContentsDependOnEnvironmentTest.java b/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/ArrayContentsDependOnEnvironmentTest.java
new file mode 100644
index 0000000..adba889
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/ArrayContentsDependOnEnvironmentTest.java
@@ -0,0 +1,72 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.analysis.sideeffect;
+
+import com.android.tools.r8.AssumeNoSideEffects;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+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 ArrayContentsDependOnEnvironmentTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public ArrayContentsDependOnEnvironmentTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(ArrayContentsDependOnEnvironmentTest.class)
+ .addKeepMainRule(TestClass.class)
+ .enableAssumeNoSideEffectsAnnotations()
+ .enableInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("42");
+ }
+
+ static class TestClass {
+
+ static int f = 42;
+
+ public static void main(String[] args) {
+ // Trigger A.<clinit>(), this will set A.values to [42].
+ A.empty();
+ // Unset f, such that the below will read -1 if we failed to trigger class initialization.
+ f = -1;
+ // Print 42.
+ System.out.println(A.values[0]);
+ }
+ }
+
+ static class A {
+
+ static long[] values = new long[] {getFourtyTwo()};
+
+ @AssumeNoSideEffects
+ @NeverInline
+ static long getFourtyTwo() {
+ return TestClass.f;
+ }
+
+ static void empty() {
+ // Intentionally empty.
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/library/BooleanParseBooleanTest.java b/src/test/java/com/android/tools/r8/ir/optimize/library/BooleanParseBooleanTest.java
new file mode 100644
index 0000000..24894bb
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/library/BooleanParseBooleanTest.java
@@ -0,0 +1,121 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.library;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class BooleanParseBooleanTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public BooleanParseBooleanTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testForD8()
+ .addProgramClasses(TestClass.class)
+ .release()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("true", "true", "true", "false", "false", "false", "true");
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(TestClass.class)
+ .addKeepMainRule(TestClass.class)
+ .enableInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("true", "true", "true", "false", "false", "false", "true");
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject testClassSubject = inspector.clazz(TestClass.class);
+ assertThat(testClassSubject, isPresent());
+
+ MethodSubject testOptimizedMethodSubject =
+ testClassSubject.uniqueMethodWithName("testOptimized");
+ assertThat(testOptimizedMethodSubject, isPresent());
+ assertTrue(
+ testOptimizedMethodSubject
+ .streamInstructions()
+ .filter(InstructionSubject::isInvokeStatic)
+ .map(InstructionSubject::getMethod)
+ .map(DexMethod::toSourceString)
+ .noneMatch(method -> method.contains("parseBoolean")));
+
+ MethodSubject testNotOptimizedMethodSubject =
+ testClassSubject.uniqueMethodWithName("testNotOptimized");
+ assertThat(testNotOptimizedMethodSubject, isPresent());
+ assertEquals(
+ 1,
+ testNotOptimizedMethodSubject
+ .streamInstructions()
+ .filter(InstructionSubject::isInvokeStatic)
+ .map(InstructionSubject::getMethod)
+ .map(DexMethod::toSourceString)
+ .filter(method -> method.contains("parseBoolean"))
+ .count());
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ testOptimized();
+ testNotOptimized();
+ }
+
+ @NeverInline
+ static void testOptimized() {
+ System.out.println(Boolean.parseBoolean("true"));
+ System.out.println(Boolean.parseBoolean("tRuE"));
+ System.out.println(Boolean.parseBoolean("TRUE"));
+ System.out.println(Boolean.parseBoolean("false"));
+ System.out.println(Boolean.parseBoolean("fAlSe"));
+ System.out.println(Boolean.parseBoolean("FALSE"));
+ }
+
+ @NeverInline
+ static void testNotOptimized() {
+ System.out.println(Boolean.parseBoolean(unknown()));
+ }
+
+ static String unknown() {
+ return System.currentTimeMillis() >= 0 ? "true" : "false";
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteAnnotationTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteAnnotationTest.java
new file mode 100644
index 0000000..11c47de
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteAnnotationTest.java
@@ -0,0 +1,185 @@
+// Copyright (c) 2020, 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.KotlinCompilerTool.KOTLINC;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isNotRenamed;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.DescriptorUtils;
+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.KmTypeAliasSubject;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import kotlinx.metadata.KmAnnotation;
+import kotlinx.metadata.KmAnnotationArgument;
+import kotlinx.metadata.KmAnnotationArgument.ArrayValue;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MetadataRewriteAnnotationTest extends KotlinMetadataTestBase {
+
+ private final String EXPECTED =
+ StringUtils.lines(
+ "class com.android.tools.r8.kotlin.metadata.annotation_lib.Foo",
+ "class com.android.tools.r8.kotlin.metadata.annotation_lib.Foo",
+ "class com.android.tools.r8.kotlin.metadata.annotation_lib.Bar",
+ "class com.android.tools.r8.kotlin.metadata.annotation_lib.Foo",
+ "UP",
+ "class com.android.tools.r8.kotlin.metadata.annotation_lib.Foo",
+ "LEFT",
+ "class com.android.tools.r8.kotlin.metadata.annotation_lib.Foo",
+ "RIGHT",
+ "class com.android.tools.r8.kotlin.metadata.annotation_lib.Foo",
+ "DOWN",
+ "class com.android.tools.r8.kotlin.metadata.annotation_lib.Foo",
+ "UP",
+ "Top most",
+ "class com.android.tools.r8.kotlin.metadata.annotation_lib.Foo",
+ "DOWN",
+ "com.android.tools.r8.kotlin.metadata.annotation_lib.Foo");
+ private static final String PKG_LIB = PKG + ".annotation_lib";
+ private static final String PKG_APP = PKG + ".annotation_app";
+ private static final String FOO_ORIGINAL_NAME = PKG_LIB + ".Foo";
+ private static final String FOO_FINAL_NAME = "a.b.c";
+
+ @Parameterized.Parameters(name = "{0} target: {1}")
+ public static Collection<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withCfRuntimes().build(), KotlinTargetVersion.values());
+ }
+
+ public MetadataRewriteAnnotationTest(
+ TestParameters parameters, KotlinTargetVersion targetVersion) {
+ super(targetVersion);
+ this.parameters = parameters;
+ }
+
+ private static Map<KotlinTargetVersion, Path> libJars = new HashMap<>();
+ private final TestParameters parameters;
+
+ @BeforeClass
+ public static void createLibJar() throws Exception {
+ for (KotlinTargetVersion targetVersion : KotlinTargetVersion.values()) {
+ Path baseLibJar =
+ kotlinc(KOTLINC, targetVersion)
+ .addSourceFiles(
+ getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_LIB), "lib"))
+ .compile();
+ libJars.put(targetVersion, baseLibJar);
+ }
+ }
+
+ @Test
+ public void smokeTest() throws Exception {
+ Path libJar = libJars.get(targetVersion);
+ Path output =
+ kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+ .addClasspathFiles(libJar)
+ .addSourceFiles(
+ getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_APP), "main"))
+ .setOutputPath(temp.newFolder().toPath())
+ .compile();
+ testForJvm()
+ .addRunClasspathFiles(
+ ToolHelper.getKotlinStdlibJar(), ToolHelper.getKotlinReflectJar(), libJar)
+ .addClasspath(output)
+ .run(parameters.getRuntime(), PKG_APP + ".MainKt")
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testMetadataForLib() throws Exception {
+ Path libJar =
+ testForR8(parameters.getBackend())
+ .addProgramFiles(libJars.get(targetVersion))
+ /// Keep the annotations
+ .addKeepClassAndMembersRules(PKG_LIB + ".AnnoWithClassAndEnum")
+ .addKeepClassAndMembersRules(PKG_LIB + ".AnnoWithClassArr")
+ .addKeepRules("-keep class " + PKG_LIB + ".Nested { *** kept(); } ")
+ .addKeepRules("-keep class " + PKG_LIB + ".Nested { *** message(); } ")
+ // .addKeepRules("-keep class " + PKG_LIB + ".Nested { *** kept; *** getKept(); } ")
+ // Keep Foo but rename to test arguments
+ .addKeepClassAndMembersRulesWithAllowObfuscation(FOO_ORIGINAL_NAME)
+ .addApplyMapping(FOO_ORIGINAL_NAME + " -> " + FOO_FINAL_NAME + ":")
+ // Keep Direction but rename the enum
+ .addKeepClassAndMembersRules(PKG_LIB + ".Direction")
+ // Keep Bar and Baz and Quux because we are directly reflecting on them
+ .addKeepClassAndMembersRules(PKG_LIB + ".Bar")
+ .addKeepClassAndMembersRules(PKG_LIB + ".Baz")
+ .addKeepClassAndMembersRules(PKG_LIB + ".Quux")
+ // Keep the static class for the type alias
+ .addKeepClassAndMembersRules(PKG_LIB + ".LibKt")
+ .addKeepAttributes(
+ ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS,
+ ProguardKeepAttributes.RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS,
+ ProguardKeepAttributes.RUNTIME_VISIBLE_TYPE_ANNOTATIONS)
+ .compile()
+ .inspect(this::inspect)
+ .writeToZip();
+ Path output =
+ kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+ .addClasspathFiles(libJar)
+ .addSourceFiles(
+ getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_APP), "main"))
+ .compile();
+ testForJvm()
+ .addRunClasspathFiles(
+ ToolHelper.getKotlinStdlibJar(), ToolHelper.getKotlinReflectJar(), libJar)
+ .addProgramFiles(output)
+ .run(parameters.getRuntime(), PKG_APP + ".MainKt")
+ .assertSuccessWithOutput(EXPECTED.replace(FOO_ORIGINAL_NAME, FOO_FINAL_NAME));
+ }
+
+ private void inspect(CodeInspector inspector) {
+ // Assert that foo is renamed.
+ ClassSubject foo = inspector.clazz(PKG_LIB + ".Foo");
+ assertThat(foo, isRenamed());
+ assertEquals(FOO_FINAL_NAME, foo.getFinalName());
+ // Assert that bar exists and is not renamed.
+ ClassSubject bar = inspector.clazz(PKG_LIB + ".Bar");
+ assertThat(bar, isPresent());
+ assertThat(bar, isNotRenamed());
+ // Check that the annotation type on the type alias has been renamed
+ inspectTypeAliasAnnotation(inspector, foo, bar);
+ }
+
+ private void inspectTypeAliasAnnotation(
+ CodeInspector inspector, ClassSubject foo, ClassSubject bar) {
+ ClassSubject libKt = inspector.clazz(PKG_LIB + ".LibKt");
+ assertThat(libKt, isPresent());
+ assertThat(libKt.getKmPackage(), isPresent());
+ KmTypeAliasSubject qux = libKt.getKmPackage().kmTypeAliasWithUniqueName("Qux");
+ assertThat(qux, isPresent());
+ assertEquals(1, qux.annotations().size());
+ KmAnnotation annotation = qux.annotations().get(0);
+ assertEquals(
+ DescriptorUtils.getBinaryNameFromJavaType(PKG_LIB) + "/AnnoWithClassArr",
+ annotation.getClassName());
+ Map<String, KmAnnotationArgument<?>> arguments = annotation.getArguments();
+ assertEquals(1, arguments.size());
+ ArrayValue classes = (ArrayValue) arguments.get("classes");
+ assertEquals(
+ "KClassValue(value=" + foo.getFinalBinaryName() + ")",
+ classes.getValue().get(0).toString());
+ assertEquals(
+ "KClassValue(value=" + bar.getFinalBinaryName() + ")",
+ classes.getValue().get(1).toString());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteCrossinlineAnonFunctionTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteCrossinlineAnonFunctionTest.java
index c6dd4fd..1ebdddb 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteCrossinlineAnonFunctionTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteCrossinlineAnonFunctionTest.java
@@ -76,7 +76,6 @@
Path libJar =
testForR8(parameters.getBackend())
.addProgramFiles(libJars.get(targetVersion))
- // Allow renaming A to ensure that we rename in the flexible upper bound type.
.addKeepAllClassesRule()
.addKeepAllAttributes()
.compile()
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDelegatedPropertyTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDelegatedPropertyTest.java
index 50007fb..badbf02 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDelegatedPropertyTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDelegatedPropertyTest.java
@@ -5,16 +5,14 @@
package com.android.tools.r8.kotlin.metadata;
import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
-import static org.hamcrest.CoreMatchers.containsString;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertEquals;
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.shaking.ProguardKeepAttributes;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import java.nio.file.Path;
import java.util.Collection;
import java.util.HashMap;
@@ -27,33 +25,15 @@
@RunWith(Parameterized.class)
public class MetadataRewriteDelegatedPropertyTest extends KotlinMetadataTestBase {
- private static final String PKG_LIB = PKG + ".delegated_property_lib";
private static final String PKG_APP = PKG + ".delegated_property_app";
private static final String EXPECTED_MAIN =
StringUtils.lines(
- "foo has been assigned to 'customDelegate' in"
- + " com.android.tools.r8.kotlin.metadata.delegated_property_lib.Delegates",
- "foo has been read in CustomDelegate from 'customDelegate' in"
- + " com.android.tools.r8.kotlin.metadata.delegated_property_lib.Delegates",
- "foo",
- "read-only has been read in CustomReadOnlyDelegate from 'customReadOnlyDelegate' in"
- + " com.android.tools.r8.kotlin.metadata.delegated_property_lib.Delegates",
- "read-only",
- "Generating lazy string",
- "42",
- "Hello World!",
- "Hello World!",
- "Jane Doe",
- "42",
- "Checking property for image",
- "Checking property for text",
- "image_id",
- "text_id");
- private static final String EXPECTED_REFLECT =
- StringUtils.lines(
- "foo has been assigned to 'customDelegate' in"
- + " com.android.tools.r8.kotlin.metadata.delegated_property_lib.Delegates",
- "foo");
+ "Initial string has been read in CustomDelegate from 'x'",
+ "Initial string has been read in CustomDelegate from 'x'",
+ "New value has been read in CustomDelegate from 'x'",
+ "New value has been read in CustomDelegate from 'x'",
+ "null",
+ "New value has been read in CustomDelegate from 'x'");
@Parameterized.Parameters(name = "{0} target: {1}")
public static Collection<Object[]> data() {
@@ -68,107 +48,47 @@
}
private final TestParameters parameters;
- private static Map<KotlinTargetVersion, Path> libJars = new HashMap<>();
+ private static Map<KotlinTargetVersion, Path> jars = new HashMap<>();
@BeforeClass
- public static void createLibJar() throws Exception {
+ public static void createJar() throws Exception {
for (KotlinTargetVersion targetVersion : KotlinTargetVersion.values()) {
Path baseLibJar =
kotlinc(KOTLINC, targetVersion)
.addSourceFiles(
- getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_LIB), "lib"))
+ getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_APP), "main"))
.compile();
- libJars.put(targetVersion, baseLibJar);
+ jars.put(targetVersion, baseLibJar);
}
}
@Test
public void smokeTest() throws Exception {
- Path libJar = libJars.get(targetVersion);
- Path output =
- kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
- .addClasspathFiles(libJar)
- .addSourceFiles(
- getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_APP), "main"))
- .setOutputPath(temp.newFolder().toPath())
- .compile();
testForJvm()
- .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
- .addClasspath(output)
+ .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), ToolHelper.getKotlinReflectJar())
+ .addClasspath(jars.get(targetVersion))
.run(parameters.getRuntime(), PKG_APP + ".MainKt")
.assertSuccessWithOutput(EXPECTED_MAIN);
}
- @Test
- public void smokeTestReflect() throws Exception {
- Path libJar = libJars.get(targetVersion);
- Path output =
- kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
- .addClasspathFiles(libJar)
- .addSourceFiles(
- getKotlinFileInTest(
- DescriptorUtils.getBinaryNameFromJavaType(PKG_APP), "main_reflect"))
- .setOutputPath(temp.newFolder().toPath())
- .compile();
- testForJvm()
- .addRunClasspathFiles(
- ToolHelper.getKotlinStdlibJar(), ToolHelper.getKotlinReflectJar(), libJar)
- .addClasspath(output)
- .run(parameters.getRuntime(), PKG_APP + ".Main_reflectKt")
- .assertSuccessWithOutput(EXPECTED_REFLECT);
- }
@Test
public void testMetadataForLib() throws Exception {
- Path libJar =
+ Path outputJar =
testForR8(parameters.getBackend())
.addClasspathFiles(ToolHelper.getKotlinStdlibJar())
- .addProgramFiles(libJars.get(targetVersion))
- .addKeepRules("-keep class " + PKG_LIB + ".Delegates { *; }")
- .addKeepRules("-keep class " + PKG_LIB + ".Resource { *; }")
- .addKeepRules("-keep class " + PKG_LIB + ".User { *; }")
- .addKeepRules("-keep class " + PKG_LIB + ".ProvidedDelegates { *; }")
+ .addProgramFiles(jars.get(targetVersion))
+ .addKeepAllClassesRule()
+ .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
.compile()
- // TODO(b/157988734): When we start modeling localDelegatedProperties, inspect the code.
+ .inspect(
+ inspector ->
+ assertEqualMetadata(new CodeInspector(jars.get(targetVersion)), inspector))
.writeToZip();
- Path output =
- kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
- .addClasspathFiles(libJar)
- .addSourceFiles(
- getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_APP), "main"))
- .setOutputPath(temp.newFolder().toPath())
- .compile();
testForJvm()
- .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
- .addClasspath(output)
+ .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), ToolHelper.getKotlinReflectJar())
+ .addClasspath(outputJar)
.run(parameters.getRuntime(), PKG_APP + ".MainKt")
.assertSuccessWithOutput(EXPECTED_MAIN);
}
-
- @Test
- public void testMetadataForReflect() throws Exception {
- Path libJar =
- testForR8(parameters.getBackend())
- .addClasspathFiles(ToolHelper.getKotlinStdlibJar())
- .addProgramFiles(libJars.get(targetVersion))
- .addKeepRules("-keep class " + PKG_LIB + ".Delegates { *; }")
- .addKeepRules("-keep class " + PKG_LIB + ".Resource { *; }")
- .addKeepRules("-keep class " + PKG_LIB + ".CustomDelegate { *; }")
- .compile()
- .writeToZip();
- ProcessResult result =
- kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
- .addClasspathFiles(libJar)
- .addSourceFiles(
- getKotlinFileInTest(
- DescriptorUtils.getBinaryNameFromJavaType(PKG_APP), "main_reflect"))
- .setOutputPath(temp.newFolder().toPath())
- .compileRaw();
- assertEquals(1, result.exitCode);
- assertThat(
- result.stderr,
- containsString(
- "unsupported [reference to the synthetic extension property for a Java get/set"
- + " method]"));
- }
}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDoNotEmitValuesIfEmpty.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDoNotEmitValuesIfEmpty.java
new file mode 100644
index 0000000..788e64b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDoNotEmitValuesIfEmpty.java
@@ -0,0 +1,81 @@
+// Copyright (c) 2020, 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 org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.graph.DexAnnotationElement;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.codeinspector.AnnotationSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
+import com.google.common.collect.Sets;
+import java.util.Collection;
+import java.util.Set;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MetadataRewriteDoNotEmitValuesIfEmpty extends KotlinMetadataTestBase {
+
+ private final Set<String> nullableFieldKeys = Sets.newHashSet("pn", "xs", "xi");
+
+ @Parameterized.Parameters(name = "{0} target: {1}")
+ public static Collection<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withCfRuntimes().build(), KotlinTargetVersion.values());
+ }
+
+ private final TestParameters parameters;
+
+ public MetadataRewriteDoNotEmitValuesIfEmpty(
+ TestParameters parameters, KotlinTargetVersion targetVersion) {
+ super(targetVersion);
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testKotlinStdLib() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramFiles(ToolHelper.getKotlinStdlibJar())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepAllClassesRule()
+ .addKeepKotlinMetadata()
+ .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+ .compile()
+ .inspect(this::inspectEmptyValuesAreNotPresent);
+ }
+
+ private void inspectEmptyValuesAreNotPresent(CodeInspector inspector) {
+ boolean seenNullableField = false;
+ boolean seenMetadataWithoutNullableField = false;
+ for (FoundClassSubject clazz : inspector.allClasses()) {
+ AnnotationSubject annotation = clazz.annotation("kotlin.Metadata");
+ if (annotation.isPresent()) {
+ boolean seenNullableFieldForAnnotation = false;
+ for (DexAnnotationElement element : annotation.getAnnotation().elements) {
+ if (nullableFieldKeys.contains(element.name.toString())) {
+ if (element.value.isDexValueInt()) {
+ assertNotEquals(0, element.value.asDexValueInt().value);
+ } else {
+ String value = element.value.asDexValueString().value.toString();
+ assertNotEquals("", value);
+ }
+ seenNullableField = true;
+ seenNullableFieldForAnnotation = true;
+ }
+ }
+ seenMetadataWithoutNullableField |= !seenNullableFieldForAnnotation;
+ }
+ }
+ assertTrue(seenNullableField);
+ assertTrue(seenMetadataWithoutNullableField);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteJvmStaticTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteJvmStaticTest.java
new file mode 100644
index 0000000..2d3834b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteJvmStaticTest.java
@@ -0,0 +1,156 @@
+// Copyright (c) 2020, 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.KotlinCompilerTool.KOTLINC;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.kotlin.metadata.jvmstatic_app.MainJava;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.DescriptorUtils;
+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.KmClassSubject;
+import com.android.tools.r8.utils.codeinspector.KmFunctionSubject;
+import com.android.tools.r8.utils.codeinspector.KmPropertySubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MetadataRewriteJvmStaticTest extends KotlinMetadataTestBase {
+
+ private static final String EXPECTED =
+ StringUtils.lines("Hello, Hello", "Calling func...", "Foo");
+ private static final String PKG_LIB = PKG + ".jvmstatic_lib";
+ private static final String PKG_APP = PKG + ".jvmstatic_app";
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0} target: {1}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withCfRuntimes().build();
+ }
+
+ public MetadataRewriteJvmStaticTest(TestParameters parameters) {
+ // We are testing static methods on interfaces which requires java 8.
+ super(KotlinTargetVersion.JAVA_8);
+ this.parameters = parameters;
+ }
+
+ private static Path kotlincLibJar = Paths.get("");
+
+ @BeforeClass
+ public static void createLibJar() throws Exception {
+ kotlincLibJar =
+ kotlinc(KOTLINC, KotlinTargetVersion.JAVA_8)
+ .addSourceFiles(
+ getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_LIB), "lib"))
+ .compile();
+ }
+
+ @Test
+ public void smokeTest() throws Exception {
+ Path output =
+ kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+ .addClasspathFiles(kotlincLibJar)
+ .addSourceFiles(
+ getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_APP), "main"))
+ .setOutputPath(temp.newFolder().toPath())
+ .compile();
+ testForJvm()
+ .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), kotlincLibJar)
+ .addClasspath(output)
+ .run(parameters.getRuntime(), PKG_APP + ".MainKt")
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void smokeTestJava() throws Exception {
+ testForJvm()
+ .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), kotlincLibJar)
+ .addProgramClassFileData(MainJava.dump())
+ .run(parameters.getRuntime(), MainJava.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testMetadata() throws Exception {
+ Path libJar =
+ testForR8(parameters.getBackend())
+ .addProgramFiles(kotlincLibJar)
+ .addKeepAllClassesRule()
+ .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+ .compile()
+ .inspect(this::inspect)
+ .writeToZip();
+ testKotlin(libJar);
+ testJava(libJar);
+ }
+
+ private void testKotlin(Path libJar) throws Exception {
+ Path output =
+ kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+ .addClasspathFiles(libJar)
+ .addSourceFiles(
+ getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_APP), "main"))
+ .setOutputPath(temp.newFolder().toPath())
+ .compile();
+ testForJvm()
+ .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+ .addClasspath(output)
+ .run(parameters.getRuntime(), PKG_APP + ".MainKt")
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ private void testJava(Path libJar) throws Exception {
+ testForJvm()
+ .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+ .addProgramClassFileData(MainJava.dump())
+ .run(parameters.getRuntime(), MainJava.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ private void inspect(CodeInspector inspector) {
+ inspectLib(inspector);
+ inspectInterfaceWithCompanion(inspector);
+ }
+
+ private void inspectLib(CodeInspector inspector) {
+ ClassSubject clazz = inspector.clazz(PKG_LIB + ".Lib");
+ assertThat(clazz, isPresent());
+ KmClassSubject kmClass = clazz.getKmClass();
+ assertThat(kmClass, isPresent());
+ KmFunctionSubject staticFun = kmClass.kmFunctionWithUniqueName("staticFun");
+ assertThat(staticFun, isPresent());
+ assertEquals("staticFun(Lkotlin/jvm/functions/Function0;)V", staticFun.signature().asString());
+ KmPropertySubject staticProp = kmClass.kmPropertyWithUniqueName("staticProp");
+ assertThat(staticProp, isPresent());
+ }
+
+ private void inspectInterfaceWithCompanion(CodeInspector inspector) {
+ ClassSubject itf = inspector.clazz(PKG_LIB + ".InterfaceWithCompanion");
+ assertThat(itf, isPresent());
+ MethodSubject greet = itf.uniqueMethodWithName("greet");
+ assertThat(greet, isPresent());
+
+ ClassSubject itfCompanion = inspector.clazz(PKG_LIB + ".InterfaceWithCompanion$Companion");
+ assertThat(itfCompanion, isPresent());
+ KmClassSubject kmClass = itfCompanion.getKmClass();
+ KmFunctionSubject greetKm = kmClass.kmFunctionWithUniqueName("greet");
+ assertThat(greetKm, isPresent());
+ assertEquals("greet(Ljava/lang/String;)V", greetKm.signature().asString());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePrunedObjectsTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePrunedObjectsTest.java
new file mode 100644
index 0000000..39e9d12
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePrunedObjectsTest.java
@@ -0,0 +1,117 @@
+// Copyright (c) 2020, 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.KotlinCompilerTool.KOTLINC;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+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 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.utils.DescriptorUtils;
+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.KmClassSubject;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MetadataRewritePrunedObjectsTest extends KotlinMetadataTestBase {
+
+ private final String EXPECTED = StringUtils.lines("42");
+ private static final String PKG_LIB = PKG + ".pruned_lib";
+ private static final String PKG_APP = PKG + ".pruned_app";
+
+ private static Map<KotlinTargetVersion, Path> libJars = new HashMap<>();
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0} target: {1}")
+ public static Collection<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withCfRuntimes().build(), KotlinTargetVersion.values());
+ }
+
+ public MetadataRewritePrunedObjectsTest(
+ TestParameters parameters, KotlinTargetVersion targetVersion) {
+ super(targetVersion);
+ this.parameters = parameters;
+ }
+
+ @BeforeClass
+ public static void createLibJar() throws Exception {
+ for (KotlinTargetVersion targetVersion : KotlinTargetVersion.values()) {
+ Path baseLibJar =
+ kotlinc(KOTLINC, targetVersion)
+ .addSourceFiles(
+ getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_LIB), "lib"))
+ .compile();
+ libJars.put(targetVersion, baseLibJar);
+ }
+ }
+
+ @Test
+ public void smokeTest() throws Exception {
+ Path libJar = libJars.get(targetVersion);
+ Path output =
+ kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+ .addClasspathFiles(libJar)
+ .addSourceFiles(
+ getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_APP), "main"))
+ .setOutputPath(temp.newFolder().toPath())
+ .compile();
+ testForJvm()
+ .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+ .addClasspath(output)
+ .run(parameters.getRuntime(), PKG_APP + ".MainKt")
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testMetadataForLib() throws Exception {
+ Path libJar =
+ testForR8(parameters.getBackend())
+ .addProgramFiles(libJars.get(targetVersion))
+ .addKeepRules("-keep class " + PKG_LIB + ".Sub { *** kept(); }")
+ .addKeepRuntimeVisibleAnnotations()
+ .noMinification()
+ .compile()
+ .inspect(this::checkPruned)
+ .writeToZip();
+ // TODO(b/158766557): This should work.
+ ProcessResult mainResult =
+ kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+ .addClasspathFiles(libJar)
+ .addSourceFiles(
+ getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_APP), "main"))
+ .setOutputPath(temp.newFolder().toPath())
+ .compileRaw();
+ assertEquals(1, mainResult.exitCode);
+ assertThat(
+ mainResult.stderr,
+ containsString("cannot access 'com.android.tools.r8.kotlin.metadata.pruned_lib.Base'"));
+ }
+
+ private void checkPruned(CodeInspector inspector) {
+ ClassSubject base = inspector.clazz(PKG_LIB + ".Base");
+ assertThat(base, not(isPresent()));
+ ClassSubject sub = inspector.clazz(PKG_LIB + ".Sub");
+ assertThat(sub, isPresent());
+ KmClassSubject kmClass = sub.getKmClass();
+ assertThat(kmClass, isPresent());
+ // TODO(b/158766557): Assert that things are indeed removed.
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/annotation_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/annotation_app/main.kt
new file mode 100644
index 0000000..d7ed237
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/annotation_app/main.kt
@@ -0,0 +1,47 @@
+// Copyright (c) 2020, 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.annotation_app
+
+import com.android.tools.r8.kotlin.metadata.annotation_lib.AnnoWithClassAndEnum
+import com.android.tools.r8.kotlin.metadata.annotation_lib.AnnoWithClassArr
+import com.android.tools.r8.kotlin.metadata.annotation_lib.Bar
+import com.android.tools.r8.kotlin.metadata.annotation_lib.Baz
+import com.android.tools.r8.kotlin.metadata.annotation_lib.Nested
+import com.android.tools.r8.kotlin.metadata.annotation_lib.Quux
+import kotlin.reflect.full.memberProperties
+import kotlin.reflect.full.primaryConstructor
+import kotlin.reflect.full.valueParameters
+
+fun main() {
+ Bar::class.primaryConstructor?.annotations?.get(0)?.printAnnoWithClassArr()
+ Baz::class.annotations.get(0).printAnnoWithClassArr()
+ Baz::class.annotations.get(1).printAnnoWithClassAndEnum()
+ Baz::prop.annotations.get(0).printAnnoWithClassAndEnum()
+ Baz::baz.annotations.get(0).printAnnoWithClassAndEnum()
+ Baz::baz.valueParameters.get(0).annotations.get(0).printAnnoWithClassAndEnum()
+ // We cannot reflect on annotations on typealiases:
+ // https://youtrack.jetbrains.com/issue/KT-21489
+ Quux::methodWithTypeAnnotations
+ .returnType.arguments.get(0).type?.annotations?.get(0)?.printAnnoWithClassAndEnum()
+ val nested = Quux::methodWithNestedAnnotations.returnType.arguments[0].type?.annotations?.get(0) as Nested
+ println(nested.message)
+ nested.kept.printAnnoWithClassAndEnum()
+ if (nested::class::memberProperties.get().any { it.name.equals("notKept") }) {
+ println("com.android.tools.r8.kotlin.metadata.annotation_lib.Foo")
+ } else {
+ println("a.b.c")
+ }
+}
+
+fun Annotation.printAnnoWithClassArr() {
+ val annoWithClassArr = this as AnnoWithClassArr
+ annoWithClassArr.classes.forEach { println(it) }
+}
+
+fun Annotation.printAnnoWithClassAndEnum() {
+ val annoWithClassAndEnum = this as AnnoWithClassAndEnum
+ println(annoWithClassAndEnum.clazz)
+ println(annoWithClassAndEnum.direction)
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/annotation_lib/lib.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/annotation_lib/lib.kt
new file mode 100644
index 0000000..0c6ff6c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/annotation_lib/lib.kt
@@ -0,0 +1,61 @@
+// Copyright (c) 2020, 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.annotation_lib
+
+import kotlin.reflect.KClass
+
+enum class Direction {
+ UP, RIGHT, DOWN, LEFT
+}
+
+@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION,
+ AnnotationTarget.VALUE_PARAMETER,
+ AnnotationTarget.PROPERTY,
+ AnnotationTarget.TYPE)
+@Retention(AnnotationRetention.RUNTIME)
+annotation class AnnoWithClassAndEnum(val clazz : KClass<*>, val direction : Direction)
+
+@Target(AnnotationTarget.CLASS, AnnotationTarget.CONSTRUCTOR, AnnotationTarget.TYPEALIAS,
+ AnnotationTarget.TYPE)
+@Retention(AnnotationRetention.RUNTIME)
+annotation class AnnoWithClassArr(val classes: Array<KClass<*>>)
+
+class Foo
+
+class Bar @AnnoWithClassArr([Foo::class]) constructor()
+
+@AnnoWithClassArr([Foo::class, Bar::class])
+@AnnoWithClassAndEnum(Foo::class, Direction.UP) class Baz {
+
+ @AnnoWithClassAndEnum(Foo::class, Direction.LEFT) val prop : Int = 0
+
+ @AnnoWithClassAndEnum(Foo::class, Direction.RIGHT) fun baz(@AnnoWithClassAndEnum(Foo::class, Direction.DOWN) foo: Int): Int {
+ return 1
+ }
+}
+
+@AnnoWithClassArr([Foo::class, Bar::class])
+typealias Qux = Foo
+
+annotation class AnnoNotKept
+
+@Target(AnnotationTarget.TYPE)
+annotation class Nested(
+ val message: String,
+ val kept: AnnoWithClassAndEnum,
+ val notKept: AnnoNotKept
+)
+
+class Quux {
+
+ fun methodWithTypeAnnotations() : Array<@AnnoWithClassAndEnum(Foo::class, Direction.UP) Int> {
+ return arrayOf(1)
+ }
+
+ fun methodWithNestedAnnotations() : Array<@Nested("Top most", AnnoWithClassAndEnum(Foo::class, Direction.DOWN), AnnoNotKept()) Int> {
+ return arrayOf(1)
+ }
+}
+
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/delegated_property_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/delegated_property_app/main.kt
index 7bfd637..a35754f 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/delegated_property_app/main.kt
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/delegated_property_app/main.kt
@@ -3,29 +3,57 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.kotlin.metadata.delegated_property_app
-import com.android.tools.r8.kotlin.metadata.delegated_property_lib.Delegates
-import com.android.tools.r8.kotlin.metadata.delegated_property_lib.ProvidedDelegates
-import com.android.tools.r8.kotlin.metadata.delegated_property_lib.Resource
-import com.android.tools.r8.kotlin.metadata.delegated_property_lib.User
+import kotlin.reflect.KMutableProperty1
+import kotlin.reflect.KProperty
+import kotlin.reflect.full.declaredMemberProperties
+import kotlin.reflect.jvm.isAccessible
+
+class Resource(private var s : String = "Initial string") {
+
+ override fun toString(): String {
+ return s;
+ }
+}
+
+object CustomDelegate {
+
+ private var resource : Resource = Resource()
+
+ operator fun getValue(thisRef: Any?, property: KProperty<*>): Resource {
+ println("$resource has been read in CustomDelegate from '${property.name}'")
+ return resource;
+ }
+
+ operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Resource) {
+ println("$value has been assigned to '${property.name}'")
+ this.resource = resource;
+ }
+}
+
+open class Base {
+
+ fun doSomethingOnBarRef() : Resource {
+ var x by CustomDelegate
+ val propRef = x.javaClass.kotlin.declaredMemberProperties.first() as KMutableProperty1<Resource, String>
+ propRef.isAccessible = true
+ propRef.set(x, "New value")
+ propRef.get(x)
+ // Getting the delegate is not yet supported and will return null. We are printing the value
+ // allowing us to observe if the behavior changes.
+ println(propRef.getDelegate(x))
+ return x
+ }
+}
+
+object Impl : Base() {
+ operator fun invoke(): Impl {
+ return this
+ }
+}
+
fun main() {
-
- val delegates = Delegates()
- delegates.customDelegate = Resource("foo");
- println(delegates.customDelegate)
- println(delegates.customReadOnlyDelegate)
- println(delegates.lazyString)
- println(delegates.localDelegatedProperties { Resource("Hello World!") })
-
- val user = User(mapOf(
- "name" to "Jane Doe",
- "age" to 42
- ))
-
- println(user.name)
- println(user.age)
-
- val providedDelegates = ProvidedDelegates()
- println(providedDelegates.image)
- println(providedDelegates.text)
+ val impl = Impl()
+ impl.doSomethingOnBarRef()
}
+
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/delegated_property_app/main_reflect.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/delegated_property_app/main_reflect.kt
deleted file mode 100644
index 0369a86..0000000
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/delegated_property_app/main_reflect.kt
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright (c) 2020, 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.delegated_property_app
-
-import com.android.tools.r8.kotlin.metadata.delegated_property_lib.CustomDelegate
-import com.android.tools.r8.kotlin.metadata.delegated_property_lib.Delegates
-import com.android.tools.r8.kotlin.metadata.delegated_property_lib.Resource
-import kotlin.reflect.KMutableProperty0
-import kotlin.reflect.jvm.isAccessible
-
-fun main() {
- val delegates = Delegates()
- delegates.customDelegate = Resource("foo");
- println(delegates::customDelegate.getResource())
-}
-
-inline fun KMutableProperty0<*>.getResource(): Resource {
- isAccessible = true
- return (getDelegate() as CustomDelegate).resource
-}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/delegated_property_lib/lib.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/delegated_property_lib/lib.kt
deleted file mode 100644
index 54c58df..0000000
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/delegated_property_lib/lib.kt
+++ /dev/null
@@ -1,88 +0,0 @@
-// Copyright (c) 2020, 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.delegated_property_lib
-
-import kotlin.properties.ReadOnlyProperty
-import kotlin.reflect.KProperty
-
-class Resource(private var s : String = "") {
-
- override fun toString(): String {
- return s;
- }
-}
-
-class CustomDelegate(var resource: Resource = Resource()) {
-
- operator fun getValue(thisRef: Any?, property: KProperty<*>): Resource {
- println("$resource has been read in CustomDelegate from '" +
- "${property.name}' in ${thisRef?.javaClass?.typeName}")
- return resource;
- }
-
- operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Resource) {
- println("$value has been assigned to '${property.name}'" +
- " in ${thisRef?.javaClass?.typeName}")
- this.resource = value
- }
-}
-
-class CustomReadOnlyDelegate(private var resource : Resource = Resource("read-only"))
- : ReadOnlyProperty<Any?, Resource> {
- override fun getValue(thisRef: Any?, property: KProperty<*>): Resource {
- println("$resource has been read in CustomReadOnlyDelegate" +
- " from '${property.name}' in ${thisRef?.javaClass?.typeName}")
- return resource;
- }
-}
-
-class Delegates {
-
- var customDelegate : Resource by CustomDelegate()
- val customReadOnlyDelegate : Resource by CustomReadOnlyDelegate()
- val lazyString : String by lazy {
- println("Generating lazy string")
- "42"
- }
-
- fun localDelegatedProperties(compute: () -> Resource) : Resource {
- val foo by lazy(compute)
- println(foo)
- return foo
- }
-}
-class User(val map : Map<String, Any?>) {
- val name : String by map
- val age: Int by map
-}
-
-class ResourceDelegate(val r : Resource): ReadOnlyProperty<ProvidedDelegates, Resource> {
- override fun getValue(thisRef: ProvidedDelegates, property: KProperty<*>): Resource {
- return r
- }
-}
-
-class ResourceLoader(val id: Resource) {
- operator fun provideDelegate(
- thisRef: ProvidedDelegates,
- prop: KProperty<*>
- ): ReadOnlyProperty<ProvidedDelegates, Resource> {
- checkProperty(prop.name)
- return ResourceDelegate(id)
- }
-
- private fun checkProperty(name: String) {
- println("Checking property for " + name)
- }
-}
-
-class ProvidedDelegates {
- fun bindResource(id: String): ResourceLoader {
- return ResourceLoader(Resource(id))
- }
-
- val image by bindResource("image_id")
- val text by bindResource("text_id")
-}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/jvmstatic_app/MainJava.java b/src/test/java/com/android/tools/r8/kotlin/metadata/jvmstatic_app/MainJava.java
new file mode 100644
index 0000000..cfae3ef
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/jvmstatic_app/MainJava.java
@@ -0,0 +1,130 @@
+// Copyright (c) 2020, 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.jvmstatic_app;
+
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Handle;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+public class MainJava implements Opcodes {
+
+ // The java code cannot reference the kotlin-code when running in gradle, so we have it here
+ // as a dump.
+
+ // public static void main(String[] args) {
+ // InterfaceWithCompanion.greet("Hello");
+ // Lib.staticFun(() -> true);
+ // Lib.setStaticProp("Foo");
+ // System.out.println(Lib.getStaticProp());
+ // }
+
+ public static byte[] dump() {
+
+ ClassWriter classWriter = new ClassWriter(0);
+ MethodVisitor methodVisitor;
+
+ classWriter.visit(
+ V1_8,
+ ACC_PUBLIC | ACC_SUPER,
+ "com/android/tools/r8/kotlin/metadata/jvmstatic_app/MainJava",
+ null,
+ "java/lang/Object",
+ null);
+
+ classWriter.visitInnerClass(
+ "java/lang/invoke/MethodHandles$Lookup",
+ "java/lang/invoke/MethodHandles",
+ "Lookup",
+ ACC_PUBLIC | ACC_FINAL | ACC_STATIC);
+
+ {
+ methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ methodVisitor.visitCode();
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(1, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor =
+ classWriter.visitMethod(
+ ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+ methodVisitor.visitCode();
+ methodVisitor.visitLdcInsn("Hello");
+ methodVisitor.visitMethodInsn(
+ INVOKESTATIC,
+ "com/android/tools/r8/kotlin/metadata/jvmstatic_lib/InterfaceWithCompanion",
+ "greet",
+ "(Ljava/lang/String;)V",
+ true);
+ methodVisitor.visitInvokeDynamicInsn(
+ "invoke",
+ "()Lkotlin/jvm/functions/Function0;",
+ new Handle(
+ Opcodes.H_INVOKESTATIC,
+ "java/lang/invoke/LambdaMetafactory",
+ "metafactory",
+ "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;",
+ false),
+ new Object[] {
+ Type.getType("()Ljava/lang/Object;"),
+ new Handle(
+ Opcodes.H_INVOKESTATIC,
+ "com/android/tools/r8/kotlin/metadata/jvmstatic_app/MainJava",
+ "lambda$main$0",
+ "()Ljava/lang/Boolean;",
+ false),
+ Type.getType("()Ljava/lang/Boolean;")
+ });
+ methodVisitor.visitMethodInsn(
+ INVOKESTATIC,
+ "com/android/tools/r8/kotlin/metadata/jvmstatic_lib/Lib",
+ "staticFun",
+ "(Lkotlin/jvm/functions/Function0;)V",
+ false);
+ methodVisitor.visitLdcInsn("Foo");
+ methodVisitor.visitMethodInsn(
+ INVOKESTATIC,
+ "com/android/tools/r8/kotlin/metadata/jvmstatic_lib/Lib",
+ "setStaticProp",
+ "(Ljava/lang/String;)V",
+ false);
+ methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ methodVisitor.visitMethodInsn(
+ INVOKESTATIC,
+ "com/android/tools/r8/kotlin/metadata/jvmstatic_lib/Lib",
+ "getStaticProp",
+ "()Ljava/lang/String;",
+ false);
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor =
+ classWriter.visitMethod(
+ ACC_PRIVATE | ACC_STATIC | ACC_SYNTHETIC,
+ "lambda$main$0",
+ "()Ljava/lang/Boolean;",
+ null,
+ null);
+ methodVisitor.visitCode();
+ methodVisitor.visitInsn(ICONST_1);
+ methodVisitor.visitMethodInsn(
+ INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;", false);
+ methodVisitor.visitInsn(ARETURN);
+ methodVisitor.visitMaxs(1, 0);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/jvmstatic_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/jvmstatic_app/main.kt
new file mode 100644
index 0000000..6bd1218
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/jvmstatic_app/main.kt
@@ -0,0 +1,15 @@
+// Copyright (c) 2020, 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.jvmstatic_app
+
+import com.android.tools.r8.kotlin.metadata.jvmstatic_lib.InterfaceWithCompanion
+import com.android.tools.r8.kotlin.metadata.jvmstatic_lib.Lib
+
+fun main() {
+ InterfaceWithCompanion.greet("Hello")
+ Lib.staticFun { true }
+ Lib.staticProp = "Foo"
+ println(Lib.staticProp)
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/jvmstatic_lib/lib.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/jvmstatic_lib/lib.kt
new file mode 100644
index 0000000..cc0f1fb
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/jvmstatic_lib/lib.kt
@@ -0,0 +1,25 @@
+// Copyright (c) 2020, 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.jvmstatic_lib
+
+interface InterfaceWithCompanion {
+ companion object {
+ @JvmStatic fun greet(username: String) {
+ println("Hello, $username")
+ }
+ }
+}
+
+object Lib {
+
+ @JvmStatic
+ fun staticFun(func : () -> Boolean) {
+ println("Calling func...")
+ func()
+ }
+
+ @JvmStatic
+ var staticProp : String = ""
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/pruned_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/pruned_app/main.kt
new file mode 100644
index 0000000..fb35a60
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/pruned_app/main.kt
@@ -0,0 +1,11 @@
+// Copyright (c) 2020, 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.pruned_app
+
+import com.android.tools.r8.kotlin.metadata.pruned_lib.Sub
+
+fun main() {
+ println(Sub().kept())
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/pruned_lib/lib.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/pruned_lib/lib.kt
new file mode 100644
index 0000000..3ceaa4d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/pruned_lib/lib.kt
@@ -0,0 +1,20 @@
+// Copyright (c) 2020, 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.pruned_lib
+
+// The Base class will be removed during
+open class Base
+
+class Sub : Base() {
+
+ fun notKept() : Boolean {
+ return true
+ }
+
+ fun kept() : Int {
+ return 42
+ }
+}
+
diff --git a/src/test/java/com/android/tools/r8/regress/b158432019/StaticizerSyntheticUseTest.java b/src/test/java/com/android/tools/r8/regress/b158432019/StaticizerSyntheticUseTest.java
new file mode 100644
index 0000000..e07f8f1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b158432019/StaticizerSyntheticUseTest.java
@@ -0,0 +1,100 @@
+// Copyright (c) 2020, 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.regress.b158432019;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+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 StaticizerSyntheticUseTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public StaticizerSyntheticUseTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testRuntime() throws Exception {
+ testForRuntime(parameters)
+ .addProgramClasses(Singleton.class, Main.class, Sam.class, A.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Foo", "Foo", "0");
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(Singleton.class, Main.class, Sam.class, A.class)
+ .addKeepClassAndMembersRules(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .noMinification()
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Foo", "Foo", "0");
+ }
+
+ @NeverClassInline
+ public static class Singleton {
+
+ public static final Singleton singleton = new Singleton();
+
+ @NeverInline
+ public void foo() {
+ System.out.println("Foo");
+ }
+ }
+
+ public interface Sam<T> {
+ T foo(boolean bar);
+ }
+
+ public static class A {
+
+ private int instanceVar = 0;
+
+ public void caller() {
+ // Ensure that the Singleton.foo method is processed to have the generated lambda being
+ // processed - due to all callees being processed.
+ otherChainFirst();
+ Sam<Integer> f =
+ b -> {
+ if (b && instanceVar == 0) {
+ Singleton.singleton.foo();
+ }
+ return instanceVar;
+ };
+ System.out.println(f.foo(System.nanoTime() > 0));
+ }
+
+ public void otherChainFirst() {
+ otherChainSecond();
+ }
+
+ public void otherChainSecond() {
+ Singleton.singleton.foo();
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ new A().caller();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
index 4cada28..222c6a8 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -5,6 +5,7 @@
import static com.android.tools.r8.DiagnosticsChecker.checkDiagnostics;
import static com.android.tools.r8.shaking.ProguardConfigurationSourceStrings.createConfigurationForTesting;
+import static com.android.tools.r8.utils.BooleanUtils.intValue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.StringContains.containsString;
import static org.junit.Assert.assertEquals;
@@ -17,6 +18,8 @@
import static org.junit.Assert.fail;
import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.graph.ClassAccessFlags;
@@ -34,10 +37,12 @@
import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.InternalOptions.PackageObfuscationMode;
import com.android.tools.r8.utils.KeepingDiagnosticHandler;
+import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.Reporter;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
@@ -47,6 +52,8 @@
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
+import java.util.Map;
+import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Supplier;
@@ -55,6 +62,8 @@
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
class EmptyMainClassForProguardTests {
@@ -62,6 +71,7 @@
}
}
+@RunWith(Parameterized.class)
public class ProguardConfigurationParserTest extends TestBase {
private static final String VALID_PROGUARD_DIR = "src/test/proguard/valid/";
@@ -151,6 +161,13 @@
private List<String> lineSeparators = ImmutableList.of("\n", "\r\n");
private List<Character> quotes = ImmutableList.of('"', '\'');
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withNoneRuntime().build();
+ }
+
+ public ProguardConfigurationParserTest(TestParameters parameters) {}
+
@Before
public void reset() {
handler = new KeepingDiagnosticHandler();
@@ -187,7 +204,7 @@
}
@Test
- public void parseMultipleNamePatterns() throws Exception {
+ public void parseMultipleNamePatterns() {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(Paths.get(MULTIPLE_NAME_PATTERNS_FILE));
@@ -210,7 +227,7 @@
}
@Test
- public void parseNonJavaIdentifiers() throws Exception {
+ public void parseNonJavaIdentifiers() {
DexItemFactory dexItemFactory = new DexItemFactory();
ProguardConfigurationParser parser =
new ProguardConfigurationParser(dexItemFactory, new Reporter());
@@ -250,8 +267,8 @@
assertEquals(0x03, matches);
}
- private void testDontXXX(String xxx, Function<ProguardConfiguration, ProguardClassFilter> pattern)
- throws Exception {
+ private void testDontXXX(
+ String xxx, Function<ProguardConfiguration, ProguardClassFilter> pattern) {
DexItemFactory dexItemFactory = new DexItemFactory();
ProguardConfigurationParser parser =
new ProguardConfigurationParser(dexItemFactory, reporter);
@@ -265,13 +282,13 @@
}
@Test
- public void testDontXXX() throws Exception {
+ public void testDontXXX() {
testDontXXX("warn", ProguardConfiguration::getDontWarnPatterns);
testDontXXX("note", ProguardConfiguration::getDontNotePatterns);
}
private void testDontXXXMultiple(
- String xxx, Function<ProguardConfiguration, ProguardClassFilter> pattern) throws Exception {
+ String xxx, Function<ProguardConfiguration, ProguardClassFilter> pattern) {
DexItemFactory dexItemFactory = new DexItemFactory();
ProguardConfigurationParser parser =
new ProguardConfigurationParser(dexItemFactory, reporter);
@@ -288,13 +305,13 @@
}
@Test
- public void testDontWarnMultiple() throws Exception {
+ public void testDontWarnMultiple() {
testDontXXXMultiple("warn", ProguardConfiguration::getDontWarnPatterns);
testDontXXXMultiple("note", ProguardConfiguration::getDontNotePatterns);
}
private void testDontXXXAllExplicitly(
- String xxx, Function<ProguardConfiguration, ProguardClassFilter> pattern) throws Exception {
+ String xxx, Function<ProguardConfiguration, ProguardClassFilter> pattern) {
DexItemFactory dexItemFactory = new DexItemFactory();
ProguardConfigurationParser parser =
new ProguardConfigurationParser(dexItemFactory, reporter);
@@ -308,13 +325,13 @@
}
@Test
- public void testDontWarnAllExplicitly() throws Exception {
+ public void testDontWarnAllExplicitly() {
testDontXXXAllExplicitly("warn", ProguardConfiguration::getDontWarnPatterns);
testDontXXXAllExplicitly("note", ProguardConfiguration::getDontNotePatterns);
}
private void testDontXXXAllImplicitly(
- String xxx, Function<ProguardConfiguration, ProguardClassFilter> pattern) throws Exception {
+ String xxx, Function<ProguardConfiguration, ProguardClassFilter> pattern) {
DexItemFactory dexItemFactory = new DexItemFactory();
ProguardConfigurationParser parser =
new ProguardConfigurationParser(dexItemFactory, reporter);
@@ -328,13 +345,13 @@
}
@Test
- public void testDontWarnAllImplicitly() throws Exception {
+ public void testDontWarnAllImplicitly() {
testDontXXXAllImplicitly("warn", ProguardConfiguration::getDontWarnPatterns);
testDontXXXAllImplicitly("note", ProguardConfiguration::getDontNotePatterns);
}
@Test
- public void parseAccessFlags() throws Exception {
+ public void parseAccessFlags() {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(Paths.get(ACCESS_FLAGS_FILE));
@@ -380,7 +397,7 @@
}
@Test
- public void parseWhyAreYouKeeping() throws Exception {
+ public void parseWhyAreYouKeeping() {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(Paths.get(WHY_ARE_YOU_KEEPING_FILE));
@@ -395,7 +412,7 @@
}
@Test
- public void parseAssumeNoSideEffects() throws Exception {
+ public void parseAssumeNoSideEffects() {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(Paths.get(ASSUME_NO_SIDE_EFFECTS));
@@ -408,7 +425,7 @@
}
@Test
- public void parseAssumeNoSideEffectsWithReturnValue() throws Exception {
+ public void parseAssumeNoSideEffectsWithReturnValue() {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(Paths.get(ASSUME_NO_SIDE_EFFECTS_WITH_RETURN_VALUE));
@@ -477,7 +494,7 @@
}
@Test
- public void parseAssumeValuesWithReturnValue() throws Exception {
+ public void parseAssumeValuesWithReturnValue() {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(Paths.get(ASSUME_VALUES_WITH_RETURN_VALUE));
@@ -546,7 +563,7 @@
}
@Test
- public void testAdaptClassStrings() throws Exception {
+ public void testAdaptClassStrings() {
DexItemFactory dexItemFactory = new DexItemFactory();
ProguardConfigurationParser parser =
new ProguardConfigurationParser(dexItemFactory, reporter);
@@ -563,7 +580,7 @@
}
@Test
- public void testAdaptClassStringsMultiple() throws Exception {
+ public void testAdaptClassStringsMultiple() {
DexItemFactory dexItemFactory = new DexItemFactory();
ProguardConfigurationParser parser =
new ProguardConfigurationParser(dexItemFactory, reporter);
@@ -584,7 +601,7 @@
}
@Test
- public void testAdaptClassStringsAllExplicitly() throws Exception {
+ public void testAdaptClassStringsAllExplicitly() {
DexItemFactory dexItemFactory = new DexItemFactory();
ProguardConfigurationParser parser =
new ProguardConfigurationParser(dexItemFactory, reporter);
@@ -601,7 +618,7 @@
}
@Test
- public void testAdaptClassStringsAllImplicitly() throws Exception {
+ public void testAdaptClassStringsAllImplicitly() {
DexItemFactory dexItemFactory = new DexItemFactory();
ProguardConfigurationParser parser =
new ProguardConfigurationParser(dexItemFactory, reporter);
@@ -618,7 +635,7 @@
}
@Test
- public void testIdentifierNameString() throws Exception {
+ public void testIdentifierNameString() {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
String config1 =
@@ -656,14 +673,22 @@
});
assertEquals(1, identifierNameStrings.get(2).getClassNames().size());
assertEquals("*", identifierNameStrings.get(2).getClassNames().toString());
- identifierNameStrings.get(2).getMemberRules().forEach(memberRule -> {
- assertEquals(ProguardMemberType.ALL, memberRule.getRuleType());
- assertTrue(memberRule.getAnnotation().toString().endsWith("IdentifierNameString"));
- });
+ identifierNameStrings
+ .get(2)
+ .getMemberRules()
+ .forEach(
+ memberRule -> {
+ assertEquals(ProguardMemberType.ALL, memberRule.getRuleType());
+ assertEquals(1, memberRule.getAnnotations().size());
+ assertTrue(
+ ListUtils.first(memberRule.getAnnotations())
+ .toString()
+ .endsWith("IdentifierNameString"));
+ });
}
@Test
- public void parseDontobfuscate() throws Exception {
+ public void parseDontobfuscate() {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(Paths.get(DONT_OBFUSCATE));
@@ -673,7 +698,7 @@
}
@Test
- public void parseRepackageClassesEmpty() throws Exception {
+ public void parseRepackageClassesEmpty() {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(Paths.get(PACKAGE_OBFUSCATION_1));
@@ -685,7 +710,7 @@
}
@Test
- public void parseRepackageClassesNonEmpty() throws Exception {
+ public void parseRepackageClassesNonEmpty() {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(Paths.get(PACKAGE_OBFUSCATION_2));
@@ -697,7 +722,7 @@
}
@Test
- public void parseFlattenPackageHierarchyEmpty() throws Exception {
+ public void parseFlattenPackageHierarchyEmpty() {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(Paths.get(PACKAGE_OBFUSCATION_3));
@@ -709,7 +734,7 @@
}
@Test
- public void parseFlattenPackageHierarchyNonEmpty() throws Exception {
+ public void parseFlattenPackageHierarchyNonEmpty() {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(Paths.get(PACKAGE_OBFUSCATION_4));
@@ -721,7 +746,7 @@
}
@Test
- public void flattenPackageHierarchyCannotOverrideRepackageClasses() throws Exception {
+ public void flattenPackageHierarchyCannotOverrideRepackageClasses() {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
Path path = Paths.get(PACKAGE_OBFUSCATION_5);
@@ -735,7 +760,7 @@
}
@Test
- public void repackageClassesOverridesFlattenPackageHierarchy() throws Exception {
+ public void repackageClassesOverridesFlattenPackageHierarchy() {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
Path path = Paths.get(PACKAGE_OBFUSCATION_6);
@@ -749,7 +774,7 @@
}
@Test
- public void parseApplyMapping() throws Exception {
+ public void parseApplyMapping() {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(Paths.get(APPLY_MAPPING));
@@ -759,7 +784,7 @@
}
@Test
- public void parseApplyMappingWithoutFile() throws Exception {
+ public void parseApplyMappingWithoutFile() {
Path path = Paths.get(APPLY_MAPPING_WITHOUT_FILE);
try {
ProguardConfigurationParser parser =
@@ -781,7 +806,7 @@
}
@Test
- public void parseIncluding() throws Exception {
+ public void parseIncluding() {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(Paths.get(INCLUDING));
@@ -789,7 +814,7 @@
}
@Test
- public void parseInvalidIncluding1() throws IOException {
+ public void parseInvalidIncluding1() {
Path path = Paths.get(INVALID_INCLUDING_1);
try {
new ProguardConfigurationParser(new DexItemFactory(), reporter)
@@ -1442,7 +1467,7 @@
}
@Test
- public void parseKeepParameterNames() throws Exception {
+ public void parseKeepParameterNames() {
try {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
@@ -1458,7 +1483,7 @@
}
@Test
- public void parseKeepParameterNamesWithoutMinification() throws Exception {
+ public void parseKeepParameterNamesWithoutMinification() {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(createConfigurationForTesting(ImmutableList.of(
@@ -1483,7 +1508,7 @@
}
@Test
- public void parseShortLine() throws IOException {
+ public void parseShortLine() {
try {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
@@ -1496,7 +1521,7 @@
}
@Test
- public void parseNoLocals() throws IOException {
+ public void parseNoLocals() {
try {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
@@ -2915,4 +2940,239 @@
parser.parse(proguardConfig);
verifyParserEndsCleanly();
}
+
+ @Test
+ public void parseClassAnnotationsAndFlags() {
+ List<String> configurationContents =
+ ImmutableList.of(
+ // 1 annotation without flags.
+ "-keep @Foo class *",
+ // 1 annotation with public flag.
+ "-keep public @Foo class *",
+ "-keep @Foo public class *",
+ // 1 annotation with public final flags.
+ "-keep public final @Foo class *",
+ "-keep public @Foo final class *",
+ "-keep @Foo public final class *",
+ // 2 annotations without flags.
+ "-keep @Foo @Bar class *",
+ // 2 annotations with public flag.
+ "-keep public @Foo @Bar class *",
+ "-keep @Foo public @Bar class *",
+ "-keep @Foo @Bar public class *",
+ // 2 annotations with public final flags.
+ "-keep public final @Foo @Bar class *",
+ "-keep public @Foo final @Bar class *",
+ "-keep public @Foo @Bar final class *",
+ "-keep @Foo public final @Bar class *",
+ "-keep @Foo public @Bar final class *",
+ "-keep @Foo @Bar public final class *");
+ for (String configurationContent : configurationContents) {
+ DexItemFactory dexItemFactory = new DexItemFactory();
+ ProguardConfigurationParser parser =
+ new ProguardConfigurationParser(dexItemFactory, reporter);
+ parser.parse(createConfigurationForTesting(ImmutableList.of(configurationContent)));
+ verifyParserEndsCleanly();
+
+ ProguardConfiguration configuration = parser.getConfig();
+ assertEquals(1, configuration.getRules().size());
+
+ ProguardKeepRule rule = ListUtils.first(configuration.getRules()).asProguardKeepRule();
+ assertEquals(configurationContent.contains("final"), rule.getClassAccessFlags().isFinal());
+ assertEquals(configurationContent.contains("public"), rule.getClassAccessFlags().isPublic());
+ assertEquals(
+ 1 + intValue(configurationContent.contains("@Bar")), rule.getClassAnnotations().size());
+
+ ProguardTypeMatcher.MatchSpecificType fooAnnotation =
+ rule.getClassAnnotations().get(0).asSpecificTypeMatcher();
+ assertEquals("Foo", fooAnnotation.getSpecificType().toSourceString());
+
+ if (configurationContent.contains("@Bar")) {
+ ProguardTypeMatcher.MatchSpecificType barAnnotation =
+ rule.getClassAnnotations().get(1).asSpecificTypeMatcher();
+ assertEquals("Bar", barAnnotation.getSpecificType().toSourceString());
+ }
+ }
+ }
+
+ @Test
+ public void parseFieldAnnotationsAndFlags() {
+ Map<String, Optional<Class<? extends Exception>>> configurationContents =
+ ImmutableMap.<String, Optional<Class<? extends Exception>>>builder()
+ // 1 annotation without flags.
+ .put("-keep class * { @Foo Type FIELD; }", Optional.empty())
+ // 1 annotation with public flag.
+ .put("-keep class * { public @Foo Type FIELD; }", Optional.of(RuntimeException.class))
+ .put("-keep class * { @Foo public Type FIELD; }", Optional.empty())
+ // 1 annotation with public final flags.
+ .put(
+ "-keep class * { public final @Foo Type FIELD; }",
+ Optional.of(RuntimeException.class))
+ .put(
+ "-keep class * { public @Foo final Type FIELD; }",
+ Optional.of(RuntimeException.class))
+ .put("-keep class * { @Foo public final Type FIELD; }", Optional.empty())
+ //// 2 annotations without flags.
+ .put("-keep class * { @Foo @Bar Type FIELD; }", Optional.empty())
+ //// 2 annotations with public flag.
+ .put(
+ "-keep class * { public @Foo @Bar Type FIELD; }",
+ Optional.of(RuntimeException.class))
+ .put(
+ "-keep class * { @Foo public @Bar Type FIELD; }",
+ Optional.of(RuntimeException.class))
+ .put("-keep class * { @Foo @Bar public Type FIELD; }", Optional.empty())
+ //// 2 annotations with public final flags.
+ .put(
+ "-keep class * { public final @Foo @Bar Type FIELD; }",
+ Optional.of(RuntimeException.class))
+ .put(
+ "-keep class * { public @Foo final @Bar Type FIELD; }",
+ Optional.of(RuntimeException.class))
+ .put(
+ "-keep class * { public @Foo @Bar final Type FIELD; }",
+ Optional.of(RuntimeException.class))
+ .put(
+ "-keep class * { @Foo public final @Bar Type FIELD; }",
+ Optional.of(RuntimeException.class))
+ .put(
+ "-keep class * { @Foo public @Bar final Type FIELD; }",
+ Optional.of(RuntimeException.class))
+ .put("-keep class * { @Foo @Bar public final Type FIELD; }", Optional.empty())
+ .build();
+ configurationContents.forEach(
+ (configurationContent, expectedExceptionClass) -> {
+ DexItemFactory dexItemFactory = new DexItemFactory();
+ ProguardConfigurationParser parser =
+ new ProguardConfigurationParser(dexItemFactory, reporter);
+ try {
+ parser.parse(createConfigurationForTesting(ImmutableList.of(configurationContent)));
+ assertFalse(expectedExceptionClass.isPresent());
+ } catch (Throwable e) {
+ assertTrue(expectedExceptionClass.isPresent());
+ assertEquals(expectedExceptionClass.get(), e.getClass());
+ reset();
+ return;
+ }
+
+ verifyParserEndsCleanly();
+
+ ProguardConfiguration configuration = parser.getConfig();
+ assertEquals(1, configuration.getRules().size());
+
+ ProguardKeepRule rule = ListUtils.first(configuration.getRules()).asProguardKeepRule();
+ assertEquals(1, rule.getMemberRules().size());
+
+ ProguardMemberRule memberRule = ListUtils.first(rule.getMemberRules());
+
+ assertEquals(
+ configurationContent.contains("final"), memberRule.getAccessFlags().isFinal());
+ assertEquals(
+ configurationContent.contains("public"), memberRule.getAccessFlags().isPublic());
+ assertEquals(
+ 1 + intValue(configurationContent.contains("@Bar")),
+ memberRule.getAnnotations().size());
+
+ ProguardTypeMatcher.MatchSpecificType fooAnnotation =
+ memberRule.getAnnotations().get(0).asSpecificTypeMatcher();
+ assertEquals("Foo", fooAnnotation.getSpecificType().toSourceString());
+
+ if (configurationContent.contains("@Bar")) {
+ ProguardTypeMatcher.MatchSpecificType barAnnotation =
+ memberRule.getAnnotations().get(1).asSpecificTypeMatcher();
+ assertEquals("Bar", barAnnotation.getSpecificType().toSourceString());
+ }
+ });
+ }
+
+ @Test
+ public void parseMethodAnnotationsAndFlags() {
+ Map<String, Optional<Class<? extends Exception>>> configurationContents =
+ ImmutableMap.<String, Optional<Class<? extends Exception>>>builder()
+ // 1 annotation without flags.
+ .put("-keep class * { @Foo Type method(); }", Optional.empty())
+ // 1 annotation with public flag.
+ .put(
+ "-keep class * { public @Foo Type method(); }", Optional.of(RuntimeException.class))
+ .put("-keep class * { @Foo public Type method(); }", Optional.empty())
+ // 1 annotation with public final flags.
+ .put(
+ "-keep class * { public final @Foo Type method(); }",
+ Optional.of(RuntimeException.class))
+ .put(
+ "-keep class * { public @Foo final Type method(); }",
+ Optional.of(RuntimeException.class))
+ .put("-keep class * { @Foo public final Type method(); }", Optional.empty())
+ //// 2 annotations without flags.
+ .put("-keep class * { @Foo @Bar Type method(); }", Optional.empty())
+ //// 2 annotations with public flag.
+ .put(
+ "-keep class * { public @Foo @Bar Type method(); }",
+ Optional.of(RuntimeException.class))
+ .put(
+ "-keep class * { @Foo public @Bar Type method(); }",
+ Optional.of(RuntimeException.class))
+ .put("-keep class * { @Foo @Bar public Type method(); }", Optional.empty())
+ //// 2 annotations with public final flags.
+ .put(
+ "-keep class * { public final @Foo @Bar Type method(); }",
+ Optional.of(RuntimeException.class))
+ .put(
+ "-keep class * { public @Foo final @Bar Type method(); }",
+ Optional.of(RuntimeException.class))
+ .put(
+ "-keep class * { public @Foo @Bar final Type method(); }",
+ Optional.of(RuntimeException.class))
+ .put(
+ "-keep class * { @Foo public final @Bar Type method(); }",
+ Optional.of(RuntimeException.class))
+ .put(
+ "-keep class * { @Foo public @Bar final Type method(); }",
+ Optional.of(RuntimeException.class))
+ .put("-keep class * { @Foo @Bar public final Type method(); }", Optional.empty())
+ .build();
+ configurationContents.forEach(
+ (configurationContent, expectedExceptionClass) -> {
+ DexItemFactory dexItemFactory = new DexItemFactory();
+ ProguardConfigurationParser parser =
+ new ProguardConfigurationParser(dexItemFactory, reporter);
+ try {
+ parser.parse(createConfigurationForTesting(ImmutableList.of(configurationContent)));
+ assertFalse(expectedExceptionClass.isPresent());
+ } catch (Throwable e) {
+ assertTrue(expectedExceptionClass.isPresent());
+ assertEquals(expectedExceptionClass.get(), e.getClass());
+ reset();
+ return;
+ }
+
+ verifyParserEndsCleanly();
+
+ ProguardConfiguration configuration = parser.getConfig();
+ assertEquals(1, configuration.getRules().size());
+
+ ProguardKeepRule rule = ListUtils.first(configuration.getRules()).asProguardKeepRule();
+ assertEquals(1, rule.getMemberRules().size());
+
+ ProguardMemberRule memberRule = ListUtils.first(rule.getMemberRules());
+
+ assertEquals(
+ configurationContent.contains("final"), memberRule.getAccessFlags().isFinal());
+ assertEquals(
+ configurationContent.contains("public"), memberRule.getAccessFlags().isPublic());
+ assertEquals(
+ 1 + intValue(configurationContent.contains("@Bar")),
+ memberRule.getAnnotations().size());
+
+ ProguardTypeMatcher.MatchSpecificType fooAnnotation =
+ memberRule.getAnnotations().get(0).asSpecificTypeMatcher();
+ assertEquals("Foo", fooAnnotation.getSpecificType().toSourceString());
+
+ if (configurationContent.contains("@Bar")) {
+ ProguardTypeMatcher.MatchSpecificType barAnnotation =
+ memberRule.getAnnotations().get(1).asSpecificTypeMatcher();
+ assertEquals("Bar", barAnnotation.getSpecificType().toSourceString());
+ }
+ });
+ }
}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/KeepWithMultipleClassAnnotationsTest.java b/src/test/java/com/android/tools/r8/shaking/annotations/KeepWithMultipleClassAnnotationsTest.java
new file mode 100644
index 0000000..80dab7c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/KeepWithMultipleClassAnnotationsTest.java
@@ -0,0 +1,102 @@
+// Copyright (c) 2020, 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.annotations;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class KeepWithMultipleClassAnnotationsTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public KeepWithMultipleClassAnnotationsTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testA() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(KeepWithMultipleClassAnnotationsTest.class)
+ .addKeepRules(
+ "-keep @" + A.class.getTypeName() + " class * {",
+ " public static void main(java.lang.String[]);",
+ "}")
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Hello world!");
+ }
+
+ @Test
+ public void testAB() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(KeepWithMultipleClassAnnotationsTest.class)
+ .addKeepRules(
+ "-keep @" + A.class.getTypeName() + " @" + B.class.getTypeName() + " class * {",
+ " public static void main(java.lang.String[]);",
+ "}")
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Hello world!");
+ }
+
+ @Test
+ public void testABC() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(KeepWithMultipleClassAnnotationsTest.class)
+ .addKeepRules(
+ "-keep @"
+ + A.class.getTypeName()
+ + " @"
+ + B.class.getTypeName()
+ + " @"
+ + C.class.getTypeName()
+ + " class * {",
+ " public static void main(java.lang.String[]);",
+ "}")
+ .allowUnusedProguardConfigurationRules()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(inspector -> assertTrue(inspector.allClasses().isEmpty()));
+ }
+
+ @A
+ @B
+ static class TestClass {
+
+ public static void main(String[] args) {
+ System.out.println("Hello world!");
+ }
+ }
+
+ @Retention(RetentionPolicy.CLASS)
+ @Target({ElementType.TYPE})
+ @interface A {}
+
+ @Retention(RetentionPolicy.CLASS)
+ @Target({ElementType.TYPE})
+ @interface B {}
+
+ @Retention(RetentionPolicy.CLASS)
+ @Target({ElementType.TYPE})
+ @interface C {}
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/KeepWithMultipleInheritanceAnnotationsTest.java b/src/test/java/com/android/tools/r8/shaking/annotations/KeepWithMultipleInheritanceAnnotationsTest.java
new file mode 100644
index 0000000..25e610b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/KeepWithMultipleInheritanceAnnotationsTest.java
@@ -0,0 +1,108 @@
+// Copyright (c) 2020, 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.annotations;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class KeepWithMultipleInheritanceAnnotationsTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public KeepWithMultipleInheritanceAnnotationsTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testA() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(KeepWithMultipleInheritanceAnnotationsTest.class)
+ .addKeepRules(
+ "-keep class * extends @" + A.class.getTypeName() + " * {",
+ " public static void main(java.lang.String[]);",
+ "}")
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Hello world!");
+ }
+
+ @Test
+ public void testAB() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(KeepWithMultipleInheritanceAnnotationsTest.class)
+ .addKeepRules(
+ "-keep class * extends @"
+ + A.class.getTypeName()
+ + " @"
+ + B.class.getTypeName()
+ + " * {",
+ " public static void main(java.lang.String[]);",
+ "}")
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Hello world!");
+ }
+
+ @Test
+ public void testABC() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(KeepWithMultipleInheritanceAnnotationsTest.class)
+ .addKeepRules(
+ "-keep class * extends @"
+ + A.class.getTypeName()
+ + " @"
+ + B.class.getTypeName()
+ + " @"
+ + C.class.getTypeName()
+ + " * {",
+ " public static void main(java.lang.String[]);",
+ "}")
+ .allowUnusedProguardConfigurationRules()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(inspector -> assertTrue(inspector.allClasses().isEmpty()));
+ }
+
+ @A
+ @B
+ static class TestClassBase {}
+
+ static class TestClass extends TestClassBase {
+
+ public static void main(String[] args) {
+ System.out.println("Hello world!");
+ }
+ }
+
+ @Retention(RetentionPolicy.CLASS)
+ @Target({ElementType.TYPE})
+ @interface A {}
+
+ @Retention(RetentionPolicy.CLASS)
+ @Target({ElementType.TYPE})
+ @interface B {}
+
+ @Retention(RetentionPolicy.CLASS)
+ @Target({ElementType.TYPE})
+ @interface C {}
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/KeepWithMultipleMemberAnnotationsTest.java b/src/test/java/com/android/tools/r8/shaking/annotations/KeepWithMultipleMemberAnnotationsTest.java
new file mode 100644
index 0000000..cafe2a4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/KeepWithMultipleMemberAnnotationsTest.java
@@ -0,0 +1,106 @@
+// Copyright (c) 2020, 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.annotations;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class KeepWithMultipleMemberAnnotationsTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public KeepWithMultipleMemberAnnotationsTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testA() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(KeepWithMultipleMemberAnnotationsTest.class)
+ .addKeepRules(
+ "-keepclasseswithmembers class * {",
+ " @" + A.class.getTypeName() + " public static void main(java.lang.String[]);",
+ "}")
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Hello world!");
+ }
+
+ @Test
+ public void testAB() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(KeepWithMultipleMemberAnnotationsTest.class)
+ .addKeepRules(
+ "-keepclasseswithmembers class * {",
+ " @"
+ + A.class.getTypeName()
+ + " @"
+ + B.class.getTypeName()
+ + " public static void main(java.lang.String[]);",
+ "}")
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Hello world!");
+ }
+
+ @Test
+ public void testABC() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(KeepWithMultipleMemberAnnotationsTest.class)
+ .addKeepRules(
+ "-keepclasseswithmembers class * {",
+ " @"
+ + A.class.getTypeName()
+ + " @"
+ + B.class.getTypeName()
+ + " @"
+ + C.class.getTypeName()
+ + " public static void main(java.lang.String[]);",
+ "}")
+ .allowUnusedProguardConfigurationRules()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(inspector -> assertTrue(inspector.allClasses().isEmpty()));
+ }
+
+ static class TestClass {
+
+ @A
+ @B
+ public static void main(String[] args) {
+ System.out.println("Hello world!");
+ }
+ }
+
+ @Retention(RetentionPolicy.CLASS)
+ @Target({ElementType.METHOD})
+ @interface A {}
+
+ @Retention(RetentionPolicy.CLASS)
+ @Target({ElementType.METHOD})
+ @interface B {}
+
+ @Retention(RetentionPolicy.CLASS)
+ @Target({ElementType.METHOD})
+ @interface C {}
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/KeepWithMultipleParameterAnnotationsTest.java b/src/test/java/com/android/tools/r8/shaking/annotations/KeepWithMultipleParameterAnnotationsTest.java
new file mode 100644
index 0000000..795574a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/KeepWithMultipleParameterAnnotationsTest.java
@@ -0,0 +1,104 @@
+// Copyright (c) 2020, 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.annotations;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class KeepWithMultipleParameterAnnotationsTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public KeepWithMultipleParameterAnnotationsTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testA() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(KeepWithMultipleParameterAnnotationsTest.class)
+ .addKeepRules(
+ "-keepclasseswithmembers class * {",
+ " @" + A.class.getTypeName() + " public static void main(java.lang.String[]);",
+ "}")
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Hello world!");
+ }
+
+ @Test
+ public void testAB() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(KeepWithMultipleParameterAnnotationsTest.class)
+ .addKeepRules(
+ "-keepclasseswithmembers class * {",
+ " @"
+ + A.class.getTypeName()
+ + " @"
+ + B.class.getTypeName()
+ + " public static void main(java.lang.String[]);",
+ "}")
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Hello world!");
+ }
+
+ @Test
+ public void testABC() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(KeepWithMultipleParameterAnnotationsTest.class)
+ .addKeepRules(
+ "-keepclasseswithmembers class * {",
+ " @"
+ + A.class.getTypeName()
+ + " @"
+ + B.class.getTypeName()
+ + " @"
+ + C.class.getTypeName()
+ + " public static void main(java.lang.String[]);",
+ "}")
+ .allowUnusedProguardConfigurationRules()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(inspector -> assertTrue(inspector.allClasses().isEmpty()));
+ }
+
+ static class TestClass {
+
+ public static void main(@A @B String[] args) {
+ System.out.println("Hello world!");
+ }
+ }
+
+ @Retention(RetentionPolicy.CLASS)
+ @Target({ElementType.PARAMETER})
+ @interface A {}
+
+ @Retention(RetentionPolicy.CLASS)
+ @Target({ElementType.PARAMETER})
+ @interface B {}
+
+ @Retention(RetentionPolicy.CLASS)
+ @Target({ElementType.PARAMETER})
+ @interface C {}
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/B152492625.java b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/B152492625.java
index 560f6ea..5b0f344 100644
--- a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/B152492625.java
+++ b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/B152492625.java
@@ -11,23 +11,28 @@
import static org.hamcrest.CoreMatchers.allOf;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.Diagnostic;
import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestDiagnosticMessages;
import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.position.TextPosition;
import com.android.tools.r8.position.TextRange;
+import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import java.util.List;
import org.hamcrest.Matcher;
import org.junit.Assert;
-import org.junit.Assume;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -36,14 +41,17 @@
public class B152492625 extends TestBase {
private final TestParameters parameters;
+ private final boolean dontWarnObject;
- @Parameterized.Parameters(name = "{0}")
- public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimesAndApiLevels().build();
+ @Parameterized.Parameters(name = "{0}, dontWarnObject {1}")
+ public static Collection<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
}
- public B152492625(TestParameters parameters) {
+ public B152492625(TestParameters parameters, boolean dontWarnObject) {
this.parameters = parameters;
+ this.dontWarnObject = dontWarnObject;
}
private void noCallToWait(CodeInspector inspector) {
@@ -60,14 +68,14 @@
}));
}
- private Matcher<Diagnostic> matchAssumeNoSideEffectsWarningMessage() {
+ private Matcher<Diagnostic> matchAssumeNoSideEffectsMessage() {
return diagnosticMessage(
containsString(
"The -assumenosideeffects rule matches methods on `java.lang.Object` with"
+ " wildcards"));
}
- private Matcher<Diagnostic> matchWarningMessageForAllProblematicMethods() {
+ private Matcher<Diagnostic> matchMessageForAllProblematicMethods() {
return diagnosticMessage(
allOf(
containsString("void notify()"),
@@ -77,7 +85,7 @@
containsString("void wait(long, int)")));
}
- private Matcher<Diagnostic> matchWarningMessageForWaitMethods() {
+ private Matcher<Diagnostic> matchMessageForWaitMethods() {
return diagnosticMessage(
allOf(
containsString("void wait()"),
@@ -85,6 +93,17 @@
containsString("void wait(long, int)")));
}
+ private void assertErrorsOrWarnings(
+ TestDiagnosticMessages diagnostics, List<Matcher<Diagnostic>> matchers) {
+ if (dontWarnObject) {
+ diagnostics.assertOnlyWarnings();
+ diagnostics.assertWarningsMatch(matchers);
+ } else {
+ diagnostics.assertOnlyErrors();
+ diagnostics.assertErrorsMatch(matchers);
+ }
+ }
+
private TextRange textRangeForString(String s) {
return new TextRange(
new TextPosition(0, 1, 1), new TextPosition(s.length(), 1, s.length() + 1));
@@ -92,21 +111,27 @@
@Test
public void testR8AllMatch() throws Exception {
- testForR8(parameters.getBackend())
- .addProgramClasses(TestClass.class, B.class)
- .addKeepMainRule(TestClass.class)
- .addKeepRules("-assumenosideeffects class " + B.class.getTypeName() + " { *; }")
- .setMinApi(parameters.getApiLevel())
- .allowDiagnosticWarningMessages()
- .compileWithExpectedDiagnostics(
- diagnostics -> {
- diagnostics.assertOnlyWarnings();
- diagnostics.assertWarningsMatch(matchAssumeNoSideEffectsWarningMessage());
- diagnostics.assertWarningsMatch(matchWarningMessageForAllProblematicMethods());
- })
- .inspect(this::noCallToWait)
- .run(parameters.getRuntime(), TestClass.class)
- .assertSuccessWithOutputLines("Hello, world");
+ List<Matcher<Diagnostic>> matchers =
+ ImmutableList.of(
+ allOf(matchAssumeNoSideEffectsMessage(), matchMessageForAllProblematicMethods()));
+
+ try {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(TestClass.class, B.class)
+ .addKeepMainRule(TestClass.class)
+ .applyIf(dontWarnObject, tb -> tb.addKeepRules("-dontwarn java.lang.Object"))
+ .addKeepRules("-assumenosideeffects class " + B.class.getTypeName() + " { *; }")
+ .setMinApi(parameters.getApiLevel())
+ .allowDiagnosticWarningMessages()
+ .compileWithExpectedDiagnostics(
+ diagnostics -> assertErrorsOrWarnings(diagnostics, matchers))
+ .inspect(this::noCallToWait)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Hello, world");
+ assertTrue(dontWarnObject);
+ } catch (CompilationFailedException e) {
+ assertFalse(dontWarnObject);
+ }
}
@Test
@@ -131,93 +156,124 @@
String starRule = "-assumenosideeffects class " + B.class.getTypeName() + " { *; }";
String methodsRule = "-assumenosideeffects class " + B.class.getTypeName() + " { <methods>; }";
- testForR8(parameters.getBackend())
- .addProgramClasses(TestClass.class, B.class)
- .addKeepMainRule(TestClass.class)
- .apply(
- b ->
- b.getBuilder().addProguardConfiguration(ImmutableList.of(starRule), starRuleOrigin))
- .apply(
- b ->
- b.getBuilder()
- .addProguardConfiguration(ImmutableList.of(methodsRule), methodsRuleOrigin))
- .setMinApi(parameters.getApiLevel())
- .allowDiagnosticWarningMessages()
- .compileWithExpectedDiagnostics(
- diagnostics -> {
- diagnostics.assertOnlyWarnings();
- diagnostics.assertWarningsMatch(
- ImmutableList.of(
- allOf(
- matchAssumeNoSideEffectsWarningMessage(),
- matchWarningMessageForAllProblematicMethods(),
- diagnosticOrigin(starRuleOrigin),
- diagnosticPosition(textRangeForString(starRule))),
- allOf(
- matchAssumeNoSideEffectsWarningMessage(),
- matchWarningMessageForAllProblematicMethods(),
- diagnosticOrigin(methodsRuleOrigin),
- diagnosticPosition(textRangeForString(methodsRule)))));
- })
- .inspect(this::noCallToWait)
- .run(parameters.getRuntime(), TestClass.class)
- .assertSuccessWithOutputLines("Hello, world");
+ List<Matcher<Diagnostic>> matchers =
+ ImmutableList.of(
+ allOf(
+ matchAssumeNoSideEffectsMessage(),
+ matchMessageForAllProblematicMethods(),
+ diagnosticOrigin(starRuleOrigin),
+ diagnosticPosition(textRangeForString(starRule))),
+ allOf(
+ matchAssumeNoSideEffectsMessage(),
+ matchMessageForAllProblematicMethods(),
+ diagnosticOrigin(methodsRuleOrigin),
+ diagnosticPosition(textRangeForString(methodsRule))));
+
+ try {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(TestClass.class, B.class)
+ .addKeepMainRule(TestClass.class)
+ .applyIf(dontWarnObject, tb -> tb.addKeepRules("-dontwarn java.lang.Object"))
+ .apply(
+ b ->
+ b.getBuilder()
+ .addProguardConfiguration(ImmutableList.of(starRule), starRuleOrigin))
+ .apply(
+ b ->
+ b.getBuilder()
+ .addProguardConfiguration(ImmutableList.of(methodsRule), methodsRuleOrigin))
+ .setMinApi(parameters.getApiLevel())
+ .allowDiagnosticWarningMessages()
+ .compileWithExpectedDiagnostics(
+ diagnostics -> assertErrorsOrWarnings(diagnostics, matchers))
+ .inspect(this::noCallToWait)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Hello, world");
+ assertTrue(dontWarnObject);
+ } catch (CompilationFailedException e) {
+ assertFalse(dontWarnObject);
+ }
}
@Test
- public void testR8AllMatchDontWarn() throws Exception {
+ public void testR8NonProblemeticMatchDontWarn() throws Exception {
testForR8(parameters.getBackend())
.addProgramClasses(TestClass.class, B.class)
.addKeepMainRule(TestClass.class)
- .addKeepRules("-assumenosideeffects class " + B.class.getTypeName() + " { *; }")
- .addKeepRules("-dontwarn java.lang.Object")
+ .applyIf(dontWarnObject, tb -> tb.addKeepRules("-dontwarn java.lang.Object"))
+ .addKeepRules("-assumenosideeffects class " + B.class.getTypeName() + " { hash*(); }")
.setMinApi(parameters.getApiLevel())
- .compile()
- .inspect(this::noCallToWait)
+ .allowDiagnosticWarningMessages(!dontWarnObject)
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ if (dontWarnObject) {
+ diagnostics.assertNoMessages();
+ } else {
+ diagnostics.assertOnlyWarnings();
+ diagnostics.assertWarningsMatch(
+ allOf(
+ matchAssumeNoSideEffectsMessage(),
+ diagnosticMessage(containsString("int hashCode()"))));
+ }
+ })
.run(parameters.getRuntime(), TestClass.class)
- .assertSuccessWithOutputLines("Hello, world");
+ // Code fails with exception if wait call is not removed.
+ .assertFailureWithErrorThatThrows(IllegalMonitorStateException.class);
}
@Test
public void testR8AllMethodsMatch() throws Exception {
- testForR8(parameters.getBackend())
- .addProgramClasses(TestClass.class, B.class)
- .addKeepMainRule(TestClass.class)
- .addKeepRules("-assumenosideeffects class " + B.class.getTypeName() + " { <methods>; }")
- .setMinApi(parameters.getApiLevel())
- .allowDiagnosticWarningMessages()
- .compileWithExpectedDiagnostics(
- diagnostics -> {
- diagnostics.assertOnlyWarnings();
- diagnostics.assertWarningsMatch(matchAssumeNoSideEffectsWarningMessage());
- diagnostics.assertWarningsMatch(matchWarningMessageForAllProblematicMethods());
- })
- .inspect(this::noCallToWait)
- .run(parameters.getRuntime(), TestClass.class)
- .assertSuccessWithOutputLines("Hello, world");
+ List<Matcher<Diagnostic>> matchers =
+ ImmutableList.of(
+ allOf(matchAssumeNoSideEffectsMessage(), matchMessageForAllProblematicMethods()));
+
+ try {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(TestClass.class, B.class)
+ .addKeepMainRule(TestClass.class)
+ .applyIf(dontWarnObject, tb -> tb.addKeepRules("-dontwarn java.lang.Object"))
+ .addKeepRules("-assumenosideeffects class " + B.class.getTypeName() + " { <methods>; }")
+ .setMinApi(parameters.getApiLevel())
+ .allowDiagnosticWarningMessages()
+ .compileWithExpectedDiagnostics(
+ diagnostics -> assertErrorsOrWarnings(diagnostics, matchers))
+ .inspect(this::noCallToWait)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Hello, world");
+ assertTrue(dontWarnObject);
+ } catch (CompilationFailedException e) {
+ assertFalse(dontWarnObject);
+ }
}
@Test
public void testR8WaitMethodMatch() throws Exception {
- testForR8(parameters.getBackend())
- .addProgramClasses(TestClass.class, B.class)
- .addKeepMainRule(TestClass.class)
- .addKeepRules("-assumenosideeffects class " + B.class.getTypeName() + " { *** w*(...); }")
- .setMinApi(parameters.getApiLevel())
- .allowDiagnosticWarningMessages()
- .compileWithExpectedDiagnostics(
- diagnostics -> {
- diagnostics.assertOnlyWarnings();
- diagnostics.assertWarningsMatch(matchAssumeNoSideEffectsWarningMessage());
- diagnostics.assertWarningsMatch(matchWarningMessageForWaitMethods());
- })
- .inspect(this::noCallToWait)
- .run(parameters.getRuntime(), TestClass.class)
- .assertSuccessWithOutputLines("Hello, world");
+ List<Matcher<Diagnostic>> matchers =
+ ImmutableList.of(allOf(matchAssumeNoSideEffectsMessage(), matchMessageForWaitMethods()));
+
+ try {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(TestClass.class, B.class)
+ .addKeepMainRule(TestClass.class)
+ .applyIf(dontWarnObject, tb -> tb.addKeepRules("-dontwarn java.lang.Object"))
+ .addKeepRules("-assumenosideeffects class " + B.class.getTypeName() + " { *** w*(...); }")
+ .setMinApi(parameters.getApiLevel())
+ .allowDiagnosticWarningMessages()
+ .compileWithExpectedDiagnostics(
+ diagnostics -> assertErrorsOrWarnings(diagnostics, matchers))
+ .inspect(this::noCallToWait)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Hello, world");
+ assertTrue(dontWarnObject);
+ } catch (CompilationFailedException e) {
+ assertFalse(dontWarnObject);
+ }
}
@Test
public void testR8WaitSpecificMethodMatch() throws Exception {
+ assumeTrue("No need to run this with -dontwarn java.lang.Object", !dontWarnObject);
+
testForR8(parameters.getBackend())
.addProgramClasses(TestClass.class, B.class)
.addKeepMainRule(TestClass.class)
@@ -254,7 +310,8 @@
@Test
public void testProguardNotRemovingWait() throws Exception {
- Assume.assumeTrue(parameters.isCfRuntime());
+ assumeTrue("No need to run this with -dontwarn java.lang.Object", !dontWarnObject);
+ assumeTrue(parameters.isCfRuntime());
testForProguard()
.addProgramClasses(TestClass.class, B.class)
@@ -269,7 +326,8 @@
@Test
public void testProguardRemovingWait() throws Exception {
- Assume.assumeTrue(parameters.isCfRuntime());
+ assumeTrue("No need to run this with -dontwarn java.lang.Object", !dontWarnObject);
+ assumeTrue(parameters.isCfRuntime());
testForProguard()
.addProgramClasses(TestClass.class, B.class)
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
index 47a6182..81b988e 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.utils.codeinspector;
import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import java.util.List;
import java.util.function.Consumer;
@@ -93,6 +94,11 @@
}
@Override
+ public String getOriginalBinaryName() {
+ return null;
+ }
+
+ @Override
public String getFinalName() {
return null;
}
@@ -138,6 +144,11 @@
}
@Override
+ public DexMethod getFinalEnclosingMethod() {
+ throw new Unreachable("Cannot determine EnclosingMethod attribute of an absent class");
+ }
+
+ @Override
public String getOriginalSignatureAttribute() {
return null;
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmTypeAliasSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmTypeAliasSubject.java
index 78ac796..622db02 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmTypeAliasSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmTypeAliasSubject.java
@@ -5,7 +5,9 @@
package com.android.tools.r8.utils.codeinspector;
import com.android.tools.r8.errors.Unreachable;
+import com.google.common.collect.ImmutableList;
import java.util.List;
+import kotlinx.metadata.KmAnnotation;
public class AbsentKmTypeAliasSubject extends KmTypeAliasSubject {
@@ -48,4 +50,9 @@
public KmTypeSubject underlyingType() {
return null;
}
+
+ @Override
+ public List<KmAnnotation> annotations() {
+ return ImmutableList.of();
+ }
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
index 8fed691..97a3b29 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.utils.codeinspector;
+import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.references.MethodReference;
@@ -164,6 +165,8 @@
public abstract String getOriginalDescriptor();
+ public abstract String getOriginalBinaryName();
+
public abstract String getFinalName();
public abstract String getFinalDescriptor();
@@ -178,6 +181,8 @@
public abstract boolean isSynthesizedJavaLambdaClass();
+ public abstract DexMethod getFinalEnclosingMethod();
+
public abstract String getOriginalSignatureAttribute();
public abstract String getFinalSignatureAttribute();
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 b2edae2..2a4c7c5 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
@@ -83,12 +83,12 @@
this(Collections.singletonList(file), null, null);
}
- public CodeInspector(List<Path> files) throws IOException {
+ public CodeInspector(Collection<Path> files) throws IOException {
this(files, null, null);
}
public CodeInspector(
- List<Path> files, String mappingFile, Consumer<InternalOptions> optionsConsumer)
+ Collection<Path> files, String mappingFile, Consumer<InternalOptions> optionsConsumer)
throws IOException {
Path mappingPath = mappingFile != null ? Paths.get(mappingFile) : null;
if (mappingPath != null && Files.exists(mappingPath)) {
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 6923021..2ce3024 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
@@ -263,6 +263,11 @@
}
}
+ @Override
+ public String getOriginalBinaryName() {
+ return DescriptorUtils.getBinaryNameFromDescriptor(getOriginalDescriptor());
+ }
+
public DexType getOriginalDexType(DexItemFactory dexItemFactory) {
return dexItemFactory.createType(getOriginalDescriptor());
}
@@ -308,6 +313,11 @@
}
@Override
+ public DexMethod getFinalEnclosingMethod() {
+ return dexClass.getEnclosingMethodAttribute().getEnclosingMethod();
+ }
+
+ @Override
public String getOriginalSignatureAttribute() {
return codeInspector.getOriginalSignatureAttribute(
dexClass.annotations(), GenericSignatureParser::parseClassSignature);
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmTypeAliasSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmTypeAliasSubject.java
index a07125c..396ef98 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmTypeAliasSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmTypeAliasSubject.java
@@ -9,6 +9,7 @@
import java.util.List;
import java.util.stream.Collectors;
+import kotlinx.metadata.KmAnnotation;
import kotlinx.metadata.KmTypeAlias;
public class FoundKmTypeAliasSubject extends KmTypeAliasSubject {
@@ -62,4 +63,9 @@
public KmTypeSubject underlyingType() {
return new KmTypeSubject(codeInspector, kmTypeAlias.underlyingType);
}
+
+ @Override
+ public List<KmAnnotation> annotations() {
+ return kmTypeAlias.getAnnotations();
+ }
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeAliasSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeAliasSubject.java
index f87d368..8ff3c90 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeAliasSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeAliasSubject.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.utils.codeinspector;
import java.util.List;
+import kotlinx.metadata.KmAnnotation;
public abstract class KmTypeAliasSubject extends Subject {
@@ -17,4 +18,6 @@
public abstract KmTypeSubject expandedType();
public abstract KmTypeSubject underlyingType();
+
+ public abstract List<KmAnnotation> annotations();
}
diff --git a/tools/archive_desugar_jdk_libs.py b/tools/archive_desugar_jdk_libs.py
index 7714362..343a7f2 100755
--- a/tools/archive_desugar_jdk_libs.py
+++ b/tools/archive_desugar_jdk_libs.py
@@ -115,7 +115,7 @@
'Target archive directory %s already exists' % destination)
bazel = os.path.join(utils.BAZEL_TOOL, 'lib', 'bazel', 'bin', 'bazel')
- cmd = [bazel, 'build', '--host_force_python=PY2', 'maven_release']
+ cmd = [bazel, 'build', 'maven_release']
utils.PrintCmd(cmd)
subprocess.check_call(cmd)
cmd = [bazel, 'shutdown']
diff --git a/tools/create_maven_release.py b/tools/create_maven_release.py
index cc36693..42dcc63 100755
--- a/tools/create_maven_release.py
+++ b/tools/create_maven_release.py
@@ -96,11 +96,6 @@
<distribution>repo</distribution>
</license>
</licenses>
- <dependencies>
- <groupId>com.android.tools</groupId>
- <artifactId>desugar_jdk_libs</artifactId>
- <version>1.0.1</version>
- </dependencies>
<developers>
<developer>
<name>The Android Open Source Project</name>
diff --git a/tools/r8_release.py b/tools/r8_release.py
index 7b1ba0b..357b659 100755
--- a/tools/r8_release.py
+++ b/tools/r8_release.py
@@ -390,41 +390,6 @@
return make_release
-def prepare_push_desugar_library(args):
- client_name = 'push-desugar-library'
- # Check if an existing client exists.
- check_no_google3_client(args, client_name)
-
- def push_desugar_library(options):
- print 'Pushing to %s' % GITHUB_DESUGAR_JDK_LIBS
-
- google3_base = subprocess.check_output(
- ['p4', 'g4d', '-f', client_name]).rstrip()
- third_party_desugar_jdk_libs = \
- os.path.join(google3_base, 'third_party', 'java_src', 'desugar_jdk_libs')
- version = archive_desugar_jdk_libs.GetVersion(
- os.path.join(third_party_desugar_jdk_libs, 'oss', 'VERSION.txt'))
- if args.push_desugar_library != version:
- print ("Failed, version of desugared library is %s, but version %s was expected." %
- (version, args.push_desugar_library))
- sys.exit(1)
- with utils.ChangedWorkingDirectory(google3_base):
- cmd = [
- 'copybara',
- os.path.join(
- 'third_party',
- 'java_src',
- 'desugar_jdk_libs',
- 'copy.bara.sky'),
- 'push-to-github']
- if options.dry_run:
- print "Dry-run, not running '%s'" % ' '.join(cmd)
- else:
- subprocess.check_call(cmd)
-
- return push_desugar_library
-
-
def download_configuration(hash, archive):
print
print 'Downloading %s from GCS' % archive
@@ -662,10 +627,6 @@
group.add_argument('--version',
metavar=('<version>'),
help='The new version of R8 (e.g., 1.4.51) to release to selected channels')
- group.add_argument('--push-desugar-library',
- metavar=('<version>'),
- help='The expected version of '
- + 'com.android.tools:desugar_jdk_libs to push to GitHub')
group.add_argument('--desugar-library',
nargs=2,
metavar=('<version>', '<configuration hash>'),
@@ -751,8 +712,7 @@
if (args.google3
or (args.studio and not args.no_sync)
- or (args.desugar_library and not args.dry_run)
- or (args.push_desugar_library and not args.dry_run)):
+ or (args.desugar_library and not args.dry_run)):
utils.check_prodacces()
if args.google3:
@@ -765,9 +725,6 @@
if args.desugar_library:
targets_to_run.append(prepare_desugar_library(args))
- if args.push_desugar_library:
- targets_to_run.append(prepare_push_desugar_library(args))
-
final_results = []
for target_closure in targets_to_run:
final_results.append(target_closure(args))