Preliminary support for -whyareyounotobfuscating
Bug: b/450540158
Change-Id: Iee9a54cf4229a5908cc0da5814cfad1b63935e93
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
index 482d5a1..0ae1c43 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
@@ -37,6 +37,8 @@
import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult;
import com.android.tools.r8.ir.desugar.ServiceLoaderSourceCode;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.KeepInfo;
+import com.android.tools.r8.shaking.MinimumKeepInfoCollection;
import com.android.tools.r8.utils.ConsumerUtils;
import com.android.tools.r8.utils.DominatorChecker;
import com.android.tools.r8.utils.ListUtils;
@@ -47,6 +49,7 @@
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
+import java.util.function.Predicate;
/**
* ServiceLoaderRewriter will attempt to rewrite calls on the form of: ServiceLoader.load(X.class,
@@ -107,9 +110,15 @@
}
private boolean shouldReportWhyAreYouNotInliningServiceLoaderLoad() {
- AppInfoWithLiveness appInfo = appView().appInfo();
- return appInfo.isWhyAreYouNotInliningMethod(serviceLoaderMethods.load)
- || appInfo.isWhyAreYouNotInliningMethod(serviceLoaderMethods.loadWithClassLoader);
+ MinimumKeepInfoCollection keepInfo =
+ appView
+ .rootSet()
+ .getDependentMinimumKeepInfo()
+ .getUnconditionalMinimumKeepInfoOrDefault(MinimumKeepInfoCollection.empty());
+ Predicate<KeepInfo.Joiner<?, ?, ?>> test =
+ joiner -> joiner.asMethodJoiner().isWhyAreYouNotInliningEnabled();
+ return keepInfo.hasMinimumKeepInfoThatMatches(serviceLoaderMethods.load, test)
+ || keepInfo.hasMinimumKeepInfoThatMatches(serviceLoaderMethods.loadWithClassLoader, test);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java
index e70e49e..eeb46c7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.ir.optimize.Inliner;
import com.android.tools.r8.ir.optimize.Inliner.Reason;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.collections.ProgramMethodSet;
import java.util.Set;
@@ -21,7 +22,7 @@
public static WhyAreYouNotInliningReporter createFor(
ProgramMethod callee, AppView<AppInfoWithLiveness> appView, ProgramMethod context) {
- if (appView.appInfo().isWhyAreYouNotInliningMethod(callee.getReference())) {
+ if (appView.getKeepInfo(callee).isWhyAreYouNotInliningEnabled()) {
return new WhyAreYouNotInliningReporterImpl(appView, callee, context);
}
return NopWhyAreYouNotInliningReporter.getInstance();
@@ -32,7 +33,9 @@
InvokeMethod invoke,
AppView<AppInfoWithLiveness> appView,
ProgramMethod context) {
- if (appView.appInfo().hasNoWhyAreYouNotInliningMethods()) {
+ InternalOptions options = appView.options();
+ if (!options.hasProguardConfiguration()
+ || !options.getProguardConfiguration().hasWhyAreYouNotInliningRule()) {
return;
}
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index 25f2a95..cb5e4b8 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -147,8 +147,6 @@
public final Map<DexReference, ProguardMemberRule> mayHaveSideEffects;
/** All methods that should be inlined if possible due to a configuration directive. */
private final Set<DexMethod> alwaysInline;
- /** Items for which to print inlining decisions for (testing only). */
- private final Set<DexMethod> whyAreYouNotInlining;
/** All methods that must be reprocessed (testing only). */
private final Set<DexMethod> reprocess;
/** All types that should be inlined if possible due to a configuration directive. */
@@ -210,7 +208,6 @@
KeepInfoCollection keepInfo,
Map<DexReference, ProguardMemberRule> mayHaveSideEffects,
Set<DexMethod> alwaysInline,
- Set<DexMethod> whyAreYouNotInlining,
Set<DexMethod> reprocess,
PredicateSet<DexType> alwaysClassInline,
IdentifierNameStringCollection identifierNameStrings,
@@ -236,7 +233,6 @@
this.mayHaveSideEffects = mayHaveSideEffects;
this.callSites = callSites;
this.alwaysInline = alwaysInline;
- this.whyAreYouNotInlining = whyAreYouNotInlining;
this.reprocess = reprocess;
this.alwaysClassInline = alwaysClassInline;
this.identifierNameStrings = identifierNameStrings;
@@ -270,7 +266,6 @@
previous.keepInfo,
previous.mayHaveSideEffects,
previous.alwaysInline,
- previous.whyAreYouNotInlining,
previous.reprocess,
previous.alwaysClassInline,
previous.identifierNameStrings,
@@ -305,7 +300,6 @@
extendPinnedItems(previous, prunedItems.getAdditionalPinnedItems()),
previous.mayHaveSideEffects,
pruneMethods(previous.alwaysInline, prunedItems, tasks),
- pruneMethods(previous.whyAreYouNotInlining, prunedItems, tasks),
pruneMethods(previous.reprocess, prunedItems, tasks),
previous.alwaysClassInline,
previous.identifierNameStrings.prune(prunedItems, tasks),
@@ -431,7 +425,6 @@
keepInfo,
mayHaveSideEffects,
alwaysInline,
- whyAreYouNotInlining,
reprocess,
alwaysClassInline,
identifierNameStrings,
@@ -502,7 +495,6 @@
this.mayHaveSideEffects = previous.mayHaveSideEffects;
this.callSites = previous.callSites;
this.alwaysInline = previous.alwaysInline;
- this.whyAreYouNotInlining = previous.whyAreYouNotInlining;
this.reprocess = previous.reprocess;
this.alwaysClassInline = previous.alwaysClassInline;
this.identifierNameStrings = previous.identifierNameStrings;
@@ -638,14 +630,6 @@
return alwaysInline.contains(method);
}
- public boolean isWhyAreYouNotInliningMethod(DexMethod method) {
- return whyAreYouNotInlining.contains(method);
- }
-
- public boolean hasNoWhyAreYouNotInliningMethods() {
- return whyAreYouNotInlining.isEmpty();
- }
-
public Set<DexMethod> getReprocessMethods() {
return reprocess;
}
@@ -999,7 +983,6 @@
// Take any rule in case of collisions.
lens.rewriteReferenceKeys(mayHaveSideEffects, (reference, rules) -> ListUtils.first(rules)),
lens.rewriteReferences(alwaysInline),
- lens.rewriteReferences(whyAreYouNotInlining),
lens.rewriteReferences(reprocess),
alwaysClassInline.rewriteItems(lens::lookupType),
identifierNameStrings.rewrittenWithLens(lens),
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 e5cb200..4482352 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -506,7 +506,7 @@
this.options = options;
this.taskCollection =
new EnqueuerTaskCollection(this, options.getThreadingModule(), executorService);
- this.keepInfo = new MutableKeepInfoCollection(options);
+ this.keepInfo = new MutableKeepInfoCollection(appView, this);
this.reflectiveIdentification = new EnqueuerReflectiveIdentification(appView, this);
this.useRegistryFactory = createUseRegistryFactory();
this.worklist = EnqueuerWorklist.createWorklist(this, options.getThreadingModule());
@@ -4746,7 +4746,6 @@
getKeepInfo(),
rootSet.mayHaveSideEffects,
amendWithCompanionMethods(rootSet.alwaysInline),
- amendWithCompanionMethods(rootSet.whyAreYouNotInlining),
amendWithCompanionMethods(rootSet.reprocess),
rootSet.alwaysClassInline,
new IdentifierNameStringCollection(
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepInfo.java
index 3eca5de..4c4e47b 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepInfo.java
@@ -31,6 +31,7 @@
private final boolean allowShrinking;
private final boolean allowSignatureRemoval;
private final boolean checkDiscarded;
+ private final boolean whyAreYouNotObfuscating;
private final KeepAnnotationCollectionInfo annotationsInfo;
private final KeepAnnotationCollectionInfo typeAnnotationsInfo;
@@ -42,6 +43,7 @@
boolean allowShrinking,
boolean allowSignatureRemoval,
boolean checkDiscarded,
+ boolean whyAreYouNotObfuscating,
KeepAnnotationCollectionInfo annotationsInfo,
KeepAnnotationCollectionInfo typeAnnotationsInfo) {
this.allowAccessModification = allowAccessModification;
@@ -51,6 +53,7 @@
this.allowShrinking = allowShrinking;
this.allowSignatureRemoval = allowSignatureRemoval;
this.checkDiscarded = checkDiscarded;
+ this.whyAreYouNotObfuscating = whyAreYouNotObfuscating;
this.annotationsInfo = annotationsInfo;
this.typeAnnotationsInfo = typeAnnotationsInfo;
}
@@ -64,6 +67,7 @@
builder.isShrinkingAllowed(),
builder.isSignatureRemovalAllowed(),
builder.isCheckDiscardedEnabled(),
+ builder.isWhyAreYouNotObfuscatingEnabled(),
builder.getAnnotationsInfo().build(),
builder.getTypeAnnotationsInfo().build());
}
@@ -150,6 +154,14 @@
return checkDiscarded;
}
+ public boolean isWhyAreYouNotObfuscatingEnabled() {
+ return internalIsWhyAreYouNotObfuscatingEnabled();
+ }
+
+ boolean internalIsWhyAreYouNotObfuscatingEnabled() {
+ return whyAreYouNotObfuscating;
+ }
+
/**
* True if an item must be present in the output.
*
@@ -299,6 +311,7 @@
&& (allowShrinking || !other.internalIsShrinkingAllowed())
&& (allowSignatureRemoval || !other.internalIsSignatureRemovalAllowed())
&& (!checkDiscarded || other.internalIsCheckDiscardedEnabled())
+ && (!whyAreYouNotObfuscating || other.internalIsWhyAreYouNotObfuscatingEnabled())
&& annotationsInfo.isLessThanOrEqualTo(other.internalAnnotationsInfo())
&& typeAnnotationsInfo.isLessThanOrEqualTo(other.internalTypeAnnotationsInfo());
}
@@ -312,7 +325,8 @@
&& allowOptimization == other.internalIsOptimizationAllowed()
&& allowShrinking == other.internalIsShrinkingAllowed()
&& allowSignatureRemoval == other.internalIsSignatureRemovalAllowed()
- && checkDiscarded == other.internalIsCheckDiscardedEnabled();
+ && checkDiscarded == other.internalIsCheckDiscardedEnabled()
+ && whyAreYouNotObfuscating == other.internalIsWhyAreYouNotObfuscatingEnabled();
}
public boolean equalsWithAnnotations(K other) {
@@ -341,6 +355,7 @@
hash += bit(allowShrinking, index++);
hash += bit(allowSignatureRemoval, index++);
hash += bit(checkDiscarded, index++);
+ hash += bit(whyAreYouNotObfuscating, index++);
hash += bit(annotationsInfo.isTop(), index++);
hash += bit(typeAnnotationsInfo.isTop(), index);
return hash;
@@ -369,6 +384,9 @@
case "checkDiscarded":
builder.setCheckDiscarded(Boolean.parseBoolean(value));
return true;
+ case "whyAreYouNotObfuscating":
+ builder.setWhyAreYouNotObfuscating(Boolean.parseBoolean(value));
+ return true;
case "annotationsInfo":
builder.setAnnotationInfo(KeepAnnotationCollectionInfoExported.parse(value));
return true;
@@ -389,6 +407,7 @@
lines.add("allowShrinking: " + allowShrinking);
lines.add("allowSignatureRemoval: " + allowSignatureRemoval);
lines.add("checkDiscarded: " + checkDiscarded);
+ lines.add("whyAreYouNotObfuscating: " + whyAreYouNotObfuscating);
lines.add("annotationsInfo: " + annotationsInfo);
lines.add("typeAnnotationsInfo: " + typeAnnotationsInfo);
return lines;
@@ -423,6 +442,7 @@
private boolean allowShrinking;
private boolean allowSignatureRemoval;
private boolean checkDiscarded;
+ private boolean whyAreYouNotObfuscating;
private KeepAnnotationCollectionInfo.Builder annotationsInfo;
private KeepAnnotationCollectionInfo.Builder typeAnnotationsInfo;
@@ -439,6 +459,7 @@
allowShrinking = original.internalIsShrinkingAllowed();
allowSignatureRemoval = original.internalIsSignatureRemovalAllowed();
checkDiscarded = original.internalIsCheckDiscardedEnabled();
+ whyAreYouNotObfuscating = original.internalIsWhyAreYouNotObfuscatingEnabled();
annotationsInfo = original.internalAnnotationsInfo().toBuilder();
typeAnnotationsInfo = original.internalTypeAnnotationsInfo().toBuilder();
}
@@ -453,6 +474,7 @@
setAllowShrinking(false);
setAllowSignatureRemoval(false);
setCheckDiscarded(false);
+ setWhyAreYouNotObfuscating(false);
return self();
}
@@ -466,6 +488,7 @@
setAllowShrinking(true);
setAllowSignatureRemoval(true);
setCheckDiscarded(false);
+ setWhyAreYouNotObfuscating(false);
return self();
}
@@ -493,6 +516,7 @@
&& isShrinkingAllowed() == other.internalIsShrinkingAllowed()
&& isSignatureRemovalAllowed() == other.internalIsSignatureRemovalAllowed()
&& isCheckDiscardedEnabled() == other.internalIsCheckDiscardedEnabled()
+ && isWhyAreYouNotObfuscatingEnabled() == other.internalIsWhyAreYouNotObfuscatingEnabled()
&& annotationsInfo.isEqualTo(other.internalAnnotationsInfo())
&& typeAnnotationsInfo.isEqualTo(other.internalTypeAnnotationsInfo());
}
@@ -506,6 +530,15 @@
return self();
}
+ public boolean isWhyAreYouNotObfuscatingEnabled() {
+ return whyAreYouNotObfuscating;
+ }
+
+ public B setWhyAreYouNotObfuscating(boolean whyAreYouNotObfuscating) {
+ this.whyAreYouNotObfuscating = whyAreYouNotObfuscating;
+ return self();
+ }
+
public boolean isMinificationAllowed() {
return allowMinification;
}
@@ -654,6 +687,10 @@
return builder.isCheckDiscardedEnabled();
}
+ public boolean isWhyAreYouNotObfuscatingEnabled() {
+ return builder.isWhyAreYouNotObfuscatingEnabled();
+ }
+
public boolean isMinificationAllowed() {
return builder.isMinificationAllowed();
}
@@ -735,6 +772,11 @@
return self();
}
+ public J setWhyAreYouNotObfuscating() {
+ builder.setWhyAreYouNotObfuscating(true);
+ return self();
+ }
+
public J merge(J joiner) {
Builder<B, K> otherBuilder = joiner.builder;
applyIf(!otherBuilder.isAccessModificationAllowed(), Joiner::disallowAccessModification);
@@ -746,6 +788,7 @@
applyIf(!otherBuilder.isShrinkingAllowed(), Joiner::disallowShrinking);
applyIf(!otherBuilder.isSignatureRemovalAllowed(), Joiner::disallowSignatureRemoval);
applyIf(otherBuilder.isCheckDiscardedEnabled(), Joiner::setCheckDiscarded);
+ applyIf(otherBuilder.isWhyAreYouNotObfuscatingEnabled(), Joiner::setWhyAreYouNotObfuscating);
builder.getAnnotationsInfo().destructiveJoin(otherBuilder.getAnnotationsInfo());
builder.getTypeAnnotationsInfo().destructiveJoin(otherBuilder.getTypeAnnotationsInfo());
reasons.addAll(joiner.reasons);
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java b/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java
index 11ebdfb..574dc74 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepInfoCollection.java
@@ -284,7 +284,9 @@
// Mutation interface for building up the keep info.
public static class MutableKeepInfoCollection extends KeepInfoCollection {
+ private final AppView<?> appView;
private final DexItemFactory factory;
+ private final Enqueuer.Mode mode;
// These are typed at signatures but the interface should make sure never to allow access
// directly with a signature. See the comment in KeepInfoCollection.
@@ -304,9 +306,10 @@
private final KeepInfoCanonicalizer canonicalizer;
- MutableKeepInfoCollection(InternalOptions options) {
+ MutableKeepInfoCollection(AppView<?> appView, Enqueuer enqueuer) {
this(
- options,
+ appView,
+ enqueuer.getMode(),
new IdentityHashMap<>(),
new IdentityHashMap<>(),
new IdentityHashMap<>(),
@@ -314,13 +317,14 @@
new IdentityHashMap<>(),
new IdentityHashMap<>(),
MaterializedRules.empty(),
- options.testing.enableKeepInfoCanonicalizer
+ appView.testing().enableKeepInfoCanonicalizer
? KeepInfoCanonicalizer.newCanonicalizer()
: KeepInfoCanonicalizer.newNopCanonicalizer());
}
private MutableKeepInfoCollection(
- InternalOptions options,
+ AppView<?> appView,
+ Enqueuer.Mode mode,
Map<DexType, KeepClassInfo> keepClassInfo,
Map<DexMethod, KeepMethodInfo> keepMethodInfo,
Map<DexField, KeepFieldInfo> keepFieldInfo,
@@ -329,7 +333,9 @@
Map<DexMethod, KeepMethodInfo.Joiner> methodRuleInstances,
MaterializedRules materializedRules,
KeepInfoCanonicalizer keepInfoCanonicalizer) {
- this.factory = options.dexItemFactory();
+ this.appView = appView;
+ this.factory = appView.dexItemFactory();
+ this.mode = mode;
this.keepClassInfo = keepClassInfo;
this.keepMethodInfo = keepMethodInfo;
this.keepFieldInfo = keepFieldInfo;
@@ -388,7 +394,8 @@
Map<DexField, KeepFieldInfo> newFieldInfo = rewriteFieldInfo(lens, options, timing);
MutableKeepInfoCollection result =
new MutableKeepInfoCollection(
- options,
+ appView,
+ mode,
newClassInfo,
newMethodInfo,
newFieldInfo,
@@ -610,10 +617,12 @@
KeepClassInfo.Joiner joiner = info.joiner();
fn.accept(joiner);
KeepClassInfo joined = joiner.join();
- if (!info.equals(joined)) {
- keepClassInfo.put(clazz.type, canonicalizer.canonicalizeKeepClassInfo(joined));
- maybeDisallowKotlinMetadataRemoval(clazz, info, joined, joiner);
+ if (info.equals(joined)) {
+ return;
}
+ keepClassInfo.put(clazz.type, canonicalizer.canonicalizeKeepClassInfo(joined));
+ maybeDisallowKotlinMetadataRemoval(clazz, info, joined, joiner);
+ reportWhyAreYouNotObfuscating(clazz, info, joined, joiner);
}
private void maybeDisallowKotlinMetadataRemoval(
@@ -639,6 +648,54 @@
allowKotlinMetadataRemoval = false;
}
+ private void reportWhyAreYouNotObfuscating(
+ ProgramDefinition definition,
+ KeepInfo<?, ?> previousKeepInfo,
+ KeepInfo<?, ?> joinedKeepInfo,
+ KeepInfo.Joiner<?, ?, ?> joiner) {
+ InternalOptions options = appView.options();
+ if (!mode.isFinalTreeShaking()
+ || options.getProguardConfiguration() == null
+ || !options.getProguardConfiguration().hasWhyAreYouNotObfuscatingRule()
+ || !previousKeepInfo.internalIsMinificationAllowed()
+ || joiner.isMinificationAllowed()) {
+ return;
+ }
+ MinimumKeepInfoCollection minimumKeepInfoCollection =
+ appView
+ .rootSet()
+ .getDependentMinimumKeepInfo()
+ .getUnconditionalMinimumKeepInfoOrDefault(MinimumKeepInfoCollection.empty());
+ if (!minimumKeepInfoCollection.hasMinimumKeepInfoThatMatches(
+ definition.getReference(), KeepInfo.Joiner::isWhyAreYouNotObfuscatingEnabled)) {
+ return;
+ }
+ assert !joinedKeepInfo.internalIsMinificationAllowed();
+ boolean foundRule = false;
+ for (ProguardKeepRuleBase rule : joiner.getRules()) {
+ if (!rule.getModifiers().allowsObfuscation) {
+ appView
+ .reporter()
+ .warning(
+ definition.getReference().toSourceString() + " is not obfuscated due to " + rule);
+ foundRule = true;
+ }
+ }
+ if (!foundRule) {
+ boolean foundReason = false;
+ for (KeepReason reason : joiner.getReasons()) {
+ appView
+ .reporter()
+ .warning(
+ definition.getReference().toSourceString()
+ + " is not obfuscated due to "
+ + reason);
+ foundReason = true;
+ }
+ assert foundReason;
+ }
+ }
+
public void keepClass(DexProgramClass clazz) {
joinClass(clazz, KeepInfo.Joiner::top);
}
@@ -652,9 +709,11 @@
KeepMethodInfo.Joiner joiner = info.joiner();
fn.accept(joiner);
KeepMethodInfo joined = joiner.join();
- if (!info.equals(joined)) {
- keepMethodInfo.put(method.getReference(), canonicalizer.canonicalizeKeepMethodInfo(joined));
+ if (info.equals(joined)) {
+ return;
}
+ keepMethodInfo.put(method.getReference(), canonicalizer.canonicalizeKeepMethodInfo(joined));
+ reportWhyAreYouNotObfuscating(method, info, joined, joiner);
}
public void keepMethod(ProgramMethod method) {
@@ -670,9 +729,11 @@
Joiner joiner = info.joiner();
fn.accept(joiner);
KeepFieldInfo joined = joiner.join();
- if (!info.equals(joined)) {
- keepFieldInfo.put(field.getReference(), canonicalizer.canonicalizeKeepFieldInfo(joined));
+ if (info.equals(joined)) {
+ return;
}
+ keepFieldInfo.put(field.getReference(), canonicalizer.canonicalizeKeepFieldInfo(joined));
+ reportWhyAreYouNotObfuscating(field, info, joined, joiner);
}
public void keepField(ProgramField field) {
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepMethodInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepMethodInfo.java
index 597b016..a9879ad 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepMethodInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepMethodInfo.java
@@ -48,6 +48,7 @@
private final boolean allowUnusedArgumentOptimization;
private final boolean allowUnusedReturnValueOptimization;
private final boolean allowParameterNamesRemoval;
+ private final boolean whyAreYouNotInlining;
private final KeepAnnotationCollectionInfo parameterAnnotationsInfo;
protected KeepMethodInfo(Builder builder) {
@@ -68,6 +69,7 @@
this.allowUnusedArgumentOptimization = builder.isUnusedArgumentOptimizationAllowed();
this.allowUnusedReturnValueOptimization = builder.isUnusedReturnValueOptimizationAllowed();
this.allowParameterNamesRemoval = builder.isParameterNamesRemovalAllowed();
+ this.whyAreYouNotInlining = builder.isWhyAreYouNotInliningEnabled();
this.parameterAnnotationsInfo = builder.getParameterAnnotationsInfo().build();
}
@@ -293,6 +295,14 @@
return allowParameterNamesRemoval;
}
+ public boolean isWhyAreYouNotInliningEnabled() {
+ return internalIsWhyAreYouNotInliningEnabled();
+ }
+
+ boolean internalIsWhyAreYouNotInliningEnabled() {
+ return whyAreYouNotInlining;
+ }
+
public Joiner joiner() {
assert !isTop();
return new Joiner(this);
@@ -326,7 +336,8 @@
&& allowUnusedArgumentOptimization == other.internalIsUnusedArgumentOptimizationAllowed()
&& allowUnusedReturnValueOptimization
== other.internalIsUnusedReturnValueOptimizationAllowed()
- && allowParameterNamesRemoval == other.internalIsParameterNamesRemovalAllowed();
+ && allowParameterNamesRemoval == other.internalIsParameterNamesRemovalAllowed()
+ && whyAreYouNotInlining == other.internalIsWhyAreYouNotInliningEnabled();
}
@Override
@@ -365,6 +376,7 @@
hash += bit(allowUnusedArgumentOptimization, index++);
hash += bit(allowUnusedReturnValueOptimization, index++);
hash += bit(allowParameterNamesRemoval, index++);
+ hash += bit(whyAreYouNotInlining, index++);
hash += bit(parameterAnnotationsInfo.isTop(), index);
return hash;
}
@@ -432,6 +444,9 @@
case "allowParameterNamesRemoval":
builder.setAllowParameterNamesRemoval(Boolean.parseBoolean(value));
break;
+ case "whyAreYouNotInlining":
+ builder.setWhyAreYouNotInlining(Boolean.parseBoolean(value));
+ break;
case "parameterAnnotationsInfo":
builder.setParameterAnnotationInfo(KeepAnnotationCollectionInfoExported.parse(value));
break;
@@ -462,6 +477,7 @@
lines.add("allowUnusedArgumentOptimization: " + allowUnusedArgumentOptimization);
lines.add("allowUnusedReturnValueOptimization: " + allowUnusedReturnValueOptimization);
lines.add("allowParameterNamesRemoval: " + allowParameterNamesRemoval);
+ lines.add("whyAreYouNotInlining: " + whyAreYouNotInlining);
lines.add("parameterAnnotationsInfo: " + parameterAnnotationsInfo);
return lines;
}
@@ -484,6 +500,7 @@
private boolean allowUnusedArgumentOptimization;
private boolean allowUnusedReturnValueOptimization;
private boolean allowParameterNamesRemoval;
+ private boolean whyAreYouNotInlining;
private KeepAnnotationCollectionInfo.Builder parameterAnnotationsInfo;
public Builder() {
@@ -509,6 +526,7 @@
allowUnusedReturnValueOptimization =
original.internalIsUnusedReturnValueOptimizationAllowed();
allowParameterNamesRemoval = original.internalIsParameterNamesRemovalAllowed();
+ whyAreYouNotInlining = original.internalIsWhyAreYouNotInliningEnabled();
parameterAnnotationsInfo = original.internalParameterAnnotationsInfo().toBuilder();
}
@@ -657,6 +675,15 @@
return self();
}
+ public boolean isWhyAreYouNotInliningEnabled() {
+ return whyAreYouNotInlining;
+ }
+
+ public Builder setWhyAreYouNotInlining(boolean whyAreYouNotInlining) {
+ this.whyAreYouNotInlining = whyAreYouNotInlining;
+ return self();
+ }
+
public KeepAnnotationCollectionInfo.Builder getParameterAnnotationsInfo() {
return parameterAnnotationsInfo;
}
@@ -709,6 +736,7 @@
&& isUnusedReturnValueOptimizationAllowed()
== other.internalIsUnusedReturnValueOptimizationAllowed()
&& isParameterNamesRemovalAllowed() == other.internalIsParameterNamesRemovalAllowed()
+ && isWhyAreYouNotInliningEnabled() == other.internalIsWhyAreYouNotInliningEnabled()
&& parameterAnnotationsInfo.isEqualTo(other.parameterAnnotationsInfo);
}
@@ -736,6 +764,7 @@
.setAllowUnusedArgumentOptimization(false)
.setAllowUnusedReturnValueOptimization(false)
.setAllowParameterNamesRemoval(false)
+ .setWhyAreYouNotInlining(false)
.setParameterAnnotationInfo(KeepAnnotationCollectionInfo.Builder.createTop());
}
@@ -758,6 +787,7 @@
.setAllowUnusedArgumentOptimization(true)
.setAllowUnusedReturnValueOptimization(true)
.setAllowParameterNamesRemoval(true)
+ .setWhyAreYouNotInlining(false)
.setParameterAnnotationInfo(KeepAnnotationCollectionInfo.Builder.createBottom());
}
}
@@ -862,6 +892,15 @@
return self();
}
+ public boolean isWhyAreYouNotInliningEnabled() {
+ return builder.isWhyAreYouNotInliningEnabled();
+ }
+
+ public Joiner setWhyAreYouNotInlining() {
+ builder.setWhyAreYouNotInlining(true);
+ return self();
+ }
+
@Override
public Joiner asMethodJoiner() {
return this;
@@ -904,7 +943,8 @@
Joiner::disallowUnusedReturnValueOptimization)
.applyIf(
!joiner.builder.isParameterNamesRemovalAllowed(),
- Joiner::disallowParameterNamesRemoval);
+ Joiner::disallowParameterNamesRemoval)
+ .applyIf(joiner.builder.isWhyAreYouNotInliningEnabled(), Joiner::setWhyAreYouNotInlining);
}
@Override
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 5b0c3f1..96c75c0 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
@@ -471,6 +471,8 @@
private final ProguardPathFilter keepDirectories;
private final boolean protoShrinking;
private final int maxRemovedAndroidLogLevel;
+ private final boolean hasWhyAreYouNotInliningRule;
+ private final boolean hasWhyAreYouNotObfuscatingRule;
private ProguardConfiguration(
String parsedConfiguration,
@@ -545,6 +547,10 @@
this.keepDirectories = keepDirectories;
this.protoShrinking = protoShrinking;
this.maxRemovedAndroidLogLevel = maxRemovedAndroidLogLevel;
+ this.hasWhyAreYouNotInliningRule =
+ Iterables.any(rules, rule -> rule instanceof WhyAreYouNotInliningRule);
+ this.hasWhyAreYouNotObfuscatingRule =
+ Iterables.any(rules, rule -> rule instanceof WhyAreYouNotObfuscatingRule);
}
/**
@@ -716,6 +722,14 @@
return Iterables.any(rules, ProguardConfigurationRule::isMaximumRemovedAndroidLogLevelRule);
}
+ public boolean hasWhyAreYouNotInliningRule() {
+ return hasWhyAreYouNotInliningRule;
+ }
+
+ public boolean hasWhyAreYouNotObfuscatingRule() {
+ return hasWhyAreYouNotObfuscatingRule;
+ }
+
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
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 dddd397..6b000b9 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -484,6 +484,10 @@
} else if (acceptString(ConvertCheckNotNullRule.RULE_NAME)) {
configurationConsumer.addRule(parseConvertCheckNotNullRule(optionStart));
return true;
+ } else if (acceptString(WhyAreYouNotObfuscatingRule.RULE_NAME)) {
+ configurationConsumer.addRule(
+ parseRuleWithClassSpec(optionStart, WhyAreYouNotObfuscatingRule.builder()));
+ return true;
} else if (acceptString(WhyAreYouNotInliningRule.RULE_NAME)) {
configurationConsumer.addRule(
parseRuleWithClassSpec(optionStart, WhyAreYouNotInliningRule.builder()));
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
index 2541fcb..e1546fc 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
@@ -138,7 +138,6 @@
DependentMinimumKeepInfoCollection.createConcurrent();
private final LinkedHashMap<DexReference, DexReference> reasonAsked = new LinkedHashMap<>();
private final Set<DexMethod> alwaysInline = Sets.newIdentityHashSet();
- private final Set<DexMethod> whyAreYouNotInlining = Sets.newIdentityHashSet();
private final Set<DexMethod> reprocess = Sets.newIdentityHashSet();
private final PredicateSet<DexType> alwaysClassInline = new PredicateSet<>();
private final Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule =
@@ -499,9 +498,14 @@
} else if (rule instanceof ProguardIdentifierNameStringRule) {
markMatchingFields(clazz, fieldKeepRules, rule, ifRulePreconditionMatch);
markMatchingMethods(clazz, methodKeepRules, rule, ifRulePreconditionMatch);
- } else {
- assert rule instanceof ConvertCheckNotNullRule;
+ } else if (rule instanceof ConvertCheckNotNullRule) {
markMatchingMethods(clazz, methodKeepRules, rule, ifRulePreconditionMatch);
+ } else if (rule instanceof WhyAreYouNotObfuscatingRule) {
+ markClass(clazz, rule, ifRulePreconditionMatch);
+ markMatchingFields(clazz, fieldKeepRules, rule, ifRulePreconditionMatch);
+ markMatchingMethods(clazz, methodKeepRules, rule, ifRulePreconditionMatch);
+ } else {
+ assert false : rule.getClass().getName();
}
}
@@ -658,7 +662,6 @@
dependentMinimumKeepInfo,
ImmutableList.copyOf(reasonAsked.values()),
alwaysInline,
- whyAreYouNotInlining,
reprocess,
alwaysClassInline,
mayHaveSideEffects,
@@ -1482,7 +1485,15 @@
if (!item.isMethod()) {
throw new Unreachable();
}
- whyAreYouNotInlining.add(item.asMethod().getReference());
+ dependentMinimumKeepInfo
+ .getOrCreateUnconditionalMinimumKeepInfoFor(item.getReference())
+ .asMethodJoiner()
+ .setWhyAreYouNotInlining();
+ context.markAsUsed();
+ } else if (context instanceof WhyAreYouNotObfuscatingRule) {
+ dependentMinimumKeepInfo
+ .getOrCreateUnconditionalMinimumKeepInfoFor(item.getReference())
+ .setWhyAreYouNotObfuscating();
context.markAsUsed();
} else if (context.isClassInlineRule()) {
ClassInlineRule classInlineRule = context.asClassInlineRule();
@@ -2202,7 +2213,6 @@
public final ImmutableList<DexReference> reasonAsked;
public final Set<DexMethod> alwaysInline;
- public final Set<DexMethod> whyAreYouNotInlining;
public final Set<DexMethod> reprocess;
public final PredicateSet<DexType> alwaysClassInline;
public final Map<DexReference, ProguardMemberRule> mayHaveSideEffects;
@@ -2215,7 +2225,6 @@
DependentMinimumKeepInfoCollection dependentMinimumKeepInfo,
ImmutableList<DexReference> reasonAsked,
Set<DexMethod> alwaysInline,
- Set<DexMethod> whyAreYouNotInlining,
Set<DexMethod> reprocess,
PredicateSet<DexType> alwaysClassInline,
Map<DexReference, ProguardMemberRule> mayHaveSideEffects,
@@ -2233,7 +2242,6 @@
pendingMethodMoveInverse);
this.reasonAsked = reasonAsked;
this.alwaysInline = alwaysInline;
- this.whyAreYouNotInlining = whyAreYouNotInlining;
this.reprocess = reprocess;
this.alwaysClassInline = alwaysClassInline;
this.mayHaveSideEffects = mayHaveSideEffects;
@@ -2347,7 +2355,6 @@
getDependentMinimumKeepInfo().rewrittenWithLens(graphLens, timing),
reasonAsked,
alwaysInline,
- whyAreYouNotInlining,
reprocess,
alwaysClassInline,
mayHaveSideEffects,
@@ -2648,7 +2655,6 @@
reasonAsked,
Collections.emptySet(),
Collections.emptySet(),
- Collections.emptySet(),
PredicateSet.empty(),
emptyMap(),
emptyMap(),
diff --git a/src/main/java/com/android/tools/r8/shaking/WhyAreYouNotInliningRule.java b/src/main/java/com/android/tools/r8/shaking/WhyAreYouNotInliningRule.java
index 4801930..f114450 100644
--- a/src/main/java/com/android/tools/r8/shaking/WhyAreYouNotInliningRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/WhyAreYouNotInliningRule.java
@@ -77,6 +77,13 @@
return new Builder();
}
+ // The ServiceLoader rewriter emits debug information if -whyareyounotinlining matches
+ // ServiceLoader.load.
+ @Override
+ public boolean isApplicableToLibraryClasses() {
+ return true;
+ }
+
@Override
String typeString() {
return RULE_NAME;
diff --git a/src/main/java/com/android/tools/r8/shaking/WhyAreYouNotObfuscatingRule.java b/src/main/java/com/android/tools/r8/shaking/WhyAreYouNotObfuscatingRule.java
new file mode 100644
index 0000000..b6b291b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/WhyAreYouNotObfuscatingRule.java
@@ -0,0 +1,84 @@
+// Copyright (c) 2025, 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.origin.Origin;
+import com.android.tools.r8.position.Position;
+import java.util.List;
+
+public class WhyAreYouNotObfuscatingRule extends ProguardConfigurationRule {
+
+ public static final String RULE_NAME = "whyareyounotobfuscating";
+
+ @SuppressWarnings("NonCanonicalType")
+ public static class Builder
+ extends ProguardConfigurationRule.Builder<WhyAreYouNotObfuscatingRule, Builder> {
+
+ private Builder() {
+ super();
+ }
+
+ @Override
+ public Builder self() {
+ return this;
+ }
+
+ @Override
+ public WhyAreYouNotObfuscatingRule build() {
+ return new WhyAreYouNotObfuscatingRule(
+ origin,
+ getPosition(),
+ source,
+ buildClassAnnotations(),
+ classAccessFlags,
+ negatedClassAccessFlags,
+ classTypeNegated,
+ classType,
+ classNames,
+ buildInheritanceAnnotations(),
+ inheritanceClassName,
+ inheritanceIsExtends,
+ memberRules);
+ }
+ }
+
+ private WhyAreYouNotObfuscatingRule(
+ Origin origin,
+ Position position,
+ String source,
+ List<ProguardTypeMatcher> classAnnotations,
+ ProguardAccessFlags classAccessFlags,
+ ProguardAccessFlags negatedClassAccessFlags,
+ boolean classTypeNegated,
+ ProguardClassType classType,
+ ProguardClassNameList classNames,
+ List<ProguardTypeMatcher> inheritanceAnnotations,
+ ProguardTypeMatcher inheritanceClassName,
+ boolean inheritanceIsExtends,
+ List<ProguardMemberRule> memberRules) {
+ super(
+ origin,
+ position,
+ source,
+ classAnnotations,
+ classAccessFlags,
+ negatedClassAccessFlags,
+ classTypeNegated,
+ classType,
+ classNames,
+ inheritanceAnnotations,
+ inheritanceClassName,
+ inheritanceIsExtends,
+ memberRules);
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ @Override
+ String typeString() {
+ return RULE_NAME;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/whyareyounotobfuscating/WhyAreYouNotObfuscatingClassTest.java b/src/test/java/com/android/tools/r8/naming/whyareyounotobfuscating/WhyAreYouNotObfuscatingClassTest.java
new file mode 100644
index 0000000..489c552
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/whyareyounotobfuscating/WhyAreYouNotObfuscatingClassTest.java
@@ -0,0 +1,53 @@
+// Copyright (c) 2025, 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.naming.whyareyounotobfuscating;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.containsString;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringDiagnostic;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class WhyAreYouNotObfuscatingClassTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDefaultDexRuntime().withMaximumApiLevel().build();
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters)
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .addKeepRules("-whyareyounotobfuscating class " + Main.class.getTypeName())
+ .allowDiagnosticWarningMessages()
+ .compileWithExpectedDiagnostics(
+ diagnostics ->
+ diagnostics.assertWarningsMatch(
+ allOf(
+ diagnosticType(StringDiagnostic.class),
+ diagnosticMessage(
+ containsString(
+ Main.class.getTypeName() + " is not obfuscated due to -keep")))));
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {}
+ }
+}