Merge "Build keep rules for the Proguard compatiblity actions"
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 8c01bc2..3ba3e5c 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -35,6 +35,7 @@
import com.android.tools.r8.shaking.Enqueuer;
import com.android.tools.r8.shaking.MainDexListBuilder;
import com.android.tools.r8.shaking.ProguardClassFilter;
+import com.android.tools.r8.shaking.ProguardKeepRule;
import com.android.tools.r8.shaking.ReasonPrinter;
import com.android.tools.r8.shaking.RootSetBuilder;
import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
@@ -47,6 +48,7 @@
import com.android.tools.r8.utils.AndroidAppOutputSink;
import com.android.tools.r8.utils.CfgPrinter;
import com.android.tools.r8.utils.CompilationFailedException;
+import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.IOExceptionDiagnostic;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
@@ -56,15 +58,18 @@
import com.android.tools.r8.utils.Timing;
import com.android.tools.r8.utils.VersionProperties;
import com.google.common.io.ByteStreams;
+import com.google.common.io.Closer;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.NoSuchFileException;
import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ExecutionException;
@@ -198,6 +203,25 @@
new AbstractMethodRemover(appInfo).run();
new AnnotationRemover(appInfo.withLiveness(), options).run();
}
+
+ // TODO(69445518): This is still work in progress, and this file writing is currently used
+ // for testing.
+ if (options.forceProguardCompatibility
+ && options.proguardCompatibilityRulesOutput != null) {
+ try (Closer closer = Closer.create()) {
+ OutputStream outputStream =
+ FileUtils.openPath(
+ closer,
+ options.proguardCompatibilityRulesOutput,
+ StandardOpenOption.CREATE,
+ StandardOpenOption.TRUNCATE_EXISTING,
+ StandardOpenOption.WRITE);
+ PrintStream ps = new PrintStream(outputStream);
+ for (ProguardKeepRule rule : appInfo.withLiveness().getProguardCompatibilityRules()) {
+ ps.println(rule);
+ }
+ }
+ }
} finally {
timing.end();
}
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index fda5054..cf0d4ec 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -46,6 +46,7 @@
private boolean ignoreMissingClasses = false;
private boolean forceProguardCompatibility = false;
private Path proguardMapOutput = null;
+ protected Path proguardCompatibilityRulesOutput = null;
private Builder() {
setMode(CompilationMode.RELEASE);
@@ -282,7 +283,8 @@
ignoreMissingClasses,
forceProguardCompatibility,
ignoreMissingClassesWhenNotShrinking,
- proguardMapOutput);
+ proguardMapOutput,
+ proguardCompatibilityRulesOutput);
failIfPendingErrors();
@@ -329,6 +331,7 @@
private final boolean forceProguardCompatibility;
private final boolean ignoreMissingClassesWhenNotShrinking;
private final Path proguardMapOutput;
+ private final Path proguardCompatibilityRulesOutput;
public static Builder builder() {
return new Builder();
@@ -453,7 +456,8 @@
boolean ignoreMissingClasses,
boolean forceProguardCompatibility,
boolean ignoreMissingClassesWhenNotShrinking,
- Path proguardMapOutput) {
+ Path proguardMapOutput,
+ Path proguardCompatibilityRulesOutput) {
super(inputApp, outputPath, outputMode, mode, minApiLevel, reporter,
enableDesugaring);
assert proguardConfiguration != null;
@@ -469,6 +473,7 @@
this.forceProguardCompatibility = forceProguardCompatibility;
this.ignoreMissingClassesWhenNotShrinking = ignoreMissingClassesWhenNotShrinking;
this.proguardMapOutput = proguardMapOutput;
+ this.proguardCompatibilityRulesOutput = proguardCompatibilityRulesOutput;
}
private R8Command(boolean printHelp, boolean printVersion) {
@@ -483,6 +488,7 @@
forceProguardCompatibility = false;
ignoreMissingClassesWhenNotShrinking = false;
proguardMapOutput = null;
+ proguardCompatibilityRulesOutput = null;
}
public boolean useTreeShaking() {
return useTreeShaking;
@@ -537,6 +543,7 @@
internal.inlineAccessors = false;
}
internal.proguardMapOutput = proguardMapOutput;
+ internal.proguardCompatibilityRulesOutput = proguardCompatibilityRulesOutput;
// EXPERIMENTAL flags.
assert !internal.forceProguardCompatibility;
diff --git a/src/main/java/com/android/tools/r8/compatproguard/CompatProguardCommandBuilder.java b/src/main/java/com/android/tools/r8/compatproguard/CompatProguardCommandBuilder.java
index b7faf07..bc44ff4 100644
--- a/src/main/java/com/android/tools/r8/compatproguard/CompatProguardCommandBuilder.java
+++ b/src/main/java/com/android/tools/r8/compatproguard/CompatProguardCommandBuilder.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.R8Command;
import com.android.tools.r8.origin.EmbeddedOrigin;
import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
import java.util.List;
public class CompatProguardCommandBuilder extends R8Command.Builder {
@@ -24,4 +25,8 @@
setEnableDesugaring(false);
addProguardConfiguration(CLASS_FOR_NAME, EmbeddedOrigin.INSTANCE);
}
+
+ public void setProguardCompatibilityRulesOutput(Path path) {
+ proguardCompatibilityRulesOutput = path;
+ }
}
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 29e0c44..ad00c86 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -139,6 +139,11 @@
private Queue<Action> workList = Queues.newArrayDeque();
/**
+ * A queue of items that have been added to try to keep Proguard compatibility.
+ */
+ private Queue<Action> proguardCompatibilityWorkList = Queues.newArrayDeque();
+
+ /**
* A cache for DexMethod that have been marked reachable.
*/
private Set<DexMethod> virtualTargetsMarkedAsReachable = Sets.newIdentityHashSet();
@@ -160,6 +165,11 @@
*/
private final Map<DexType, Set<DexAnnotation>> deferredAnnotations = new IdentityHashMap<>();
+ /**
+ * Set of keep rules generated for Proguard compatibility in Proguard compatibility mode.
+ */
+ private final Set<ProguardKeepRule> proguardCompatibilityRules = Sets.newHashSet();
+
public Enqueuer(AppInfoWithSubtyping appInfo, InternalOptions options) {
this.appInfo = appInfo;
this.options = options;
@@ -368,14 +378,16 @@
// Add all dependent static members to the workqueue.
enqueueRootItems(rootSet.getDependentStaticMembers(type));
- // For Proguard compatibility mark default initializer for live type as live.
+ // For Proguard compatibility keep the default initializer for live types.
if (options.forceProguardCompatibility) {
- if (holder.hasDefaultInitializer()) {
+ if (holder.isProgramClass() && holder.hasDefaultInitializer()) {
DexEncodedMethod init = holder.getDefaultInitializer();
- // TODO(68246915): For now just register the default constructor as the source of
- // instantiation.
- markInstantiated(type, init);
- markDirectStaticOrConstructorMethodAsLive(init, KeepReason.reachableFromLiveType(type));
+ ProguardKeepRule rule =
+ ProguardConfigurationUtils.buildDefaultInitializerKeepRule(holder);
+ proguardCompatibilityWorkList.add(
+ Action.markInstantiated(holder, KeepReason.dueToProguardCompatibilityKeepRule(rule)));
+ proguardCompatibilityWorkList.add(
+ Action.markMethodLive(init, KeepReason.dueToProguardCompatibilityKeepRule(rule)));
}
}
}
@@ -452,6 +464,7 @@
if (!instantiatedTypes.add(clazz.type, reason)) {
return;
}
+ collectProguardCompatibilityRule(reason);
if (Log.ENABLED) {
Log.verbose(getClass(), "Class `%s` is instantiated, processing...", clazz);
}
@@ -815,44 +828,53 @@
this.rootSet = rootSet;
// Translate the result of root-set computation into enqueuer actions.
enqueueRootItems(rootSet.noShrinking);
- appInfo.libraryClasses().forEach(this::markAllVirtualMethodsReachable);
+ appInfo.libraryClasses().forEach(this::markAllLibraryVirtualMethodsReachable);
return trace(timing);
}
private AppInfoWithLiveness trace(Timing timing) {
timing.begin("Grow the tree.");
try {
- while (!workList.isEmpty()) {
- Action action = workList.poll();
- switch (action.kind) {
- case MARK_INSTANTIATED:
- processNewlyInstantiatedClass((DexClass) action.target, action.reason);
- break;
- case MARK_REACHABLE_FIELD:
- markInstanceFieldAsReachable((DexField) action.target, action.reason);
- break;
- case MARK_REACHABLE_VIRTUAL:
- markVirtualMethodAsReachable((DexMethod) action.target, false, action.reason);
- break;
- case MARK_REACHABLE_INTERFACE:
- markVirtualMethodAsReachable((DexMethod) action.target, true, action.reason);
- break;
- case MARK_REACHABLE_SUPER:
- markSuperMethodAsReachable((DexMethod) action.target,
- (DexEncodedMethod) action.context);
- break;
- case MARK_METHOD_KEPT:
- markMethodAsKept((DexEncodedMethod) action.target, action.reason);
- break;
- case MARK_FIELD_KEPT:
- markFieldAsKept((DexEncodedField) action.target, action.reason);
- break;
- case MARK_METHOD_LIVE:
- processNewlyLiveMethod(((DexEncodedMethod) action.target), action.reason);
- break;
- default:
- throw new IllegalArgumentException(action.kind.toString());
+ while (true) {
+ while (!workList.isEmpty()) {
+ Action action = workList.poll();
+ switch (action.kind) {
+ case MARK_INSTANTIATED:
+ processNewlyInstantiatedClass((DexClass) action.target, action.reason);
+ break;
+ case MARK_REACHABLE_FIELD:
+ markInstanceFieldAsReachable((DexField) action.target, action.reason);
+ break;
+ case MARK_REACHABLE_VIRTUAL:
+ markVirtualMethodAsReachable((DexMethod) action.target, false, action.reason);
+ break;
+ case MARK_REACHABLE_INTERFACE:
+ markVirtualMethodAsReachable((DexMethod) action.target, true, action.reason);
+ break;
+ case MARK_REACHABLE_SUPER:
+ markSuperMethodAsReachable((DexMethod) action.target,
+ (DexEncodedMethod) action.context);
+ break;
+ case MARK_METHOD_KEPT:
+ markMethodAsKept((DexEncodedMethod) action.target, action.reason);
+ break;
+ case MARK_FIELD_KEPT:
+ markFieldAsKept((DexEncodedField) action.target, action.reason);
+ break;
+ case MARK_METHOD_LIVE:
+ processNewlyLiveMethod(((DexEncodedMethod) action.target), action.reason);
+ break;
+ default:
+ throw new IllegalArgumentException(action.kind.toString());
+ }
}
+ // Continue fix-point processing while there are additional work items to ensure
+ // Proguard compatibility.
+ if (proguardCompatibilityWorkList.isEmpty()) {
+ break;
+ }
+ workList.addAll(proguardCompatibilityWorkList);
+ proguardCompatibilityWorkList.clear();
}
if (Log.ENABLED) {
Set<DexEncodedMethod> allLive = Sets.newIdentityHashSet();
@@ -911,7 +933,8 @@
}
}
- private void markAllVirtualMethodsReachable(DexClass clazz) {
+ private void markAllLibraryVirtualMethodsReachable(DexClass clazz) {
+ assert clazz.isLibraryClass();
if (Log.ENABLED) {
Log.verbose(getClass(), "Marking all methods of library class `%s` as reachable.",
clazz.type);
@@ -925,6 +948,7 @@
private void processNewlyLiveMethod(DexEncodedMethod method, KeepReason reason) {
if (liveMethods.add(method, reason)) {
+ collectProguardCompatibilityRule(reason);
DexClass holder = appInfo.definitionFor(method.method.holder);
assert holder != null;
if (holder.isLibraryClass()) {
@@ -966,6 +990,12 @@
}
}
+ private void collectProguardCompatibilityRule(KeepReason reason) {
+ if (reason.isDueToProguardCompatibility()) {
+ proguardCompatibilityRules.add(reason.getProguardKeepRule());
+ }
+ }
+
private Set<DexField> collectFields(Map<DexType, Set<DexField>> map) {
return map.values().stream().flatMap(Collection::stream)
.collect(Collectors.toCollection(Sets::newIdentityHashSet));
@@ -1195,6 +1225,11 @@
*/
final Set<DexType> prunedTypes;
+ /**
+ * Set of keep rules generated for Proguard compatibility in Proguard compatibility mode.
+ */
+ final Set<ProguardKeepRule> proguardCompatibilityRules;
+
private AppInfoWithLiveness(AppInfoWithSubtyping appInfo, Enqueuer enqueuer) {
super(appInfo);
this.liveTypes = ImmutableSortedSet.copyOf(
@@ -1222,6 +1257,7 @@
this.identifierNameStrings = enqueuer.rootSet.identifierNameStrings;
this.extensions = enqueuer.extensionsState;
this.prunedTypes = Collections.emptySet();
+ this.proguardCompatibilityRules = ImmutableSet.copyOf(enqueuer.proguardCompatibilityRules);
assert Sets.intersection(instanceFieldReads, staticFieldReads).size() == 0;
assert Sets.intersection(instanceFieldWrites, staticFieldWrites).size() == 0;
}
@@ -1254,6 +1290,7 @@
this.alwaysInline = previous.alwaysInline;
this.identifierNameStrings = previous.identifierNameStrings;
this.prunedTypes = mergeSets(previous.prunedTypes, removedClasses);
+ this.proguardCompatibilityRules = previous.proguardCompatibilityRules;
assert Sets.intersection(instanceFieldReads, staticFieldReads).size() == 0;
assert Sets.intersection(instanceFieldWrites, staticFieldWrites).size() == 0;
}
@@ -1280,6 +1317,7 @@
this.directInvokes = rewriteItems(previous.directInvokes, lense::lookupMethod);
this.staticInvokes = rewriteItems(previous.staticInvokes, lense::lookupMethod);
this.prunedTypes = rewriteItems(previous.prunedTypes, lense::lookupType);
+ this.proguardCompatibilityRules = previous.proguardCompatibilityRules;
// TODO(herhut): Migrate these to Descriptors, as well.
assert assertNotModifiedByLense(previous.noSideEffects.keySet(), lense);
this.noSideEffects = previous.noSideEffects;
@@ -1444,6 +1482,9 @@
return pinnedItems;
}
+ public Set<ProguardKeepRule> getProguardCompatibilityRules() {
+ return proguardCompatibilityRules;
+ }
/**
* Returns a copy of this AppInfoWithLiveness where the set of classes is pruned using the
* given DexApplication object.
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 a6ce411..7308181 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepReason.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepReason.java
@@ -15,6 +15,10 @@
return new DueToKeepRule(rule);
}
+ static KeepReason dueToProguardCompatibilityKeepRule(ProguardKeepRule rule) {
+ return new DueToProguardCompatibilityKeepRule(rule);
+ }
+
static KeepReason instantiatedIn(DexEncodedMethod method) {
return new InstatiatedIn(method);
}
@@ -45,15 +49,28 @@
public abstract void print(ReasonFormatter formatter);
+ public boolean isDueToProguardCompatibility() {
+ return false;
+ }
+
+ public ProguardKeepRule getProguardKeepRule() {
+ return null;
+ }
+
private static class DueToKeepRule extends KeepReason {
- private final ProguardKeepRule keepRule;
+ final ProguardKeepRule keepRule;
private DueToKeepRule(ProguardKeepRule keepRule) {
this.keepRule = keepRule;
}
@Override
+ public ProguardKeepRule getProguardKeepRule() {
+ return keepRule;
+ }
+
+ @Override
public void print(ReasonFormatter formatter) {
formatter.addReason("referenced in keep rule:");
formatter.addMessage(" " + keepRule + " {");
@@ -69,6 +86,17 @@
}
}
+ private static class DueToProguardCompatibilityKeepRule extends DueToKeepRule {
+ private DueToProguardCompatibilityKeepRule(ProguardKeepRule keepRule) {
+ super(keepRule);
+ }
+
+ @Override
+ public boolean isDueToProguardCompatibility() {
+ return true;
+ }
+ }
+
private abstract static class BasedOnOtherMethod extends KeepReason {
private final DexEncodedMethod method;
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardClassSpecification.java b/src/main/java/com/android/tools/r8/shaking/ProguardClassSpecification.java
index 8e981a7..efe294a 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardClassSpecification.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardClassSpecification.java
@@ -251,9 +251,9 @@
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
- StringUtils.appendNonEmpty(builder, " @", classAnnotation, null);
- StringUtils.appendNonEmpty(builder, " ", classAccessFlags, null);
- StringUtils.appendNonEmpty(builder, " !", negatedClassAccessFlags.toString().replace(" ", " !"),
+ StringUtils.appendNonEmpty(builder, "@", classAnnotation, null);
+ StringUtils.appendNonEmpty(builder, "", classAccessFlags, null);
+ StringUtils.appendNonEmpty(builder, "!", negatedClassAccessFlags.toString().replace(" ", " !"),
null);
if (builder.length() > 0) {
builder.append(' ');
@@ -262,8 +262,8 @@
builder.append(' ');
classNames.writeTo(builder);
if (hasInheritanceClassName()) {
- builder.append(inheritanceIsExtends ? " extends" : " implements");
- StringUtils.appendNonEmpty(builder, " @", inheritanceAnnotation, null);
+ builder.append(inheritanceIsExtends ? "extends" : "implements");
+ StringUtils.appendNonEmpty(builder, "@", inheritanceAnnotation, null);
builder.append(' ');
builder.append(inheritanceClassName);
}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
index f8aa3e2..1f80bb1 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
@@ -185,22 +185,7 @@
this.overloadAggressively = overloadAggressively;
}
- public ProguardConfiguration build() throws CompilationException {
- boolean rulesWasEmpty = rules.isEmpty();
- if (rulesWasEmpty) {
- setObfuscating(false);
- setShrinking(false);
- addRule(ProguardKeepRule.defaultKeepAllRule());
- }
-
- ProguardKeepAttributes keepAttributes;
- if (keepAttributePatterns.isEmpty()
- && (rulesWasEmpty || (forceProguardCompatibility && !isObfuscating()))) {
- keepAttributes = ProguardKeepAttributes.fromPatterns(ProguardKeepAttributes.KEEP_ALL);
- } else {
- keepAttributes = ProguardKeepAttributes.fromPatterns(keepAttributePatterns);
- }
-
+ ProguardConfiguration buildRaw() throws CompilationException {
return new ProguardConfiguration(
dexItemFactory,
injars,
@@ -219,7 +204,7 @@
applyMappingFile,
verbose,
renameSourceFileAttribute,
- keepAttributes,
+ ProguardKeepAttributes.fromPatterns(keepAttributePatterns),
dontWarnPatterns.build(),
rules,
printSeeds,
@@ -232,6 +217,21 @@
keepParameterNames,
adaptClassStrings.build());
}
+
+ public ProguardConfiguration build() throws CompilationException {
+ boolean rulesWasEmpty = rules.isEmpty();
+ if (rules.isEmpty()) {
+ setObfuscating(false);
+ setShrinking(false);
+ addRule(ProguardKeepRule.defaultKeepAllRule());
+ }
+ if (keepAttributePatterns.isEmpty()
+ && (rulesWasEmpty || (forceProguardCompatibility && !isObfuscating()))) {
+ keepAttributePatterns.addAll(ProguardKeepAttributes.KEEP_ALL);
+ }
+
+ return buildRaw();
+ }
}
private final DexItemFactory dexItemFactory;
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index 40747d7..4574b26 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -85,7 +85,7 @@
return configurationBuilder;
}
- public ProguardConfiguration getConfig()
+ private void validate()
throws ProguardRuleParserException, CompilationException {
if (configurationBuilder.isKeepParameterNames()
&& configurationBuilder.isObfuscating()) {
@@ -93,10 +93,27 @@
// are not.
throw new ProguardRuleParserException("-keepparameternames is not supported");
}
+ }
+ /**
+ * Returns the Proguard configuration with default rules derived from empty rules added.
+ */
+ public ProguardConfiguration getConfig()
+ throws ProguardRuleParserException, CompilationException {
+ validate();
return configurationBuilder.build();
}
+ /**
+ * Returns the Proguard configuration from exactly the rules parsed, without any
+ * defaults derived from empty rules.
+ */
+ public ProguardConfiguration getConfigRawForTesting()
+ throws ProguardRuleParserException, CompilationException {
+ validate();
+ return configurationBuilder.buildRaw();
+ }
+
public void parse(Path path) throws ProguardRuleParserException, IOException {
parse(ImmutableList.of(new ProguardConfigurationSourceFile(path)));
}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
index 223307c..5fd62df 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
@@ -34,7 +34,7 @@
@Override
public String toString() {
- StringBuilder builder = new StringBuilder();
+ StringBuilder builder = new StringBuilder("-");
builder.append(typeString());
StringUtils.appendNonEmpty(builder, ",", modifierString(), null);
builder.append(' ');
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationUtils.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationUtils.java
new file mode 100644
index 0000000..e00d36a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationUtils.java
@@ -0,0 +1,28 @@
+// 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;
+
+import com.android.tools.r8.graph.DexClass;
+import com.google.common.collect.ImmutableList;
+
+public class ProguardConfigurationUtils {
+ public static ProguardKeepRule buildDefaultInitializerKeepRule(DexClass clazz) {
+ ProguardKeepRule.Builder builder = ProguardKeepRule.builder();
+ builder.setType(ProguardKeepRuleType.KEEP);
+ builder.getModifiersBuilder().allowsObfuscation = true;
+ builder.getModifiersBuilder().allowsOptimization = true;
+ builder.getClassAccessFlags().setPublic();
+ builder.setClassType(ProguardClassType.CLASS);
+ 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());
+ return builder.build();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardTypeMatcher.java b/src/main/java/com/android/tools/r8/shaking/ProguardTypeMatcher.java
index ef149f7..809278f 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardTypeMatcher.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardTypeMatcher.java
@@ -57,6 +57,10 @@
}
}
+ public static ProguardTypeMatcher create(DexType type) {
+ return new MatchSpecificType(type);
+ }
+
public static ProguardTypeMatcher defaultAllMatcher() {
return MatchAllTypes.MATCH_ALL_TYPES;
}
diff --git a/src/main/java/com/android/tools/r8/utils/FileUtils.java b/src/main/java/com/android/tools/r8/utils/FileUtils.java
index 2f08db1..85bb29e 100644
--- a/src/main/java/com/android/tools/r8/utils/FileUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/FileUtils.java
@@ -99,6 +99,15 @@
return path;
}
+ public static OutputStream openPath(
+ Closer closer,
+ Path file,
+ OpenOption... openOptions)
+ throws IOException {
+ assert file != null;
+ return openPathWithDefault(closer, file, null, openOptions);
+ }
+
public static OutputStream openPathWithDefault(
Closer closer,
Path file,
@@ -107,6 +116,7 @@
throws IOException {
OutputStream mapOut;
if (file == null) {
+ assert defaultOutput != null;
mapOut = defaultOutput;
} else {
mapOut = Files.newOutputStream(file, openOptions);
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 0840393..76ad1d6 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -173,6 +173,8 @@
public Path proguardMapOutput = null;
+ public Path proguardCompatibilityRulesOutput = null;
+
public void warningMissingEnclosingMember(DexType clazz, Origin origin, int version) {
if (missingEnclosingMembers == null) {
missingEnclosingMembers = new HashMap<>();
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 8214bc7..62ca811 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
@@ -11,7 +11,13 @@
import com.android.tools.r8.TestBase;
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.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.shaking.ProguardClassNameList;
+import com.android.tools.r8.shaking.ProguardConfiguration;
+import com.android.tools.r8.shaking.ProguardConfigurationParser;
+import com.android.tools.r8.shaking.ProguardMemberRule;
+import com.android.tools.r8.shaking.ProguardMemberType;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.DexInspector;
import com.android.tools.r8.utils.DexInspector.ClassSubject;
@@ -101,19 +107,41 @@
private void runDefaultConstructorTest(boolean forceProguardCompatibility,
Class<?> testClass, boolean hasDefaultConstructor) throws Exception {
- R8Command.Builder builder = new CompatProguardCommandBuilder(forceProguardCompatibility, false);
+ CompatProguardCommandBuilder builder =
+ new CompatProguardCommandBuilder(forceProguardCompatibility, false);
builder.addProgramFiles(ToolHelper.getClassFileForTestClass(testClass));
List<String> proguardConfig = ImmutableList.of(
"-keep class " + testClass.getCanonicalName() + " {",
" public void method();",
"}");
builder.addProguardConfiguration(proguardConfig, Origin.unknown());
+ Path proguardCompatibilityRules = temp.newFile().toPath();
+ builder.setProguardCompatibilityRulesOutput(proguardCompatibilityRules);
+
DexInspector inspector = new DexInspector(ToolHelper.runR8(builder.build()));
ClassSubject clazz = inspector.clazz(getJavacGeneratedClassName(testClass));
assertTrue(clazz.isPresent());
assertEquals(forceProguardCompatibility && hasDefaultConstructor,
clazz.init(ImmutableList.of()).isPresent());
+ // Check the Proguard compatibility rules generated.
+ ProguardConfigurationParser parser =
+ new ProguardConfigurationParser(new DexItemFactory(), null);
+ parser.parse(proguardCompatibilityRules);
+ ProguardConfiguration configuration = parser.getConfigRawForTesting();
+ if (forceProguardCompatibility && hasDefaultConstructor) {
+ assertEquals(1, configuration.getRules().size());
+ ProguardClassNameList classNames = configuration.getRules().get(0).getClassNames();
+ assertEquals(1, classNames.size());
+ assertEquals(testClass.getCanonicalName(),
+ classNames.asSpecificDexTypes().get(0).toSourceString());
+ Set<ProguardMemberRule> memberRules = configuration.getRules().get(0).getMemberRules();
+ 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();
@@ -133,7 +161,8 @@
public void testCheckCast(boolean forceProguardCompatibility, Class mainClass,
Class instantiatedClass, boolean containsCheckCast)
throws Exception {
- R8Command.Builder builder = new CompatProguardCommandBuilder(forceProguardCompatibility, false);
+ R8Command.Builder builder =
+ new CompatProguardCommandBuilder(forceProguardCompatibility, false);
builder.addProgramFiles(ToolHelper.getClassFileForTestClass(mainClass));
builder.addProgramFiles(ToolHelper.getClassFileForTestClass(instantiatedClass));
List<String> proguardConfig = ImmutableList.of(