Add an option to "force Proguard compatibility"
Right now it will keep the default constructor for all live classes.
Only available through from the CompatProguard command.
Change-Id: Ic5e9b515973bf6e89eb783ab2f91665a5583a4c8
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexList.java b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
index 296507f..a885bbb 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexList.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
@@ -42,7 +42,8 @@
AppInfoWithSubtyping appInfo = new AppInfoWithSubtyping(application);
RootSet mainDexRootSet =
new RootSetBuilder(application, appInfo, options.mainDexKeepRules, options).run(executor);
- Set<DexType> mainDexBaseClasses = new Enqueuer(appInfo).traceMainDex(mainDexRootSet, timing);
+ Set<DexType> mainDexBaseClasses =
+ new Enqueuer(appInfo, options).traceMainDex(mainDexRootSet, timing);
Set<DexType> mainDexClasses = new MainDexListBuilder(mainDexBaseClasses, application).run();
List<String> result = mainDexClasses.stream()
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 39e0342..cbb7f0b 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -221,7 +221,7 @@
new RootSetBuilder(
application, appInfo, options.proguardConfiguration.getRules(), options)
.run(executorService);
- Enqueuer enqueuer = new Enqueuer(appInfo);
+ Enqueuer enqueuer = new Enqueuer(appInfo, options);
enqueuer.addExtension(new ProtoLiteExtension(appInfo));
appInfo = enqueuer.traceApplication(rootSet, timing);
if (options.proguardConfiguration.isPrintSeeds()) {
@@ -282,7 +282,7 @@
if (!options.mainDexKeepRules.isEmpty()) {
appInfo = new AppInfoWithSubtyping(application);
- Enqueuer enqueuer = new Enqueuer(appInfo);
+ Enqueuer enqueuer = new Enqueuer(appInfo, options);
// Lets find classes which may have code executed before secondary dex files installation.
RootSet mainDexRootSet =
new RootSetBuilder(application, appInfo, options.mainDexKeepRules, options)
@@ -301,7 +301,7 @@
if (options.useTreeShaking || !options.skipMinification) {
timing.begin("Post optimization code stripping");
try {
- Enqueuer enqueuer = new Enqueuer(appInfo);
+ Enqueuer enqueuer = new Enqueuer(appInfo, options);
appInfo = enqueuer.traceApplication(rootSet, timing);
if (options.useTreeShaking) {
TreePruner pruner = new TreePruner(application, appInfo.withLiveness(), options);
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 502dd3c..9529093 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -36,12 +36,18 @@
private Optional<Boolean> discardedChecker = Optional.empty();
private Optional<Boolean> minification = Optional.empty();
private boolean ignoreMissingClasses = false;
+ private boolean forceProguardCompatibility = false;
private Path proguardMapOutput = null;
private Builder() {
super(CompilationMode.RELEASE);
}
+ protected Builder(boolean forceProguardCompatibility) {
+ super(CompilationMode.RELEASE);
+ this.forceProguardCompatibility = forceProguardCompatibility;
+ }
+
private Builder(AndroidApp app) {
super(app, CompilationMode.RELEASE);
}
@@ -243,6 +249,7 @@
useDiscardedChecker,
useMinification,
ignoreMissingClasses,
+ forceProguardCompatibility,
proguardMapOutput);
}
}
@@ -283,6 +290,7 @@
private final boolean useDiscardedChecker;
private final boolean useMinification;
private final boolean ignoreMissingClasses;
+ private final boolean forceProguardCompatibility;
private final Path proguardMapOutput;
public static Builder builder() {
@@ -400,6 +408,7 @@
boolean useDiscardedChecker,
boolean useMinification,
boolean ignoreMissingClasses,
+ boolean forceProguardCompatibility,
Path proguardMapOutput) {
super(inputApp, outputPath, outputMode, mode, minApiLevel, diagnosticsHandler,
enableDesugaring);
@@ -413,6 +422,7 @@
this.useDiscardedChecker = useDiscardedChecker;
this.useMinification = useMinification;
this.ignoreMissingClasses = ignoreMissingClasses;
+ this.forceProguardCompatibility = forceProguardCompatibility;
this.proguardMapOutput = proguardMapOutput;
}
@@ -425,6 +435,7 @@
useDiscardedChecker = false;
useMinification = false;
ignoreMissingClasses = false;
+ forceProguardCompatibility = false;
proguardMapOutput = null;
}
public boolean useTreeShaking() {
@@ -477,6 +488,11 @@
internal.inlineAccessors = false;
}
internal.proguardMapOutput = proguardMapOutput;
+
+ // EXPERIMENTAL flags.
+ assert !internal.forceProguardCompatibility;
+ internal.forceProguardCompatibility = forceProguardCompatibility;
+
return internal;
}
}
diff --git a/src/main/java/com/android/tools/r8/code/ConstClass.java b/src/main/java/com/android/tools/r8/code/ConstClass.java
index 04fda4b..a5755e9 100644
--- a/src/main/java/com/android/tools/r8/code/ConstClass.java
+++ b/src/main/java/com/android/tools/r8/code/ConstClass.java
@@ -36,7 +36,7 @@
@Override
public void registerUse(UseRegistry registry) {
- registry.registerTypeReference(getType());
+ registry.registerConstClass(getType());
}
public DexType getType() {
diff --git a/src/main/java/com/android/tools/r8/compatproguard/CompatProguard.java b/src/main/java/com/android/tools/r8/compatproguard/CompatProguard.java
index 1ad589f..9dc5424 100644
--- a/src/main/java/com/android/tools/r8/compatproguard/CompatProguard.java
+++ b/src/main/java/com/android/tools/r8/compatproguard/CompatProguard.java
@@ -26,17 +26,21 @@
public static class CompatProguardOptions {
public final String output;
public final int minApi;
+ public final boolean forceProguardCompatibility;
public final List<String> proguardConfig;
- CompatProguardOptions(List<String> proguardConfig, String output, int minApi) {
+ CompatProguardOptions(List<String> proguardConfig, String output, int minApi,
+ boolean forceProguardCompatibility) {
this.output = output;
this.minApi = minApi;
+ this.forceProguardCompatibility = forceProguardCompatibility;
this.proguardConfig = proguardConfig;
}
public static CompatProguardOptions parse(String[] args) throws CompilationException {
String output = null;
int minApi = 1;
+ boolean forceProguardCompatibility = false;
ImmutableList.Builder<String> builder = ImmutableList.builder();
if (args.length > 0) {
StringBuilder currentLine = new StringBuilder(args[0]);
@@ -45,6 +49,8 @@
if (arg.charAt(0) == '-') {
if (arg.equals("--min-api")) {
minApi = Integer.valueOf(args[++i]);
+ } else if (arg.equals("--force-proguard-compatibility")) {
+ forceProguardCompatibility = true;
} else if (arg.equals("--output")) {
output = args[++i];
} else if (arg.equals("-outjars")) {
@@ -60,7 +66,7 @@
}
builder.add(currentLine.toString());
}
- return new CompatProguardOptions(builder.build(), output, minApi);
+ return new CompatProguardOptions(builder.build(), output, minApi, forceProguardCompatibility);
}
}
@@ -68,11 +74,12 @@
System.out.println("CompatProguard " + String.join(" ", args));
// Run R8 passing all the options from the command line as a Proguard configuration.
CompatProguardOptions options = CompatProguardOptions.parse(args);
- R8.run(R8Command.builder()
- .setOutputPath(Paths.get(options.output))
+ R8Command.Builder builder =
+ new CompatProguardCommandBuilder(options.forceProguardCompatibility);
+ builder.setOutputPath(Paths.get(options.output))
.addProguardConfiguration(options.proguardConfig)
- .setMinApiLevel(options.minApi)
- .build());
+ .setMinApiLevel(options.minApi);
+ R8.run(builder.build());
}
public static void main(String[] args) throws IOException {
diff --git a/src/main/java/com/android/tools/r8/compatproguard/CompatProguardCommandBuilder.java b/src/main/java/com/android/tools/r8/compatproguard/CompatProguardCommandBuilder.java
new file mode 100644
index 0000000..df2298c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/compatproguard/CompatProguardCommandBuilder.java
@@ -0,0 +1,13 @@
+// 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.compatproguard;
+
+import com.android.tools.r8.R8Command;
+
+public class CompatProguardCommandBuilder extends R8Command.Builder {
+ CompatProguardCommandBuilder(boolean forceProguardCompatibility) {
+ super(forceProguardCompatibility);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index 5de6538..e454d89 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -239,6 +239,19 @@
return true;
}
+ public boolean hasDefaultInitializer() {
+ return getDefaultInitializer() != null;
+ }
+
+ public DexEncodedMethod getDefaultInitializer() {
+ for (DexEncodedMethod method : directMethods()) {
+ if (method.isDefaultInitializer()) {
+ return method;
+ }
+ }
+ return null;
+ }
+
public boolean defaultValuesForStaticFieldsMayTriggerAllocation() {
return Arrays.stream(staticFields())
.anyMatch(field -> !field.staticValue.mayTriggerAllocation());
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 7766a94..a541f5a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -204,6 +204,10 @@
return accessFlags.isConstructor() && !accessFlags.isStatic();
}
+ public boolean isDefaultInitializer() {
+ return isInstanceInitializer() && method.proto.parameters.isEmpty();
+ }
+
public boolean isClassInitializer() {
return accessFlags.isConstructor() && accessFlags.isStatic();
}
diff --git a/src/main/java/com/android/tools/r8/graph/UseRegistry.java b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
index 78b2f56..f6736fa 100644
--- a/src/main/java/com/android/tools/r8/graph/UseRegistry.java
+++ b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
@@ -65,4 +65,8 @@
throw new AssertionError();
}
}
+
+ public boolean registerConstClass(DexType type) {
+ return registerTypeReference(type);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/jar/JarRegisterEffectsVisitor.java b/src/main/java/com/android/tools/r8/jar/JarRegisterEffectsVisitor.java
index c718e91..e1599b9 100644
--- a/src/main/java/com/android/tools/r8/jar/JarRegisterEffectsVisitor.java
+++ b/src/main/java/com/android/tools/r8/jar/JarRegisterEffectsVisitor.java
@@ -48,7 +48,7 @@
@Override
public void visitLdcInsn(Object cst) {
if (cst instanceof Type) {
- registry.registerTypeReference(application.getType((Type) cst));
+ registry.registerConstClass(application.getType((Type) cst));
} else if (cst instanceof Handle) {
registerMethodHandleType((Handle) cst);
}
diff --git a/src/main/java/com/android/tools/r8/naming/MemberNaming.java b/src/main/java/com/android/tools/r8/naming/MemberNaming.java
index 1537f05..762bdb6 100644
--- a/src/main/java/com/android/tools/r8/naming/MemberNaming.java
+++ b/src/main/java/com/android/tools/r8/naming/MemberNaming.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.naming;
+import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
@@ -280,6 +281,10 @@
method.proto.returnType.toSourceString(), paramNames);
}
+ public static MethodSignature initializer(String[] parameters) {
+ return new MethodSignature(Constants.INSTANCE_INITIALIZER_NAME, "void", parameters);
+ }
+
@Override
Signature asRenamed(String renamedName) {
return new MethodSignature(renamedName, type, parameters);
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 ff8adee..358c1d3 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -28,6 +28,7 @@
import com.android.tools.r8.graph.PresortedComparable;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
+import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.MethodSignatureEquivalence;
import com.android.tools.r8.utils.Timing;
import com.google.common.base.Equivalence.Wrapper;
@@ -69,6 +70,7 @@
public class Enqueuer {
private final AppInfoWithSubtyping appInfo;
+ private final InternalOptions options;
private RootSet rootSet;
private Map<DexType, Set<DexMethod>> virtualInvokes = Maps.newIdentityHashMap();
@@ -156,8 +158,9 @@
*/
private final Map<DexType, Set<DexAnnotation>> deferredAnnotations = new IdentityHashMap<>();
- public Enqueuer(AppInfoWithSubtyping appInfo) {
+ public Enqueuer(AppInfoWithSubtyping appInfo, InternalOptions options) {
this.appInfo = appInfo;
+ this.options = options;
}
public void addExtension(SemanticsProvider extension) {
@@ -332,6 +335,30 @@
}
return false;
}
+
+ @Override
+ public boolean registerConstClass(DexType type) {
+ boolean result = registerTypeReference(type);
+ // For Proguard compatibility mark default initializer for live type as live.
+ if (options.forceProguardCompatibility) {
+ if (type.isArrayType()) {
+ return result;
+ }
+ assert type.isClassType();
+ DexClass holder = appInfo.definitionFor(type);
+ if (holder == null) {
+ // Don't call reportMissingClass(type) here. That already happened in the call to
+ // registerTypeReference(type).
+ return result;
+ }
+ if (holder.hasDefaultInitializer()) {
+ registerNewInstance(type);
+ DexEncodedMethod init = holder.getDefaultInitializer();
+ markDirectStaticOrConstructorMethodAsLive(init, KeepReason.reachableFromLiveType(type));
+ }
+ }
+ return result;
+ }
}
//
diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
index 947abe3..10716bd 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -76,7 +76,7 @@
} else {
newClasses.add(clazz);
if (!appInfo.instantiatedTypes.contains(clazz.type) &&
- (!options.debugKeepRules || !hasDefaultConstructor(clazz))) {
+ (!options.debugKeepRules || !clazz.hasDefaultInitializer())) {
// The class is only needed as a type but never instantiated. Make it abstract to reflect
// this.
if (clazz.accessFlags.isFinal()) {
@@ -102,15 +102,6 @@
return newClasses;
}
- private boolean hasDefaultConstructor(DexProgramClass clazz) {
- for (DexEncodedMethod method : clazz.directMethods()) {
- if (isDefaultConstructor(method)) {
- return true;
- }
- }
- return false;
- }
-
private <S extends PresortedComparable<S>, T extends KeyedDexItem<S>> int firstUnreachableIndex(
T[] items, Set<S> live) {
for (int i = 0; i < items.length; i++) {
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 68a6502..9f883bf 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -103,6 +103,8 @@
public String printCfgFile;
public Path printMainDexListFile;
public boolean ignoreMissingClasses = false;
+ // EXPERIMENTAL flag to get behaviour as close to Proguard as possible.
+ public boolean forceProguardCompatibility = false;
public boolean skipMinification = false;
public boolean disableAssertions = true;
public boolean debugKeepRules = false;
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index e46ff48..984d112 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -18,6 +18,7 @@
import com.android.tools.r8.utils.OutputMode;
import com.android.tools.r8.utils.Timing;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
import com.google.common.io.ByteStreams;
import java.io.File;
import java.io.FileInputStream;
@@ -92,6 +93,20 @@
}
/**
+ * Get the class name generated by javac.
+ */
+ protected String getJavacGeneratedClassName(Class clazz) {
+ List<String> parts = Lists.newArrayList(clazz.getCanonicalName().split("\\."));
+ Class enclosing = clazz;
+ while (enclosing.getEnclosingClass() != null) {
+ parts.set(parts.size() - 2, parts.get(parts.size() - 2) + "$" + parts.get(parts.size() - 1));
+ parts.remove(parts.size() - 1);
+ enclosing = clazz.getEnclosingClass();
+ }
+ return String.join(".", parts);
+ }
+
+ /**
* Compile an application with D8.
*/
protected AndroidApp compileWithD8(AndroidApp app)
diff --git a/src/test/java/com/android/tools/r8/naming/NamingTestBase.java b/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
index 3d65e0d..53f0793 100644
--- a/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
+++ b/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
@@ -73,7 +73,7 @@
RootSet rootSet = new RootSetBuilder(program, appInfo, configuration.getRules(), options)
.run(ThreadUtils.getExecutorService(options));
- Enqueuer enqueuer = new Enqueuer(appInfo);
+ Enqueuer enqueuer = new Enqueuer(appInfo, options);
appInfo = enqueuer.traceApplication(rootSet, timing);
return new Minifier(appInfo.withLiveness(), rootSet, options).run(timing);
}
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
new file mode 100644
index 0000000..4d59f63
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
@@ -0,0 +1,52 @@
+// 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;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import org.junit.Test;
+
+public class ForceProguardCompatibilityTest extends TestBase {
+ private void test(Class mainClass, Class mentionedClass, boolean arrayClass,
+ boolean forceProguardCompatibility)
+ throws Exception {
+ String proguardConfig = keepMainProguardConfiguration(mainClass, true, false);
+ DexInspector inspector = new DexInspector(
+ compileWithR8(
+ ImmutableList.of(mainClass, mentionedClass),
+ proguardConfig,
+ options -> options.forceProguardCompatibility = forceProguardCompatibility));
+ assertTrue(inspector.clazz(mainClass.getCanonicalName()).isPresent());
+ ClassSubject clazz = inspector.clazz(getJavacGeneratedClassName(mentionedClass));
+ assertTrue(clazz.isPresent());
+ if (arrayClass) {
+ MethodSubject defaultInitializer = clazz.method(MethodSignature.initializer(new String[]{}));
+ assertFalse(defaultInitializer.isPresent());
+ } else {
+ MethodSubject defaultInitializer = clazz.method(MethodSignature.initializer(new String[]{}));
+ assertEquals(forceProguardCompatibility, defaultInitializer.isPresent());
+ }
+ }
+
+ @Test
+ public void testKeepDefaultInitializer() throws Exception {
+ test(TestMain.class, TestMain.MentionedClass.class, false, true);
+ test(TestMain.class, TestMain.MentionedClass.class, false, false);
+ }
+
+ @Test
+ public void testKeepDefaultInitializerArrayType() throws Exception {
+ test(TestMainArrayType.class, TestMainArrayType.MentionedClass.class, true, true);
+ test(TestMainArrayType.class, TestMainArrayType.MentionedClass.class, true, false);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/TestMain.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/TestMain.java
new file mode 100644
index 0000000..94a9d0a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/TestMain.java
@@ -0,0 +1,17 @@
+// 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 TestMain {
+
+ public static class MentionedClass {
+ public MentionedClass() {
+ }
+ }
+
+ public static void main(String[] args) {
+ System.out.println(MentionedClass.class.getCanonicalName());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/TestMainArrayType.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/TestMainArrayType.java
new file mode 100644
index 0000000..8b1acaf
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/TestMainArrayType.java
@@ -0,0 +1,17 @@
+// 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 TestMainArrayType {
+
+ public static class MentionedClass {
+ public MentionedClass() {
+ }
+ }
+
+ public static void main(String[] args) {
+ System.out.println(MentionedClass[].class.getCanonicalName());
+ }
+}