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); }