Merge "Start looking for specific code patterns in the enqueuer"
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index dcf29a4..2c3a656 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -109,6 +109,7 @@
public final DexString getClassMethodName = createString("getClass");
public final DexString ordinalMethodName = createString("ordinal");
public final DexString desiredAssertionStatusMethodName = createString("desiredAssertionStatus");
+ public final DexString forNameMethodName = createString("forName");
public final DexString getNameName = createString("getName");
public final DexString getSimpleNameName = createString("getSimpleName");
public final DexString assertionsDisabled = createString("$assertionsDisabled");
@@ -246,12 +247,15 @@
public class ClassMethods {
public DexMethod desiredAssertionStatus;
+ public DexMethod forName;
public DexMethod getName;
public DexMethod getSimpleName;
private ClassMethods() {
desiredAssertionStatus = createMethod(classDescriptor,
desiredAssertionStatusMethodName, booleanDescriptor, DexString.EMPTY_ARRAY);
+ forName = createMethod(classDescriptor,
+ forNameMethodName, classDescriptor, new DexString[]{stringDescriptor});
getName = createMethod(classDescriptor, getNameName, stringDescriptor, DexString.EMPTY_ARRAY);
getSimpleName = createMethod(classDescriptor,
getSimpleNameName, stringDescriptor, DexString.EMPTY_ARRAY);
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 ad00c86..2d0ddb8 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking;
+import com.android.tools.r8.ApiLevelException;
import com.android.tools.r8.dex.IndexedItemCollection;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.Unreachable;
@@ -28,8 +29,12 @@
import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.graph.KeyedDexItem;
import com.android.tools.r8.graph.PresortedComparable;
+import com.android.tools.r8.ir.code.ConstString;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
+import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.Timing;
import com.google.common.collect.ImmutableMap;
@@ -144,6 +149,12 @@
private Queue<Action> proguardCompatibilityWorkList = Queues.newArrayDeque();
/**
+ * A set of methods that need code inspection for Proguard compatibility rules.
+ */
+ private final Set<DexEncodedMethod> pendingProguardReflectiveCompatibility =
+ Sets.newLinkedHashSet();
+
+ /**
* A cache for DexMethod that have been marked reachable.
*/
private Set<DexMethod> virtualTargetsMarkedAsReachable = Sets.newIdentityHashSet();
@@ -237,6 +248,10 @@
@Override
public boolean registerInvokeStatic(DexMethod method) {
+ if (options.forceProguardCompatibility &&
+ method == appInfo.dexItemFactory.classMethods.forName) {
+ pendingProguardReflectiveCompatibility.add(currentMethod);
+ }
if (!registerItemWithTarget(staticInvokes, method)) {
return false;
}
@@ -381,13 +396,7 @@
// For Proguard compatibility keep the default initializer for live types.
if (options.forceProguardCompatibility) {
if (holder.isProgramClass() && holder.hasDefaultInitializer()) {
- DexEncodedMethod init = holder.getDefaultInitializer();
- ProguardKeepRule rule =
- ProguardConfigurationUtils.buildDefaultInitializerKeepRule(holder);
- proguardCompatibilityWorkList.add(
- Action.markInstantiated(holder, KeepReason.dueToProguardCompatibilityKeepRule(rule)));
- proguardCompatibilityWorkList.add(
- Action.markMethodLive(init, KeepReason.dueToProguardCompatibilityKeepRule(rule)));
+ markClassAsInstantiatedWithCompatRule(holder);
}
}
}
@@ -870,11 +879,14 @@
}
// Continue fix-point processing while there are additional work items to ensure
// Proguard compatibility.
- if (proguardCompatibilityWorkList.isEmpty()) {
+ if (proguardCompatibilityWorkList.isEmpty() &&
+ pendingProguardReflectiveCompatibility.isEmpty()) {
break;
}
+ pendingProguardReflectiveCompatibility.forEach(this::handleProguardReflectiveBehavior);
workList.addAll(proguardCompatibilityWorkList);
proguardCompatibilityWorkList.clear();
+ pendingProguardReflectiveCompatibility.clear();
}
if (Log.ENABLED) {
Set<DexEncodedMethod> allLive = Sets.newIdentityHashSet();
@@ -1049,6 +1061,49 @@
collectReachedFields(staticFieldsWritten, this::tryLookupStaticField)));
}
+ private void markClassAsInstantiatedWithCompatRule(DexClass clazz) {
+ ProguardKeepRule rule =
+ ProguardConfigurationUtils.buildDefaultInitializerKeepRule(clazz);
+ proguardCompatibilityWorkList.add(
+ Action.markInstantiated(clazz, KeepReason.dueToProguardCompatibilityKeepRule(rule)));
+ if (clazz.hasDefaultInitializer()) {
+ proguardCompatibilityWorkList.add(
+ Action.markMethodLive(
+ clazz.getDefaultInitializer(), KeepReason.dueToProguardCompatibilityKeepRule(rule)));
+ }
+ }
+
+ private void handleProguardReflectiveBehavior(DexEncodedMethod method) {
+ try {
+ IRCode code = method.buildIR(options);
+ code.instructionIterator().forEachRemaining(this::handleProguardReflectiveBehavior);
+ } catch (ApiLevelException e) {
+ // Ignore this exception here. It will be hit again further in the pipeline when
+ // generating code.
+ }
+ }
+
+ private void handleProguardReflectiveBehavior(Instruction instruction) {
+ // Handle actual invokes of Class.forName(className) where className is a const string
+ // representing a known class as an implicit keep rule on that class.
+ if (instruction.isInvokeStatic()
+ && (instruction.asInvokeStatic().getInvokedMethod() ==
+ appInfo.dexItemFactory.classMethods.forName)
+ && instruction.asInvokeStatic().arguments().get(0).isConstant()) {
+ assert instruction.asInvokeStatic().arguments().get(0).getConstInstruction().isConstString();
+ ConstString constString =
+ instruction.asInvokeStatic().arguments().get(0).getConstInstruction().asConstString();
+ DexString forNameArgument = constString.getValue();
+ DexString forNameDescriptor = appInfo.dexItemFactory.createString(
+ DescriptorUtils.javaTypeToDescriptor(forNameArgument.toString()));
+ DexType forNameType = appInfo.dexItemFactory.createType(forNameDescriptor);
+ DexClass forNameClass = appInfo.definitionFor(forNameType);
+ if (forNameClass != null) {
+ markClassAsInstantiatedWithCompatRule(forNameClass);
+ }
+ }
+ }
+
private static class Action {
final Kind kind;
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 e00d36a..9fea981 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationUtils.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationUtils.java
@@ -18,11 +18,13 @@
ProguardClassNameList.Builder classNameListBuilder = ProguardClassNameList.builder();
classNameListBuilder.addClassName(false, ProguardTypeMatcher.create(clazz.type));
builder.setClassNames(classNameListBuilder.build());
- ProguardMemberRule.Builder memberRuleBuilder = ProguardMemberRule.builder();
- memberRuleBuilder.setRuleType(ProguardMemberType.INIT);
- memberRuleBuilder.setName("<init>");
- memberRuleBuilder.setArguments(ImmutableList.of());
- builder.getMemberRules().add(memberRuleBuilder.build());
+ if (clazz.hasDefaultInitializer()) {
+ ProguardMemberRule.Builder memberRuleBuilder = ProguardMemberRule.builder();
+ memberRuleBuilder.setRuleType(ProguardMemberType.INIT);
+ memberRuleBuilder.setName("<init>");
+ memberRuleBuilder.setArguments(ImmutableList.of());
+ builder.getMemberRules().add(memberRuleBuilder.build());
+ }
return builder.build();
}
}
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 62ca811..a920ec7 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
@@ -12,6 +12,7 @@
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.compatproguard.CompatProguardCommandBuilder;
import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.shaking.ProguardClassNameList;
import com.android.tools.r8.shaking.ProguardConfiguration;
@@ -204,4 +205,78 @@
testCheckCast(
false, TestMainWithoutCheckCast.class, TestClassWithDefaultConstructor.class, false);
}
+
+ public void testClassForName(boolean forceProguardCompatibility) throws Exception {
+ CompatProguardCommandBuilder builder =
+ new CompatProguardCommandBuilder(forceProguardCompatibility, false);
+ Class mainClass = TestMainWithClassForName.class;
+ Class forNameClass1 = TestClassWithDefaultConstructor.class;
+ Class forNameClass2 = TestClassWithoutDefaultConstructor.class;
+ List<Class> forNameClasses = ImmutableList.of(forNameClass1, forNameClass2);
+ builder.addProgramFiles(ToolHelper.getClassFileForTestClass(mainClass));
+ builder.addProgramFiles(ToolHelper.getClassFileForTestClass(forNameClass1));
+ builder.addProgramFiles(ToolHelper.getClassFileForTestClass(forNameClass2));
+ List<String> proguardConfig = ImmutableList.of(
+ "-keep class " + mainClass.getCanonicalName() + " {",
+ " <init>();", // Add <init>() so it does not become a compatibility rule below.
+ " public static void main(java.lang.String[]);",
+ "}",
+ "-dontobfuscate");
+ builder.addProguardConfiguration(proguardConfig, Origin.unknown());
+ Path proguardCompatibilityRules = temp.newFile().toPath();
+ builder.setProguardCompatibilityRulesOutput(proguardCompatibilityRules);
+
+ DexInspector inspector = new DexInspector(ToolHelper.runR8(builder.build()));
+ assertTrue(inspector.clazz(getJavacGeneratedClassName(mainClass)).isPresent());
+ forNameClasses.forEach(clazz -> {
+ assertEquals(forceProguardCompatibility,
+ inspector.clazz(getJavacGeneratedClassName(clazz)).isPresent());
+ });
+
+ // Check the Proguard compatibility rules generated.
+ ProguardConfigurationParser parser =
+ new ProguardConfigurationParser(new DexItemFactory(), null);
+ parser.parse(proguardCompatibilityRules);
+ ProguardConfiguration configuration = parser.getConfigRawForTesting();
+ if (forceProguardCompatibility) {
+ assertEquals(2, configuration.getRules().size());
+ configuration.getRules().forEach(rule -> {
+ Set<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 (RUN_PROGUARD) {
+ Path proguardedJar = File.createTempFile("proguarded", ".jar", temp.getRoot()).toPath();
+ Path proguardConfigFile = File.createTempFile("proguard", ".config", temp.getRoot()).toPath();
+ FileUtils.writeTextFile(proguardConfigFile, proguardConfig);
+ ToolHelper.runProguard(jarTestClasses(
+ ImmutableList.of(mainClass, forNameClass1, forNameClass2)),
+ proguardedJar, proguardConfigFile);
+ Set<String> classesAfterProguard = readClassesInJar(proguardedJar);
+ assertTrue(classesAfterProguard.contains(mainClass.getCanonicalName()));
+ assertTrue(classesAfterProguard.contains(forNameClass1.getCanonicalName()));
+ assertTrue(classesAfterProguard.contains(forNameClass2.getCanonicalName()));
+ }
+ }
+
+ @Test
+ public void classForNameTest() throws Exception {
+ testClassForName(true);
+ testClassForName(false);
+ }
}
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/TestMainWithClassForName.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/TestMainWithClassForName.java
new file mode 100644
index 0000000..bebcf94
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/TestMainWithClassForName.java
@@ -0,0 +1,18 @@
+// Copyright (c) 2017, 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.forceproguardcompatibility;
+
+public class TestMainWithClassForName {
+ private static void method1() throws Exception {
+ System.out.println(Class.forName(
+ "com.android.tools.r8.shaking.forceproguardcompatibility.TestClassWithoutDefaultConstructor"));
+ }
+
+ public static void main(String[] args) throws Exception {
+ System.out.println(Class.forName(
+ "com.android.tools.r8.shaking.forceproguardcompatibility.TestClassWithDefaultConstructor"));
+ TestMainWithClassForName.method1();
+ }
+}