Version 1.4.29
Cherry-pick: Fix not setting offset on DexItemBasedConstString replacement
CL: https://r8-review.googlesource.com/c/r8/+/33145
Cherry-pick: Add test for gms core with forced jumbo string rewriting
CL: https://r8-review.googlesource.com/c/r8/+/33228
Cherry-pick: Don't generate compat rules when tracing reflective use
CL: https://r8-review.googlesource.com/c/r8/+/32845
Cherry-pick: Mark fields accessed by reflection as both read and written
CL: https://r8-review.googlesource.com/c/r8/+/33271
Cherry-pick: Print proper reason for kept-by-reflection edges.
CL: https://r8-review.googlesource.com/c/r8/+/33263
Cherry-pick: Ensure that we can merge desugared java8 classes
CL: https://r8-review.googlesource.com/c/r8/+/33281
Cherry-pick: Add support for -libraryjars in ProguardTestBuilder.
CL: https://r8-review.googlesource.com/c/r8/+/33274
Cherry-pick: Throw for unimplemented APIs in ProguardTestBuilder
CL: https://r8-review.googlesource.com/c/r8/+/33262
Cherry-pick: Guard check-cast elimination when in-value is null constant
CL: https://r8-review.googlesource.com/c/r8/+/33300
Cherry-pick: Revert "Revert "Assert that definitionFor is only called on non-null class types.""
CL: https://r8-review.googlesource.com/c/r8/+/33280
Bug: 123152027
Bug: 123242448
Bug: 123215165
Bug: 69590587
Bug: 123164442
Bug: 123269162
Change-Id: I06db0b137ceb3d3410f04680c5bbecc697b8f905
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 5b36379..a7b396e 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
// This field is accessed from release scripts using simple pattern matching.
// Therefore, changing this field could break our release scripts.
- public static final String LABEL = "1.4.28";
+ public static final String LABEL = "1.4.29";
private Version() {
}
diff --git a/src/main/java/com/android/tools/r8/experimental/graphinfo/GraphEdgeInfo.java b/src/main/java/com/android/tools/r8/experimental/graphinfo/GraphEdgeInfo.java
index 0c838ac..e3fa01b 100644
--- a/src/main/java/com/android/tools/r8/experimental/graphinfo/GraphEdgeInfo.java
+++ b/src/main/java/com/android/tools/r8/experimental/graphinfo/GraphEdgeInfo.java
@@ -16,9 +16,11 @@
InvokedFrom,
InvokedFromLambdaCreatedIn,
ReferencedFrom,
+ ReflectiveUseFrom,
ReachableFromLiveType,
ReferencedInAnnotation,
IsLibraryMethod,
+ MethodHandleUseFrom
}
private final EdgeKind kind;
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
index a45064e..e415b0f 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
@@ -119,8 +119,10 @@
// For mapping invoke virtual instruction to target methods.
public Set<DexEncodedMethod> lookupVirtualTargets(DexMethod method) {
- Set<DexEncodedMethod> result = new HashSet<>();
- // First add the target for receiver type method.type.
+ if (method.holder.isArrayType()) {
+ assert method.name == dexItemFactory.cloneMethodName;
+ return null;
+ }
DexClass root = definitionFor(method.holder);
if (root == null) {
// type specified in method does not have a materialized class.
@@ -131,6 +133,8 @@
// This will fail at runtime.
return null;
}
+ // First add the target for receiver type method.type.
+ Set<DexEncodedMethod> result = new HashSet<>();
topTargets.forEachTarget(result::add);
// Add all matching targets from the subclass hierarchy.
for (DexType type : subtypes(method.holder)) {
diff --git a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
index 09094aa..1e125d9 100644
--- a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
@@ -45,6 +45,7 @@
@Override
public DexClass definitionFor(DexType type) {
+ assert type.isClassType() : "Cannot lookup definition for type: " + type;
DexClass result = programClasses.get(type);
if (result == null) {
result = libraryClasses.get(type);
diff --git a/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java b/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
index 6dea825..b236428 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
@@ -42,9 +42,7 @@
@Override
public DexClass definitionFor(DexType type) {
- if (type == null) {
- return null;
- }
+ assert type.isClassType() : "Cannot lookup definition for type: " + type;
DexClass clazz = programClasses.get(type);
if (clazz == null && classpathClasses != null) {
clazz = classpathClasses.get(type);
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/TypeChecker.java b/src/main/java/com/android/tools/r8/ir/analysis/TypeChecker.java
index be7709c..456d45f 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/TypeChecker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/TypeChecker.java
@@ -78,7 +78,7 @@
return true;
}
- if (valueType.isReference()) {
+ if (fieldType.isClassType() && valueType.isReference()) {
// Interface types are treated like Object according to the JVM spec.
// https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.10.1.2-100
DexClass clazz = appInfo.definitionFor(instruction.getField().type);
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
index 0b8ef55..c52bcf9 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
@@ -90,6 +90,9 @@
// A const-class instruction can be dead code only if the resulting program is known to contain
// the class mentioned.
DexType baseType = clazz.toBaseType(appInfo.dexItemFactory);
+ if (baseType.isPrimitiveType()) {
+ return true;
+ }
DexClass holder = appInfo.definitionFor(baseType);
return holder != null && holder.isProgramClass();
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/Java8MethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/Java8MethodRewriter.java
index c0a7569..9e2e9ab 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/Java8MethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/Java8MethodRewriter.java
@@ -84,6 +84,10 @@
return null;
}
+ public static boolean hasJava8MethodRewritePrefix(DexType clazz) {
+ return clazz.descriptor.toString().startsWith(UTILITY_CLASS_DESCRIPTOR_PREFIX);
+ }
+
public void synthesizeUtilityClass(Builder<?> builder, InternalOptions options) {
if (holders.isEmpty()) {
return;
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 6a7e6ba..e3257d5 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
@@ -2096,6 +2096,16 @@
return false;
}
+ // If the in-value is `null` and the cast-type is a float-array type, then trivial check-cast
+ // elimination may lead to verification errors. See b/123269162.
+ if (options.canHaveArtCheckCastVerifierBug()) {
+ if (inValue.getTypeLattice().isNullType()
+ && castType.isArrayType()
+ && castType.toBaseType(dexItemFactory).isFloatType()) {
+ return false;
+ }
+ }
+
// We might see chains of casts on subtypes. It suffices to cast to the lowest subtype,
// as that will fail if a cast on a supertype would have failed.
Predicate<Instruction> isCheckcastToSubtype =
@@ -2140,6 +2150,9 @@
private boolean isTypeInaccessibleInCurrentContext(DexType type, DexEncodedMethod context) {
DexType baseType = type.toBaseType(appInfo.dexItemFactory);
+ if (baseType.isPrimitiveType()) {
+ return false;
+ }
DexClass clazz = definitionFor(baseType);
if (clazz == null) {
// Conservatively say yes.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index 7375303..cdf37c4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -113,6 +113,9 @@
eligibleClass =
root.isNewInstance() ? root.asNewInstance().clazz : root.asStaticGet().getField().type;
+ if (!eligibleClass.isClassType()) {
+ return false;
+ }
eligibleClassDefinition = appInfo.definitionFor(eligibleClass);
if (eligibleClassDefinition == null && lambdaRewriter != null) {
// Check if the class is synthesized for a desugared lambda
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java b/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
index 61695f1..e670925 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
@@ -170,7 +170,9 @@
appInfo.definitionFor(cnst.getItem().asDexType()),
cnst.getClassNameComputationInfo()))
: lens.lookupName(cnst.getItem(), appInfo.dexItemFactory);
- instructions[i] = new ConstString(cnst.AA, replacement);
+ ConstString constString = new ConstString(cnst.AA, replacement);
+ constString.setOffset(instruction.getOffset());
+ instructions[i] = constString;
}
}
} else {
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 3376854..86a4a8d 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -604,7 +604,7 @@
if (methodHandle.isMethodHandle() && use != MethodHandleUse.ARGUMENT_TO_LAMBDA_METAFACTORY) {
DexClass holder = appInfo.definitionFor(methodHandle.asMethod().holder);
if (holder != null) {
- markClassAsInstantiatedWithMethodHandleRule(holder);
+ markInstantiated(holder.type, KeepReason.methodHandleReferencedIn(currentMethod));
}
}
}
@@ -1189,8 +1189,10 @@
private void markVirtualMethodAsLive(DexEncodedMethod method, KeepReason reason) {
assert method != null;
- // Only explicit keep rules should make abstract methods live.
- assert !method.accessFlags.isAbstract() || reason.isDueToKeepRule();
+ // Only explicit keep rules or reflective use should make abstract methods live.
+ assert !method.accessFlags.isAbstract()
+ || reason.isDueToKeepRule()
+ || reason.isDueToReflectiveUse();
if (!liveMethods.contains(method)) {
if (Log.ENABLED) {
Log.verbose(getClass(), "Adding virtual method `%s` to live set.", method.method);
@@ -1662,16 +1664,8 @@
collectReachedFields(staticFields, this::tryLookupStaticField)));
}
- private void markClassAsInstantiatedWithMethodHandleRule(DexClass clazz) {
- ProguardKeepRule rule =
- ProguardConfigurationUtils.buildMethodHandleKeepRule(clazz);
- proguardCompatibilityWorkList.add(
- Action.markInstantiated(clazz, KeepReason.dueToProguardCompatibilityKeepRule(rule)));
- }
-
private void markClassAsInstantiatedWithCompatRule(DexClass clazz) {
- ProguardKeepRule rule =
- ProguardConfigurationUtils.buildDefaultInitializerKeepRule(clazz);
+ ProguardKeepRule rule = ProguardConfigurationUtils.buildDefaultInitializerKeepRule(clazz);
proguardCompatibilityWorkList.add(
Action.markInstantiated(clazz, KeepReason.dueToProguardCompatibilityKeepRule(rule)));
if (clazz.hasDefaultInitializer()) {
@@ -1681,19 +1675,6 @@
}
}
- private void markFieldAsKeptWithCompatRule(DexEncodedField field, boolean keepClass) {
- DexClass holderClass = appInfo.definitionFor(field.field.getHolder());
- ProguardKeepRule rule =
- ProguardConfigurationUtils.buildFieldKeepRule(holderClass, field, keepClass);
- if (keepClass) {
- proguardCompatibilityWorkList.add(
- Action.markInstantiated(
- holderClass, KeepReason.dueToProguardCompatibilityKeepRule(rule)));
- }
- proguardCompatibilityWorkList.add(
- Action.markFieldKept(field, KeepReason.dueToProguardCompatibilityKeepRule(rule)));
- }
-
private void markMethodAsKeptWithCompatRule(DexEncodedMethod method) {
DexClass holderClass = appInfo.definitionFor(method.method.getHolder());
ProguardKeepRule rule =
@@ -1733,7 +1714,11 @@
if (identifierItem.isDexType()) {
DexClass clazz = appInfo.definitionFor(identifierItem.asDexType());
if (clazz != null) {
- markClassAsInstantiatedWithCompatRule(clazz);
+ markInstantiated(clazz.type, KeepReason.reflectiveUseIn(method));
+ if (clazz.hasDefaultInitializer()) {
+ markDirectStaticOrConstructorMethodAsLive(
+ clazz.getDefaultInitializer(), KeepReason.reflectiveUseIn(method));
+ }
}
} else if (identifierItem.isDexField()) {
DexEncodedField encodedField = appInfo.definitionFor(identifierItem.asDexField());
@@ -1746,13 +1731,30 @@
boolean keepClass =
!encodedField.accessFlags.isStatic()
&& appInfo.dexItemFactory.atomicFieldUpdaterMethods.isFieldUpdater(invokedMethod);
- markFieldAsKeptWithCompatRule(encodedField, keepClass);
+ if (keepClass) {
+ DexClass holderClass = appInfo.definitionFor(encodedField.field.getHolder());
+ markInstantiated(holderClass.type, KeepReason.reflectiveUseIn(method));
+ }
+ markFieldAsKept(encodedField, KeepReason.reflectiveUseIn(method));
+ // Fields accessed by reflection is marked as both read and written.
+ if (encodedField.isStatic()) {
+ registerItemWithTargetAndContext(staticFieldsRead, encodedField.field, method);
+ registerItemWithTargetAndContext(staticFieldsWritten, encodedField.field, method);
+ } else {
+ registerItemWithTargetAndContext(instanceFieldsRead, encodedField.field, method);
+ registerItemWithTargetAndContext(instanceFieldsWritten, encodedField.field, method);
+ }
}
} else {
assert identifierItem.isDexMethod();
DexEncodedMethod encodedMethod = appInfo.definitionFor(identifierItem.asDexMethod());
if (encodedMethod != null) {
- markMethodAsKeptWithCompatRule(encodedMethod);
+ if (encodedMethod.accessFlags.isStatic() || encodedMethod.accessFlags.isConstructor()) {
+ markDirectStaticOrConstructorMethodAsLive(
+ encodedMethod, KeepReason.reflectiveUseIn(method));
+ } else {
+ markVirtualMethodAsLive(encodedMethod, KeepReason.reflectiveUseIn(method));
+ }
}
}
}
@@ -2420,6 +2422,10 @@
// https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-6.html#jvms-6.5.invokevirtual
assert method != null;
assert refinedReceiverType.isSubtypeOf(method.holder, this);
+ if (method.holder.isArrayType()) {
+ assert method.name == dexItemFactory.cloneMethodName;
+ return null;
+ }
DexClass holder = definitionFor(method.holder);
if (holder == null || holder.isLibraryClass() || holder.isInterface()) {
return null;
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepReason.java b/src/main/java/com/android/tools/r8/shaking/KeepReason.java
index 16d78a7..a797742 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepReason.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepReason.java
@@ -61,6 +61,10 @@
return false;
}
+ public boolean isDueToReflectiveUse() {
+ return false;
+ }
+
public boolean isDueToProguardCompatibility() {
return false;
}
@@ -73,6 +77,14 @@
return new TargetedBySuper(from);
}
+ public static KeepReason reflectiveUseIn(DexEncodedMethod method) {
+ return new ReflectiveUseFrom(method);
+ }
+
+ public static KeepReason methodHandleReferencedIn(DexEncodedMethod method) {
+ return new MethodHandleReferencedFrom(method);
+ }
+
private static class DueToKeepRule extends KeepReason {
final ProguardKeepRule keepRule;
@@ -289,4 +301,43 @@
return enqueuer.getAnnotationGraphNode(holder);
}
}
+
+ private static class ReflectiveUseFrom extends BasedOnOtherMethod {
+
+ private ReflectiveUseFrom(DexEncodedMethod method) {
+ super(method);
+ }
+
+ @Override
+ public boolean isDueToReflectiveUse() {
+ return true;
+ }
+
+ @Override
+ public EdgeKind edgeKind() {
+ return EdgeKind.ReflectiveUseFrom;
+ }
+
+ @Override
+ String getKind() {
+ return "reflective use in";
+ }
+ }
+
+ private static class MethodHandleReferencedFrom extends BasedOnOtherMethod {
+
+ private MethodHandleReferencedFrom(DexEncodedMethod method) {
+ super(method);
+ }
+
+ @Override
+ public EdgeKind edgeKind() {
+ return EdgeKind.MethodHandleUseFrom;
+ }
+
+ @Override
+ String getKind() {
+ return "method handle referenced from";
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java b/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java
index 543dcc4..71b3d53 100644
--- a/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java
+++ b/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java
@@ -19,7 +19,6 @@
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.UseRegistry;
-import com.google.common.collect.ImmutableSet;
import java.util.Set;
import java.util.function.Consumer;
@@ -61,35 +60,23 @@
boolean value = false;
}
- public static boolean hasReferencesOutside(
- AppInfoWithSubtyping appInfo, DexProgramClass clazz, Set<DexType> types) {
- BooleanBox result = new BooleanBox();
-
- new MainDexDirectReferenceTracer(appInfo, type -> {
- if (!types.contains(type)) {
- DexClass cls = appInfo.definitionFor(type);
- if (cls != null && !cls.isLibraryClass()) {
- result.value = true;
- }
- }
- }).run(ImmutableSet.of(clazz.type));
-
- return result.value;
- }
-
public static boolean hasReferencesOutsideFromCode(
AppInfoWithSubtyping appInfo, DexEncodedMethod method, Set<DexType> classes) {
BooleanBox result = new BooleanBox();
- new MainDexDirectReferenceTracer(appInfo, type -> {
- if (!classes.contains(type)) {
- DexClass cls = appInfo.definitionFor(type);
- if (cls != null && !cls.isLibraryClass()) {
- result.value = true;
- }
- }
- }).runOnCode(method);
+ new MainDexDirectReferenceTracer(
+ appInfo,
+ type -> {
+ DexType baseType = type.toBaseType(appInfo.dexItemFactory);
+ if (baseType.isClassType() && !classes.contains(baseType)) {
+ DexClass cls = appInfo.definitionFor(baseType);
+ if (cls != null && !cls.isLibraryClass()) {
+ result.value = true;
+ }
+ }
+ })
+ .runOnCode(method);
return result.value;
}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationUtils.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationUtils.java
index 163c60b..f9846b8 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationUtils.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationUtils.java
@@ -5,13 +5,8 @@
package com.android.tools.r8.shaking;
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;
import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexReference;
-import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.shaking.ProguardConfigurationParser.IdentifierPatternWithWildcards;
import com.android.tools.r8.utils.AndroidApiLevel;
@@ -31,19 +26,6 @@
}
};
- public static ProguardKeepRule buildMethodHandleKeepRule(DexClass clazz) {
- ProguardKeepRule.Builder builder = ProguardKeepRule.builder();
- builder.setOrigin(proguardCompatOrigin);
- builder.setType(ProguardKeepRuleType.KEEP);
- builder.getModifiersBuilder().setAllowsObfuscation(true);
- builder.getModifiersBuilder().setAllowsOptimization(true);
- builder.setClassType(
- clazz.isInterface() ? ProguardClassType.INTERFACE : ProguardClassType.CLASS);
- builder.setClassNames(
- ProguardClassNameList.singletonList(ProguardTypeMatcher.create(clazz.type)));
- return builder.build();
- }
-
public static ProguardKeepRule buildDefaultInitializerKeepRule(DexClass clazz) {
ProguardKeepRule.Builder builder = ProguardKeepRule.builder();
builder.setOrigin(proguardCompatOrigin);
@@ -64,36 +46,6 @@
return builder.build();
}
- public static ProguardKeepRule buildFieldKeepRule(
- DexClass clazz, DexEncodedField field, boolean keepClass) {
- assert clazz.type == field.field.getHolder();
- ProguardKeepRule.Builder builder = ProguardKeepRule.builder();
- builder.setOrigin(proguardCompatOrigin);
- if (keepClass) {
- builder.setType(ProguardKeepRuleType.KEEP);
- } else {
- builder.setType(ProguardKeepRuleType.KEEP_CLASS_MEMBERS);
- }
- builder.getModifiersBuilder().setAllowsObfuscation(true);
- builder.getModifiersBuilder().setAllowsOptimization(true);
- builder.getClassAccessFlags().setVisibility(clazz.accessFlags);
- if (clazz.isInterface()) {
- builder.setClassType(ProguardClassType.INTERFACE);
- } else {
- builder.setClassType(ProguardClassType.CLASS);
- }
- builder.setClassNames(
- ProguardClassNameList.singletonList(ProguardTypeMatcher.create(clazz.type)));
- ProguardMemberRule.Builder memberRuleBuilder = ProguardMemberRule.builder();
- memberRuleBuilder.setRuleType(ProguardMemberType.FIELD);
- memberRuleBuilder.getAccessFlags().setFlags(field.accessFlags);
- memberRuleBuilder.setName(
- IdentifierPatternWithWildcards.withoutWildcards(field.field.name.toString()));
- memberRuleBuilder.setTypeMatcher(ProguardTypeMatcher.create(field.field.type));
- builder.getMemberRules().add(memberRuleBuilder.build());
- return builder.build();
- }
-
public static ProguardKeepRule buildMethodKeepRule(DexClass clazz, DexEncodedMethod method) {
// TODO(b/122295241): These generated rules should be linked into the graph, eg, the method
// using identified reflection should be the source keeping the target alive.
@@ -125,41 +77,6 @@
return builder.build();
}
- public static ProguardIdentifierNameStringRule buildIdentifierNameStringRule(DexReference item) {
- assert item.isDexField() || item.isDexMethod();
- ProguardIdentifierNameStringRule.Builder builder = ProguardIdentifierNameStringRule.builder();
- ProguardMemberRule.Builder memberRuleBuilder = ProguardMemberRule.builder();
- DexType holderType;
- if (item.isDexField()) {
- DexField field = item.asDexField();
- holderType = field.getHolder();
- memberRuleBuilder.setRuleType(ProguardMemberType.FIELD);
- memberRuleBuilder.setName(
- IdentifierPatternWithWildcards.withoutWildcards(field.name.toString()));
- memberRuleBuilder.setTypeMatcher(ProguardTypeMatcher.create(field.type));
- } else {
- DexMethod method = item.asDexMethod();
- holderType = method.getHolder();
- memberRuleBuilder.setRuleType(ProguardMemberType.METHOD);
- memberRuleBuilder.setName(
- IdentifierPatternWithWildcards.withoutWildcards(method.name.toString()));
- memberRuleBuilder.setTypeMatcher(ProguardTypeMatcher.create(method.proto.returnType));
- List<ProguardTypeMatcher> arguments = Arrays.stream(method.proto.parameters.values)
- .map(ProguardTypeMatcher::create)
- .collect(Collectors.toList());
- memberRuleBuilder.setArguments(arguments);
- }
- if (holderType.isInterface()) {
- builder.setClassType(ProguardClassType.INTERFACE);
- } else {
- builder.setClassType(ProguardClassType.CLASS);
- }
- builder.setClassNames(
- ProguardClassNameList.singletonList(ProguardTypeMatcher.create(holderType)));
- builder.getMemberRules().add(memberRuleBuilder.build());
- return builder.build();
- }
-
public static ProguardAssumeValuesRule buildAssumeValuesForApiLevel(
DexItemFactory factory, AndroidApiLevel apiLevel) {
Origin synthesizedFromApiLevel =
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 46d3a68..a0da9e7 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -843,6 +843,9 @@
}
private void includeDescriptor(DexDefinition item, DexType type, ProguardKeepRule context) {
+ if (type.isVoidType()) {
+ return;
+ }
if (type.isArrayType()) {
type = type.toBaseType(application.dexItemFactory);
}
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index fbe8968..7edd536 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -336,15 +336,16 @@
}
private void markTypeAsPinned(DexType type, AbortReason reason) {
- if (appInfo.isPinned(type)) {
+ DexType baseType = type.toBaseType(appInfo.dexItemFactory);
+ if (!baseType.isClassType() || appInfo.isPinned(baseType)) {
// We check for the case where the type is pinned according to appInfo.isPinned,
// so we only need to add it here if it is not the case.
return;
}
- DexClass clazz = appInfo.definitionFor(type);
+ DexClass clazz = appInfo.definitionFor(baseType);
if (clazz != null && clazz.isProgramClass()) {
- boolean changed = pinnedTypes.add(type);
+ boolean changed = pinnedTypes.add(baseType);
if (Log.ENABLED) {
if (changed && isMergeCandidate(clazz.asProgramClass(), ImmutableSet.of())) {
@@ -743,10 +744,6 @@
return false;
}
- private boolean hasReferencesOutside(DexProgramClass clazz, Set<DexType> types) {
- return MainDexDirectReferenceTracer.hasReferencesOutside(appInfo, clazz, types);
- }
-
private void mergeClassIfPossible(DexProgramClass clazz) {
if (!mergeCandidates.contains(clazz)) {
return;
diff --git a/src/main/java/com/android/tools/r8/shaking/WhyAreYouKeepingConsumer.java b/src/main/java/com/android/tools/r8/shaking/WhyAreYouKeepingConsumer.java
index 0572e88..abd22bf 100644
--- a/src/main/java/com/android/tools/r8/shaking/WhyAreYouKeepingConsumer.java
+++ b/src/main/java/com/android/tools/r8/shaking/WhyAreYouKeepingConsumer.java
@@ -211,12 +211,16 @@
return "invoked from lambda created in";
case ReferencedFrom:
return "referenced from";
+ case ReflectiveUseFrom:
+ return "reflected from";
case ReachableFromLiveType:
return "reachable from";
case ReferencedInAnnotation:
return "referenced in annotation";
case IsLibraryMethod:
return "defined in library";
+ case MethodHandleUseFrom:
+ return "referenced by method handle";
default:
throw new Unreachable("Unexpected edge kind: " + info.edgeKind());
}
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 4921c79..045aa39 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -890,4 +890,12 @@
public boolean canHaveExceptionTypeBug() {
return minApiLevel < AndroidApiLevel.Q.getLevel();
}
+
+ // Art 4.0.4 fails with a verification error when a null-literal is being passed directly to an
+ // aget instruction. We therefore need to be careful when performing trivial check-cast
+ // elimination of check-cast instructions where the value being cast is the constant null.
+ // See b/123269162.
+ public boolean canHaveArtCheckCastVerifierBug() {
+ return minApiLevel < AndroidApiLevel.J.getLevel();
+ }
}
diff --git a/src/main/java/com/android/tools/r8/utils/ProgramClassCollection.java b/src/main/java/com/android/tools/r8/utils/ProgramClassCollection.java
index 3b2b878..1cf61c0 100644
--- a/src/main/java/com/android/tools/r8/utils/ProgramClassCollection.java
+++ b/src/main/java/com/android/tools/r8/utils/ProgramClassCollection.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
+import com.android.tools.r8.ir.desugar.Java8MethodRewriter;
import com.android.tools.r8.ir.desugar.LambdaRewriter;
import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
import java.util.List;
@@ -68,6 +69,7 @@
private static boolean assumeClassesAreEqual(DexProgramClass a) {
return LambdaRewriter.hasLambdaClassPrefix(a.type)
+ || Java8MethodRewriter.hasJava8MethodRewritePrefix(a.type)
|| InterfaceMethodRewriter.hasDispatchClassSuffix(a.type)
|| a.type.descriptor.toString().equals(TwrCloseResourceRewriter.UTILITY_CLASS_DESCRIPTOR);
}
diff --git a/src/test/examples/classmerging/PinnedArrayParameterTypesTest.java b/src/test/examples/classmerging/PinnedArrayParameterTypesTest.java
new file mode 100644
index 0000000..4a8b5be
--- /dev/null
+++ b/src/test/examples/classmerging/PinnedArrayParameterTypesTest.java
@@ -0,0 +1,47 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package classmerging;
+
+import java.lang.reflect.Method;
+
+public class PinnedArrayParameterTypesTest {
+
+ public static void main(String[] args) throws Exception {
+ for (Method method : TestClass.class.getMethods()) {
+ if (method.getName().equals("method")) {
+ Class<?> parameterType = method.getParameterTypes()[0];
+
+ // Should print classmerging.PinnedArrayParameterTypesTest$Interface when
+ // -keepparameternames is used.
+ System.out.println(parameterType.getName());
+
+ method.invoke(null, new Object[]{ new InterfaceImpl[]{ new InterfaceImpl() } });
+ break;
+ }
+ }
+ }
+
+ public interface Interface {
+
+ void foo();
+ }
+
+ public static class InterfaceImpl implements Interface {
+
+ @Override
+ public void foo() {
+ System.out.println("In InterfaceImpl.foo()");
+ }
+ }
+
+ public static class TestClass {
+
+ // This method has been kept explicitly by a keep rule. Therefore, since -keepparameternames is
+ // used, Interface must not be merged into InterfaceImpl.
+ public static void method(Interface[] obj) {
+ obj[0].foo();
+ }
+ }
+}
diff --git a/src/test/examples/classmerging/keep-rules.txt b/src/test/examples/classmerging/keep-rules.txt
index e3e0006..c75058d 100644
--- a/src/test/examples/classmerging/keep-rules.txt
+++ b/src/test/examples/classmerging/keep-rules.txt
@@ -43,6 +43,12 @@
-keep public class classmerging.PinnedParameterTypesTest$TestClass {
public static void method(...);
}
+-keep public class classmerging.PinnedArrayParameterTypesTest {
+ public static void main(...);
+}
+-keep public class classmerging.PinnedArrayParameterTypesTest$TestClass {
+ public static void method(...);
+}
-keep public class classmerging.ProguardFieldMapTest {
public static void main(...);
}
diff --git a/src/test/java/com/android/tools/r8/D8TestBuilder.java b/src/test/java/com/android/tools/r8/D8TestBuilder.java
index 88d077b..37e4dec 100644
--- a/src/test/java/com/android/tools/r8/D8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/D8TestBuilder.java
@@ -3,13 +3,18 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8;
+import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
import com.android.tools.r8.D8Command.Builder;
import com.android.tools.r8.TestBase.Backend;
import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.InternalOptions;
+import java.io.IOException;
import java.nio.file.Path;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.List;
import java.util.function.Consumer;
import java.util.function.Supplier;
@@ -17,6 +22,9 @@
extends TestCompilerBuilder<
D8Command, Builder, D8TestCompileResult, D8TestRunResult, D8TestBuilder> {
+ // Consider an in-order collection of both class and files on the classpath.
+ private List<Class<?>> classpathClasses = new ArrayList<>();
+
private D8TestBuilder(TestState state, Builder builder) {
super(state, builder, Backend.DEX);
}
@@ -34,6 +42,29 @@
D8TestCompileResult internalCompile(
Builder builder, Consumer<InternalOptions> optionsConsumer, Supplier<AndroidApp> app)
throws CompilationFailedException {
+ if (!classpathClasses.isEmpty()) {
+ Path cp;
+ try {
+ cp = getState().getNewTempFolder().resolve("cp.jar");
+ } catch (IOException e) {
+ throw builder.getReporter().fatalError("Failed to create temp file for classpath archive");
+ }
+ ArchiveConsumer archiveConsumer = new ArchiveConsumer(cp);
+ for (Class<?> classpathClass : classpathClasses) {
+ try {
+ archiveConsumer.accept(
+ ByteDataView.of(ToolHelper.getClassAsBytes(classpathClass)),
+ DescriptorUtils.javaTypeToDescriptor(classpathClass.getTypeName()),
+ builder.getReporter());
+ } catch (IOException e) {
+ builder
+ .getReporter()
+ .error("Failed to read bytes for classpath class: " + classpathClass.getTypeName());
+ }
+ }
+ archiveConsumer.finished(builder.getReporter());
+ builder.addClasspathFiles(cp);
+ }
ToolHelper.runD8(builder, optionsConsumer);
return new D8TestCompileResult(getState(), app.get());
}
@@ -43,7 +74,8 @@
}
public D8TestBuilder addClasspathClasses(Collection<Class<?>> classes) {
- return addClasspathFiles(getFilesForClasses(classes));
+ classpathClasses.addAll(classes);
+ return self();
}
public D8TestBuilder addClasspathFiles(Path... files) {
diff --git a/src/test/java/com/android/tools/r8/ProguardTestBuilder.java b/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
index ea482fa..0fd0354 100644
--- a/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
@@ -6,9 +6,11 @@
import com.android.tools.r8.R8Command.Builder;
import com.android.tools.r8.TestBase.Backend;
import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.debug.DebugTestConfig;
import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.FileUtils;
@@ -99,6 +101,9 @@
// Ordered list of injar entries.
private List<Path> injars = new ArrayList<>();
+ // Ordered list of libraryjar entries.
+ private List<Path> libraryjars = new ArrayList<>();
+
// Proguard configuration file lines.
private List<String> config = new ArrayList<>();
@@ -136,10 +141,16 @@
command.add("-injars");
command.add(injar.toString());
}
- command.add("-libraryjars");
- // TODO(sgjesse): Add support for running with Android Jar.
- // command.add(ToolHelper.getAndroidJar(AndroidApiLevel.P).toString());
- command.add(ToolHelper.getJava8RuntimeJar().toString());
+ for (Path libraryjar : libraryjars) {
+ command.add("-libraryjars");
+ command.add(libraryjar.toString());
+ }
+ if (libraryjars.isEmpty()) {
+ command.add("-libraryjars");
+ // TODO(sgjesse): Add support for running with Android Jar.
+ // command.add(ToolHelper.getAndroidJar(AndroidApiLevel.P).toString());
+ command.add(ToolHelper.getJava8RuntimeJar().toString());
+ }
command.add("-include");
command.add(configFile.toString());
for (Path proguardConfigFile : proguardConfigFiles) {
@@ -196,7 +207,7 @@
injars.add(file);
} else {
throw new Unimplemented(
- "No support for adding paths directly (we need to compute the descriptor)");
+ "No support for adding class files directly (we need to compute the descriptor)");
}
}
return self();
@@ -205,7 +216,7 @@
@Override
public ProguardTestBuilder addProgramClassFileData(Collection<byte[]> classes) {
throw new Unimplemented(
- "No support for adding classfile data directly (we need to compute the descriptor)");
+ "No support for adding class files directly (we need to compute the descriptor)");
}
@Override
@@ -219,4 +230,56 @@
config.addAll(rules);
return self();
}
+ @Override
+ public ProguardTestBuilder addLibraryFiles(Collection<Path> files) {
+ for (Path file : files) {
+ if (FileUtils.isJarFile(file)) {
+ libraryjars.add(file);
+ } else {
+ throw new Unimplemented(
+ "No support for adding class files directly (we need to compute the descriptor)");
+ }
+ }
+ return self();
+ }
+
+ @Override
+ public ProguardTestBuilder setProgramConsumer(ProgramConsumer programConsumer) {
+ throw new Unimplemented("No support for program consumer");
+ }
+
+ @Override
+ public ProguardTestBuilder setMinApi(AndroidApiLevel minApiLevel) {
+ throw new Unimplemented("No support for setting min api");
+ }
+
+ @Override
+ public ProguardTestBuilder addMainDexListFiles(Collection<Path> files) {
+ throw new Unimplemented("No support for adding main dex list files");
+ }
+
+ @Override
+ public ProguardTestBuilder setMainDexListConsumer(StringConsumer consumer) {
+ throw new Unimplemented("No support for main dex list consumer");
+ }
+
+ @Override
+ public ProguardTestBuilder setMode(CompilationMode mode) {
+ throw new Unimplemented("No support for setting compilation mode");
+ }
+
+ @Override
+ public ProguardTestBuilder noDesugaring() {
+ throw new Unimplemented("No support for disabling desugaring");
+ }
+
+ @Override
+ public DebugTestConfig debugConfig() {
+ throw new Unimplemented("No support for debug config");
+ }
+
+ @Override
+ public ProguardTestBuilder addOptionsModification(Consumer<InternalOptions> optionsConsumer) {
+ throw new Unimplemented("No support for changing internal options");
+ }
}
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index bb5677f..daf5568 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -182,7 +182,11 @@
}
public R8TestBuilder enableGraphInspector() {
- CollectingGraphConsumer consumer = new CollectingGraphConsumer(null);
+ return enableGraphInspector(null);
+ }
+
+ public R8TestBuilder enableGraphInspector(GraphConsumer subConsumer) {
+ CollectingGraphConsumer consumer = new CollectingGraphConsumer(subConsumer);
setKeptGraphConsumer(consumer);
graphConsumer = consumer;
return self();
diff --git a/src/test/java/com/android/tools/r8/R8TestRunResult.java b/src/test/java/com/android/tools/r8/R8TestRunResult.java
index 42870fa..55730eb 100644
--- a/src/test/java/com/android/tools/r8/R8TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/R8TestRunResult.java
@@ -12,6 +12,7 @@
import com.android.tools.r8.utils.graphinspector.GraphInspector;
import java.io.IOException;
import java.util.concurrent.ExecutionException;
+import java.util.function.Consumer;
public class R8TestRunResult extends TestRunResult<R8TestRunResult> {
@@ -50,6 +51,13 @@
return graphInspector.get();
}
+ public R8TestRunResult inspectGraph(Consumer<GraphInspector> consumer)
+ throws IOException, ExecutionException {
+ consumer.accept(graphInspector());
+ return self();
+ }
+
+
public String proguardMap() {
return proguardMap;
}
diff --git a/src/test/java/com/android/tools/r8/RegressionForPrimitiveDefinitionForLookup.java b/src/test/java/com/android/tools/r8/RegressionForPrimitiveDefinitionForLookup.java
new file mode 100644
index 0000000..b43bb8b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/RegressionForPrimitiveDefinitionForLookup.java
@@ -0,0 +1,43 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+
+class Tester {
+ public int foo() {
+ float[][][] fs = new float[1][2][3];
+ return fs.length;
+ }
+
+ public static void main(String[] args) {
+ System.out.println(new Tester().foo());
+ }
+}
+
+// The DirectoryClasspathProvider asserts lookups are reference types which witnessed the issue.
+public class RegressionForPrimitiveDefinitionForLookup extends TestBase {
+
+ public final Class<Tester> CLASS = Tester.class;
+ public String EXPECTED = StringUtils.lines("1");
+
+ @Test
+ public void testWithArchiveClasspath() throws Exception {
+ testForD8()
+ .addClasspathClasses(CLASS)
+ .addProgramClasses(CLASS)
+ .run(CLASS)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testWithDirectoryClasspath() throws Exception {
+ testForD8()
+ .addClasspathFiles(ToolHelper.getClassPathForTests())
+ .addProgramClasses(CLASS)
+ .run(CLASS)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
index c4151d1..1836a29 100644
--- a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
@@ -463,6 +463,29 @@
}
@Test
+ public void testPinnedArrayParameterTypes() throws Throwable {
+ String main = "classmerging.PinnedArrayParameterTypesTest";
+ Path[] programFiles =
+ new Path[] {
+ CF_DIR.resolve("PinnedArrayParameterTypesTest.class"),
+ CF_DIR.resolve("PinnedArrayParameterTypesTest$Interface.class"),
+ CF_DIR.resolve("PinnedArrayParameterTypesTest$InterfaceImpl.class"),
+ CF_DIR.resolve("PinnedArrayParameterTypesTest$TestClass.class")
+ };
+ Set<String> preservedClassNames =
+ ImmutableSet.of(
+ "classmerging.PinnedArrayParameterTypesTest",
+ "classmerging.PinnedArrayParameterTypesTest$Interface",
+ "classmerging.PinnedArrayParameterTypesTest$InterfaceImpl",
+ "classmerging.PinnedArrayParameterTypesTest$TestClass");
+ runTest(
+ main,
+ programFiles,
+ preservedClassNames::contains,
+ getProguardConfig(EXAMPLE_KEEP, "-keepparameternames"));
+ }
+
+ @Test
public void testProguardFieldMap() throws Throwable {
String main = "classmerging.ProguardFieldMapTest";
Path[] programFiles =
diff --git a/src/test/java/com/android/tools/r8/desugar/Java8MethodsTest.java b/src/test/java/com/android/tools/r8/desugar/Java8MethodsTest.java
index f94d5a5..053ac5c 100644
--- a/src/test/java/com/android/tools/r8/desugar/Java8MethodsTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/Java8MethodsTest.java
@@ -4,7 +4,10 @@
package com.android.tools.r8.desugar;
+import com.android.tools.r8.D8TestCompileResult;
import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.nio.file.Path;
import org.junit.Before;
import org.junit.Test;
@@ -26,6 +29,62 @@
.assertSuccessWithOutput(expectedOutput);
}
+ @Test
+ public void testD8Merge() throws Exception {
+ String jvmOutput = testForJvm()
+ .addTestClasspath()
+ .run(MergeRun.class).getStdOut();
+
+ // See b/123242448
+ Path zip1 = temp.newFile("first.zip").toPath();
+ Path zip2 = temp.newFile("second.zip").toPath();
+ Path zip3 = temp.newFile("third.zip").toPath();
+
+ D8TestCompileResult result1 =
+ testForD8()
+ .setMinApi(AndroidApiLevel.L)
+ .addProgramClasses(MergeRun.class, MergeInputB.class)
+ .compile()
+ .writeToZip(zip1);
+ D8TestCompileResult result2 =
+ testForD8()
+ .setMinApi(AndroidApiLevel.L)
+ .addProgramClasses(MergeInputA.class)
+ .compile()
+ .writeToZip(zip2);
+
+ testForD8()
+ .addProgramFiles(zip1, zip2)
+ .setMinApi(AndroidApiLevel.L)
+ .compile()
+ .run(MergeRun.class)
+ .assertSuccessWithOutput(jvmOutput);
+ }
+
+
+ static class MergeInputA {
+ public void foo() {
+ System.out.println(Integer.hashCode(42));
+ System.out.println(Double.hashCode(42.0));
+ }
+ }
+
+ static class MergeInputB {
+ public void foo() {
+ System.out.println(Integer.hashCode(43));
+ System.out.println(Double.hashCode(43.0));
+ }
+ }
+
+ static class MergeRun {
+ public static void main(String[] args) {
+ MergeInputA a = new MergeInputA();
+ MergeInputB b = new MergeInputB();
+ a.foo();
+ b.foo();
+ }
+ }
+
static class Java8Methods {
public static void main(String[] args) {
byte[] aBytes = new byte[]{42, 1, -1, Byte.MAX_VALUE, Byte.MIN_VALUE};
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10JumboStringTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10JumboStringTest.java
new file mode 100644
index 0000000..365dc58
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10JumboStringTest.java
@@ -0,0 +1,20 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.internal;
+
+import com.android.tools.r8.CompilationMode;
+import org.junit.Test;
+
+public class R8GMSCoreV10JumboStringTest extends R8GMSCoreTreeShakeJarVerificationTest {
+
+ @Test
+ public void verify() throws Exception {
+ buildAndTreeShakeFromDeployJar(
+ CompilationMode.RELEASE,
+ GMSCORE_V10_DIR,
+ false,
+ GMSCORE_V10_MAX_SIZE,
+ options -> options.testing.forceJumboStringProcessing = true);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/checkcast/TrivialArrayCheckCastTest.java b/src/test/java/com/android/tools/r8/ir/optimize/checkcast/TrivialArrayCheckCastTest.java
new file mode 100644
index 0000000..8cf8b26
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/checkcast/TrivialArrayCheckCastTest.java
@@ -0,0 +1,188 @@
+// 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.optimize.checkcast;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+
+/** Regression test for b/123269162. */
+public class TrivialArrayCheckCastTest extends TestBase {
+
+ @Test
+ public void test() throws Exception {
+ String expectedOutput =
+ StringUtils.lines(
+ "Caught NullPointerException", "Caught NullPointerException",
+ "Caught NullPointerException", "Caught NullPointerException",
+ "Caught NullPointerException", "Caught NullPointerException",
+ "Caught NullPointerException", "Caught NullPointerException",
+ "Caught NullPointerException", "Caught NullPointerException");
+
+ testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expectedOutput);
+
+ InternalOptions options = new InternalOptions();
+ options.minApiLevel = AndroidApiLevel.I_MR1.getLevel();
+ assert options.canHaveArtCheckCastVerifierBug();
+
+ testForR8(Backend.DEX)
+ .addInnerClasses(TrivialArrayCheckCastTest.class)
+ .addKeepMainRule(TestClass.class)
+ .enableInliningAnnotations()
+ .setMinApi(AndroidApiLevel.I_MR1)
+ .run(TestClass.class)
+ .assertSuccessWithOutput(expectedOutput);
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ testBooleanArray();
+ testByteArray();
+ testCharArray();
+ testDoubleArray();
+ testFloatArray();
+ testFloatArrayNested();
+ testIntArray();
+ testLongArray();
+ testObjectArray();
+ testShortArray();
+ }
+
+ @NeverInline
+ private static void testBooleanArray() {
+ boolean[] array = (boolean[]) null;
+ try {
+ boolean value = array[42];
+ System.out.println("Read value: " + value);
+ } catch (ArrayIndexOutOfBoundsException e) {
+ System.out.println("Caught ArrayIndexOutOfBoundsException");
+ } catch (NullPointerException e) {
+ System.out.println("Caught NullPointerException");
+ }
+ }
+
+ @NeverInline
+ private static void testByteArray() {
+ byte[] array = (byte[]) null;
+ try {
+ byte value = array[42];
+ System.out.println("Read value: " + value);
+ } catch (ArrayIndexOutOfBoundsException e) {
+ System.out.println("Caught ArrayIndexOutOfBoundsException");
+ } catch (NullPointerException e) {
+ System.out.println("Caught NullPointerException");
+ }
+ }
+
+ @NeverInline
+ private static void testCharArray() {
+ char[] array = (char[]) null;
+ try {
+ char value = array[42];
+ System.out.println("Read value: " + value);
+ } catch (ArrayIndexOutOfBoundsException e) {
+ System.out.println("Caught ArrayIndexOutOfBoundsException");
+ } catch (NullPointerException e) {
+ System.out.println("Caught NullPointerException");
+ }
+ }
+
+ @NeverInline
+ private static void testDoubleArray() {
+ double[] array = (double[]) null;
+ try {
+ double value = array[42];
+ System.out.println("Read value: " + value);
+ } catch (ArrayIndexOutOfBoundsException e) {
+ System.out.println("Caught ArrayIndexOutOfBoundsException");
+ } catch (NullPointerException e) {
+ System.out.println("Caught NullPointerException");
+ }
+ }
+
+ @NeverInline
+ private static void testFloatArray() {
+ float[] array = (float[]) null;
+ try {
+ float value = array[42];
+ System.out.println("Read value: " + value);
+ } catch (ArrayIndexOutOfBoundsException e) {
+ System.out.println("Caught ArrayIndexOutOfBoundsException");
+ } catch (NullPointerException e) {
+ System.out.println("Caught NullPointerException");
+ }
+ }
+
+ @NeverInline
+ private static void testFloatArrayNested() {
+ float[][] nestedArray = (float[][]) null;
+ try {
+ float[] array = nestedArray[42];
+ float value = array[42];
+ System.out.println("Read value: " + value);
+ } catch (ArrayIndexOutOfBoundsException e) {
+ System.out.println("Caught ArrayIndexOutOfBoundsException");
+ } catch (NullPointerException e) {
+ System.out.println("Caught NullPointerException");
+ }
+ }
+
+ @NeverInline
+ private static void testIntArray() {
+ int[] array = (int[]) null;
+ try {
+ int value = array[42];
+ System.out.println("Read value: " + value);
+ } catch (ArrayIndexOutOfBoundsException e) {
+ System.out.println("Caught ArrayIndexOutOfBoundsException");
+ } catch (NullPointerException e) {
+ System.out.println("Caught NullPointerException");
+ }
+ }
+
+ @NeverInline
+ private static void testLongArray() {
+ long[] array = (long[]) null;
+ try {
+ long value = array[42];
+ System.out.println("Read value: " + value);
+ } catch (ArrayIndexOutOfBoundsException e) {
+ System.out.println("Caught ArrayIndexOutOfBoundsException");
+ } catch (NullPointerException e) {
+ System.out.println("Caught NullPointerException");
+ }
+ }
+
+ @NeverInline
+ private static void testObjectArray() {
+ Object[] array = (Object[]) null;
+ try {
+ Object value = array[42];
+ System.out.println("Read value: " + value);
+ } catch (ArrayIndexOutOfBoundsException e) {
+ System.out.println("Caught ArrayIndexOutOfBoundsException");
+ } catch (NullPointerException e) {
+ System.out.println("Caught NullPointerException");
+ }
+ }
+
+ @NeverInline
+ private static void testShortArray() {
+ short[] array = (short[]) null;
+ try {
+ short value = array[42];
+ System.out.println("Read value: " + value);
+ } catch (ArrayIndexOutOfBoundsException e) {
+ System.out.println("Caught ArrayIndexOutOfBoundsException");
+ } catch (NullPointerException e) {
+ System.out.println("Caught NullPointerException");
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
index 6504169..906aee9 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
@@ -23,8 +23,6 @@
import com.android.tools.r8.shaking.ProguardConfigurationParser;
import com.android.tools.r8.shaking.ProguardConfigurationRule;
import com.android.tools.r8.shaking.ProguardKeepAttributes;
-import com.android.tools.r8.shaking.ProguardKeepRule;
-import com.android.tools.r8.shaking.ProguardKeepRuleType;
import com.android.tools.r8.shaking.ProguardMemberRule;
import com.android.tools.r8.shaking.ProguardMemberType;
import com.android.tools.r8.shaking.forceproguardcompatibility.defaultmethods.ClassImplementingInterface;
@@ -40,8 +38,6 @@
import com.android.tools.r8.utils.codeinspector.FieldSubject;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
-import it.unimi.dsi.fastutil.objects.Object2BooleanArrayMap;
-import it.unimi.dsi.fastutil.objects.Object2BooleanMap;
import java.io.File;
import java.nio.file.Path;
import java.util.ArrayList;
@@ -277,39 +273,6 @@
assertEquals(subject.isPresent() && allowObfuscation, subject.isRenamed());
});
- // Check the Proguard compatibility rules generated.
- ProguardConfigurationParser parser =
- new ProguardConfigurationParser(new DexItemFactory(), new Reporter());
- parser.parse(proguardCompatibilityRules);
- ProguardConfiguration configuration = parser.getConfigRawForTesting();
- if (forceProguardCompatibility) {
- List<ProguardConfigurationRule> rules = configuration.getRules();
- assertEquals(2, rules.size());
- for (ProguardConfigurationRule r : rules) {
- assertTrue(r instanceof ProguardKeepRule);
- ProguardKeepRule rule = (ProguardKeepRule) r;
- assertEquals(ProguardKeepRuleType.KEEP, rule.getType());
- assertTrue(rule.getModifiers().allowsObfuscation);
- assertTrue(rule.getModifiers().allowsOptimization);
- List<ProguardMemberRule> memberRules = rule.getMemberRules();
- ProguardClassNameList classNames = rule.getClassNames();
- assertEquals(1, classNames.size());
- DexType type = classNames.asSpecificDexTypes().get(0);
- if (type.toSourceString().equals(forNameClass1.getCanonicalName())) {
- assertEquals(1, memberRules.size());
- assertEquals(ProguardMemberType.INIT, memberRules.iterator().next().getRuleType());
- } else {
- assertTrue(type.toSourceString().equals(forNameClass2.getCanonicalName()));
- // During parsing we add in the default constructor if there are otherwise no single
- // member rule.
- assertEquals(1, memberRules.size());
- assertEquals(ProguardMemberType.INIT, memberRules.iterator().next().getRuleType());
- }
- }
- } else {
- assertEquals(0, configuration.getRules().size());
- }
-
if (isRunProguard()) {
Path proguardedJar = File.createTempFile("proguarded", ".jar", temp.getRoot()).toPath();
Path proguardConfigFile = File.createTempFile("proguard", ".config", temp.getRoot()).toPath();
@@ -376,38 +339,6 @@
assertTrue(bar.isPresent());
assertEquals(bar.isPresent() && allowObfuscation, bar.isRenamed());
- // Check the Proguard compatibility rules generated.
- ProguardConfigurationParser parser =
- new ProguardConfigurationParser(new DexItemFactory(), new Reporter());
- parser.parse(proguardCompatibilityRules);
- ProguardConfiguration configuration = parser.getConfigRawForTesting();
- if (forceProguardCompatibility) {
- List<ProguardConfigurationRule> rules = configuration.getRules();
- assertEquals(2, rules.size());
- for (ProguardConfigurationRule r : rules) {
- assertTrue(r instanceof ProguardKeepRule);
- ProguardKeepRule rule = (ProguardKeepRule) r;
- assertEquals(ProguardKeepRuleType.KEEP_CLASS_MEMBERS, rule.getType());
- assertTrue(rule.getModifiers().allowsObfuscation);
- assertTrue(rule.getModifiers().allowsOptimization);
- List<ProguardMemberRule> memberRules = rule.getMemberRules();
- ProguardClassNameList classNames = rule.getClassNames();
- assertEquals(1, classNames.size());
- DexType type = classNames.asSpecificDexTypes().get(0);
- assertEquals(withMemberClass.getCanonicalName(), type.toSourceString());
- assertEquals(1, memberRules.size());
- ProguardMemberRule memberRule = memberRules.iterator().next();
- if (memberRule.getRuleType() == ProguardMemberType.FIELD) {
- assertTrue(memberRule.getName().matches("foo"));
- } else {
- assertEquals(ProguardMemberType.METHOD, memberRule.getRuleType());
- assertTrue(memberRule.getName().matches("bar"));
- }
- }
- } else {
- assertEquals(0, configuration.getRules().size());
- }
-
if (isRunProguard()) {
Path proguardedJar = File.createTempFile("proguarded", ".jar", temp.getRoot()).toPath();
Path proguardConfigFile = File.createTempFile("proguard", ".config", temp.getRoot()).toPath();
@@ -481,39 +412,6 @@
assertTrue(f.isPresent());
assertEquals(f.isPresent() && allowObfuscation, f.isRenamed());
- // Check the Proguard compatibility rules generated.
- ProguardConfigurationParser parser =
- new ProguardConfigurationParser(new DexItemFactory(), new Reporter());
- parser.parse(proguardCompatibilityRules);
- ProguardConfiguration configuration = parser.getConfigRawForTesting();
- if (forceProguardCompatibility) {
- List<ProguardConfigurationRule> rules = configuration.getRules();
- assertEquals(3, rules.size());
- Object2BooleanMap<String> keptFields = new Object2BooleanArrayMap<>();
- for (ProguardConfigurationRule r : rules) {
- assertTrue(r instanceof ProguardKeepRule);
- ProguardKeepRule rule = (ProguardKeepRule) r;
- assertEquals(ProguardKeepRuleType.KEEP, rule.getType());
- assertTrue(rule.getModifiers().allowsObfuscation);
- assertTrue(rule.getModifiers().allowsOptimization);
- List<ProguardMemberRule> memberRules = rule.getMemberRules();
- ProguardClassNameList classNames = rule.getClassNames();
- assertEquals(1, classNames.size());
- DexType type = classNames.asSpecificDexTypes().get(0);
- assertEquals(withVolatileFields.getCanonicalName(), type.toSourceString());
- assertEquals(1, memberRules.size());
- ProguardMemberRule memberRule = memberRules.iterator().next();
- assertEquals(ProguardMemberType.FIELD, memberRule.getRuleType());
- keptFields.put(memberRule.getName().toString(), true);
- }
- assertEquals(3, keptFields.size());
- assertTrue(keptFields.containsKey("intField"));
- assertTrue(keptFields.containsKey("longField"));
- assertTrue(keptFields.containsKey("objField"));
- } else {
- assertEquals(0, configuration.getRules().size());
- }
-
if (isRunProguard()) {
Path proguardedJar = File.createTempFile("proguarded", ".jar", temp.getRoot()).toPath();
Path proguardConfigFile = File.createTempFile("proguard", ".config", temp.getRoot()).toPath();
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTest.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTest.java
new file mode 100644
index 0000000..e518181
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTest.java
@@ -0,0 +1,20 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.shaking.keptgraph;
+
+import com.android.tools.r8.Keep;
+
+@Keep
+public class KeptByFieldReflectionTest {
+
+ // TODO(b/123262024): This field must be kept un-initialized. Otherwise the "-whyareyoukeeping"
+ // output tested will hit the initialization in <init> and not the reflective access.
+ public int foo;
+
+ public static void main(String[] args) throws Exception {
+ // Due to b/123210548 the object cannot be created by a reflective newInstance call.
+ KeptByFieldReflectionTest obj = new KeptByFieldReflectionTest();
+ System.out.println("got foo: " + KeptByFieldReflectionTest.class.getField("foo").getInt(obj));
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTestRunner.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTestRunner.java
new file mode 100644
index 0000000..62dcebe
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByFieldReflectionTestRunner.java
@@ -0,0 +1,85 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.shaking.keptgraph;
+
+import static com.android.tools.r8.references.Reference.fieldFromField;
+import static com.android.tools.r8.references.Reference.methodFromMethod;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.references.FieldReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.shaking.WhyAreYouKeepingConsumer;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.graphinspector.GraphInspector;
+import com.android.tools.r8.utils.graphinspector.GraphInspector.QueryNode;
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.util.Arrays;
+import java.util.Collection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class KeptByFieldReflectionTestRunner extends TestBase {
+
+ private static final Class<?> CLASS = KeptByFieldReflectionTest.class;
+ private static final Collection<Class<?>> CLASSES = Arrays.asList(CLASS);
+
+ private final String EXPECTED_STDOUT = StringUtils.lines("got foo: 0");
+
+ private final String EXPECTED_WHYAREYOUKEEPING =
+ StringUtils.lines(
+ "int com.android.tools.r8.shaking.keptgraph.KeptByFieldReflectionTest.foo",
+ "|- is reflected from:",
+ "| void com.android.tools.r8.shaking.keptgraph.KeptByFieldReflectionTest.main(java.lang.String[])",
+ "|- is referenced in keep rule:",
+ "| -keep class com.android.tools.r8.shaking.keptgraph.KeptByFieldReflectionTest { public static void main(java.lang.String[]); }");
+
+ private final Backend backend;
+
+ @Parameters(name = "{0}")
+ public static Backend[] data() {
+ return Backend.values();
+ }
+
+ public KeptByFieldReflectionTestRunner(Backend backend) {
+ this.backend = backend;
+ }
+
+ @Test
+ public void test() throws Exception {
+ MethodReference mainMethod = methodFromMethod(CLASS.getDeclaredMethod("main", String[].class));
+ FieldReference fooField = fieldFromField(CLASS.getDeclaredField("foo"));
+
+ if (backend == Backend.CF) {
+ testForJvm().addProgramClasses(CLASSES).run(CLASS).assertSuccessWithOutput(EXPECTED_STDOUT);
+ }
+
+ WhyAreYouKeepingConsumer consumer = new WhyAreYouKeepingConsumer(null);
+ GraphInspector inspector =
+ testForR8(backend)
+ .enableGraphInspector(consumer)
+ .enableInliningAnnotations()
+ .addProgramClasses(CLASSES)
+ .addKeepMainRule(CLASS)
+ .run(CLASS)
+ .assertSuccessWithOutput(EXPECTED_STDOUT)
+ .graphInspector();
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ consumer.printWhyAreYouKeeping(fooField, new PrintStream(baos));
+ assertEquals(EXPECTED_WHYAREYOUKEEPING, baos.toString());
+
+ assertEquals(1, inspector.getRoots().size());
+ QueryNode keepMain = inspector.rule(Origin.unknown(), 1, 1).assertRoot();
+
+ inspector.method(mainMethod).assertNotRenamed().assertKeptBy(keepMain);
+
+ inspector.field(fooField).assertRenamed().assertReflectedFrom(mainMethod);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByMethodReflectionTest.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByMethodReflectionTest.java
new file mode 100644
index 0000000..6394237
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByMethodReflectionTest.java
@@ -0,0 +1,20 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.shaking.keptgraph;
+
+import com.android.tools.r8.Keep;
+
+@Keep
+public class KeptByMethodReflectionTest {
+
+ public void foo() {
+ System.out.println("called foo");
+ }
+
+ public static void main(String[] args) throws Exception {
+ // Due to b/123210548 the object cannot be created by a reflective newInstance call.
+ KeptByMethodReflectionTest obj = new KeptByMethodReflectionTest();
+ KeptByMethodReflectionTest.class.getMethod("foo").invoke(obj);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByMethodReflectionTestRunner.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByMethodReflectionTestRunner.java
new file mode 100644
index 0000000..3224689
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByMethodReflectionTestRunner.java
@@ -0,0 +1,83 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.shaking.keptgraph;
+
+import static com.android.tools.r8.references.Reference.methodFromMethod;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.shaking.WhyAreYouKeepingConsumer;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.graphinspector.GraphInspector;
+import com.android.tools.r8.utils.graphinspector.GraphInspector.QueryNode;
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.util.Arrays;
+import java.util.Collection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class KeptByMethodReflectionTestRunner extends TestBase {
+
+ private static final Class<?> CLASS = KeptByMethodReflectionTest.class;
+ private static final Collection<Class<?>> CLASSES = Arrays.asList(CLASS);
+
+ private final String EXPECTED_STDOUT = StringUtils.lines("called foo");
+
+ private final String EXPECTED_WHYAREYOUKEEPING =
+ StringUtils.lines(
+ "void com.android.tools.r8.shaking.keptgraph.KeptByMethodReflectionTest.foo()",
+ "|- is reflected from:",
+ "| void com.android.tools.r8.shaking.keptgraph.KeptByMethodReflectionTest.main(java.lang.String[])",
+ "|- is referenced in keep rule:",
+ "| -keep class com.android.tools.r8.shaking.keptgraph.KeptByMethodReflectionTest { public static void main(java.lang.String[]); }");
+
+ private final Backend backend;
+
+ @Parameters(name = "{0}")
+ public static Backend[] data() {
+ return Backend.values();
+ }
+
+ public KeptByMethodReflectionTestRunner(Backend backend) {
+ this.backend = backend;
+ }
+
+ @Test
+ public void test() throws Exception {
+ MethodReference mainMethod = methodFromMethod(CLASS.getDeclaredMethod("main", String[].class));
+ MethodReference fooMethod = methodFromMethod(CLASS.getDeclaredMethod("foo"));
+
+ if (backend == Backend.CF) {
+ testForJvm().addProgramClasses(CLASSES).run(CLASS).assertSuccessWithOutput(EXPECTED_STDOUT);
+ }
+
+ WhyAreYouKeepingConsumer consumer = new WhyAreYouKeepingConsumer(null);
+ GraphInspector inspector =
+ testForR8(backend)
+ .enableGraphInspector(consumer)
+ .enableInliningAnnotations()
+ .addProgramClasses(CLASSES)
+ .addKeepMainRule(CLASS)
+ .run(CLASS)
+ .assertSuccessWithOutput(EXPECTED_STDOUT)
+ .graphInspector();
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ consumer.printWhyAreYouKeeping(fooMethod, new PrintStream(baos));
+ assertEquals(EXPECTED_WHYAREYOUKEEPING, baos.toString());
+
+ assertEquals(1, inspector.getRoots().size());
+ QueryNode keepMain = inspector.rule(Origin.unknown(), 1, 1).assertRoot();
+
+ inspector.method(mainMethod).assertNotRenamed().assertKeptBy(keepMain);
+
+ inspector.method(fooMethod).assertRenamed().assertReflectedFrom(mainMethod);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/reflection/FieldAccessTest.java b/src/test/java/com/android/tools/r8/shaking/reflection/FieldAccessTest.java
new file mode 100644
index 0000000..7aa573e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/reflection/FieldAccessTest.java
@@ -0,0 +1,161 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.reflection;
+import static com.android.tools.r8.references.Reference.fieldFromField;
+import static com.android.tools.r8.references.Reference.methodFromMethod;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.references.FieldReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.graphinspector.GraphInspector.QueryNode;
+import org.junit.Assert;
+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 FieldAccessTest extends TestBase {
+
+ private final Backend backend;
+
+ @Parameters(name = "{0}")
+ public static Backend[] data() {
+ return Backend.values();
+ }
+
+ public FieldAccessTest(Backend backend) {
+ this.backend = backend;
+ }
+
+ private boolean isInvokeGetField(InstructionSubject instruction) {
+ return
+ instruction.isInvoke()
+ && instruction.getMethod().qualifiedName().equals("java.lang.Class.getField");
+ }
+
+ private void runTest(Class<?> testClass) throws Exception {
+ MethodReference mainMethod =
+ methodFromMethod(testClass.getDeclaredMethod("main", String[].class));
+ FieldReference fooField = fieldFromField(testClass.getDeclaredField("foo"));
+
+ testForR8(Backend.DEX)
+ .enableGraphInspector()
+ .addProgramClasses(testClass)
+ .addKeepMainRule(testClass)
+ .run(testClass)
+ .inspectGraph(inspector -> {
+ // The only root should be the keep annotation rule.
+ assertEquals(1, inspector.getRoots().size());
+ QueryNode root = inspector.rule(Origin.unknown(), 1, 1).assertRoot();
+
+ inspector.method(mainMethod).assertNotRenamed().assertKeptBy(root);
+ inspector.field(fooField).assertRenamed();
+ })
+ .inspect(inspector -> {
+ Assert.assertTrue(
+ inspector
+ .clazz(testClass)
+ .uniqueMethodWithName("main")
+ .streamInstructions()
+ .anyMatch(this::isInvokeGetField));
+ })
+ .assertSuccessWithOutput("42");
+ }
+
+ @Test
+ public void reflectiveGet() throws Exception {
+ runTest(FieldAccessTestGet.class);
+ }
+
+ @Test
+ public void reflectiveGetStatic() throws Exception {
+ runTest(FieldAccessTestGetStatic.class);
+ }
+
+ @Test
+ public void reflectivePut() throws Exception {
+ runTest(FieldAccessTestPut.class);
+ }
+
+ @Test
+ public void reflectivePutStatic() throws Exception {
+ runTest(FieldAccessTestPutStatic.class);
+ }
+
+ @Test
+ public void reflectivePutGet() throws Exception {
+ runTest(FieldAccessTestPutGet.class);
+ }
+
+ @Test
+ public void reflectivePutGetStatic() throws Exception {
+ runTest(FieldAccessTestPutGetStatic.class);
+ }
+}
+
+class FieldAccessTestGet {
+
+ public int foo = 42;
+
+ public static void main(String[] args) throws Exception {
+ FieldAccessTestGet obj = new FieldAccessTestGet();
+ System.out.print(FieldAccessTestGet.class.getField("foo").getInt(obj));
+ }
+}
+
+class FieldAccessTestGetStatic {
+
+ public static int foo = 42;
+
+ public static void main(String[] args) throws Exception {
+ System.out.print(FieldAccessTestGetStatic.class.getField("foo").getInt(null));
+ }
+}
+
+class FieldAccessTestPut {
+
+ public int foo;
+
+ public static void main(String[] args) throws Exception {
+ FieldAccessTestPut obj = new FieldAccessTestPut();
+ FieldAccessTestPut.class.getField("foo").setInt(obj, 42);
+ System.out.print(42);
+ }
+}
+
+class FieldAccessTestPutStatic {
+
+ public static int foo;
+
+ public static void main(String[] args) throws Exception {
+ FieldAccessTestPutStatic.class.getField("foo").setInt(null, 42);
+ System.out.print(42);
+ }
+}
+
+class FieldAccessTestPutGet {
+
+ public int foo;
+
+ public static void main(String[] args) throws Exception {
+ FieldAccessTestPutGet obj = new FieldAccessTestPutGet();
+ FieldAccessTestPutGet.class.getField("foo").setInt(obj, 42);
+ System.out.print(FieldAccessTestPutGet.class.getField("foo").getInt(obj));
+ }
+}
+
+class FieldAccessTestPutGetStatic {
+
+ public static int foo;
+
+ public static void main(String[] args) throws Exception {
+ FieldAccessTestPutGetStatic.class.getField("foo").setInt(null, 42);
+ System.out.print(FieldAccessTestPutGetStatic.class.getField("foo").getInt(null));
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java b/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java
index 940482b..1d53013 100644
--- a/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java
@@ -41,6 +41,8 @@
public static class EdgeKindPredicate implements Predicate<Set<GraphEdgeInfo>> {
public static final EdgeKindPredicate keepRule = new EdgeKindPredicate(EdgeKind.KeepRule);
public static final EdgeKindPredicate invokedFrom = new EdgeKindPredicate(EdgeKind.InvokedFrom);
+ public static final EdgeKindPredicate reflectedFrom =
+ new EdgeKindPredicate(EdgeKind.ReflectiveUseFrom);
private final EdgeKind edgeKind;
@@ -69,6 +71,8 @@
abstract boolean isInvokedFrom(MethodReference method);
+ abstract boolean isReflectedFrom(MethodReference method);
+
abstract boolean isKeptBy(QueryNode node);
abstract String getNodeDescription();
@@ -119,6 +123,19 @@
return this;
}
+ public QueryNode assertReflectedFrom(MethodReference method) {
+ assertTrue(
+ errorMessage("reflection from " + method.toString(), "none"), isReflectedFrom(method));
+ return this;
+ }
+
+ public QueryNode assertNotReflectedFrom(MethodReference method) {
+ assertFalse(
+ errorMessage("no reflection from " + method.toString(), "reflection"),
+ isReflectedFrom(method));
+ return this;
+ }
+
public QueryNode assertKeptBy(QueryNode node) {
assertTrue(
"Invalid call to assertKeptBy with: " + node.getNodeDescription(), node.isPresent());
@@ -174,6 +191,12 @@
}
@Override
+ public boolean isReflectedFrom(MethodReference method) {
+ fail("Invalid call to isReflectedFrom on " + getNodeDescription());
+ throw new Unreachable();
+ }
+
+ @Override
public boolean isKeptBy(QueryNode node) {
fail("Invalid call to isKeptBy on " + getNodeDescription());
throw new Unreachable();
@@ -238,6 +261,18 @@
}
@Override
+ public boolean isReflectedFrom(MethodReference method) {
+ GraphNode sourceMethod = inspector.methods.get(method);
+ if (sourceMethod == null) {
+ return false;
+ }
+ return filterSources(
+ (node, infos) -> node == sourceMethod && EdgeKindPredicate.reflectedFrom.test(infos))
+ .findFirst()
+ .isPresent();
+ }
+
+ @Override
public boolean isKeptBy(QueryNode node) {
if (!(node instanceof QueryNodeImpl)) {
return false;
@@ -356,6 +391,10 @@
return getQueryNode(methods.get(method), method.toString());
}
+ public QueryNode field(FieldReference field) {
+ return getQueryNode(fields.get(field), field.toString());
+ }
+
private QueryNode getQueryNode(GraphNode node, String absentString) {
return node == null ? new AbsentQueryNode(absentString) : new QueryNodeImpl(this, node);
}