Build keep rules for the Proguard compatiblity actions
Instead of enqueuing items kept due to Proguard compatibility mode
immediately, postpone enqueuing until all explicit rules have been
processed.
For the items kept due to Proguard compatibility generate explicit keep
rules that will match what is kept.
Currently this only covers keeping the default constructor for kept types.
Bug: 69445518
Change-Id: Ib228ab37f9fdff31dfc70e74e440608c26e40e4b
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(