blob: 8064668feff2b9cb999f15205eb0d29794574ac3 [file]
// Copyright (c) 2026, 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.keepradius;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.shaking.Enqueuer;
import com.android.tools.r8.shaking.KeepClassInfo;
import com.android.tools.r8.shaking.KeepClassMembersNoShrinkingOfInitializerOnSubclassesFakeProguardRule;
import com.android.tools.r8.shaking.KeepFieldInfo;
import com.android.tools.r8.shaking.KeepInfo;
import com.android.tools.r8.shaking.KeepInfoCollectionEventConsumer;
import com.android.tools.r8.shaking.KeepMethodInfo;
import com.android.tools.r8.shaking.ProguardKeepRuleBase;
import com.android.tools.r8.shaking.rules.KeepAnnotationFakeProguardRule;
import com.android.tools.r8.utils.internal.ListUtils;
import java.util.Collection;
import java.util.Comparator;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
public class RootSetKeepRadius {
private final Map<ProguardKeepRuleBase, RootSetKeepRadiusForRule> keepRadius;
private RootSetKeepRadius(Map<ProguardKeepRuleBase, RootSetKeepRadiusForRule> keepRadius) {
this.keepRadius = keepRadius;
}
public static Builder builder(
AppView<? extends AppInfoWithClassHierarchy> appView, Enqueuer.Mode mode) {
return mode.isInitialTreeShaking() && appView.options().getKeepRadiusOptions().isEnabled()
? new Builder()
: null;
}
public Collection<RootSetKeepRadiusForRule> getKeepRadius() {
return keepRadius.values();
}
public Collection<RootSetKeepRadiusForRule> getKeepRadiusWithDeterministicOrder() {
// TODO(b/441055269): Sorting by source is not guaranteed to be deterministic.
return ListUtils.sort(getKeepRadius(), Comparator.comparing(x -> x.getRule().getSource()));
}
public Map<RootSetKeepRadiusForRule, Collection<RootSetKeepRadiusForRule>> getSubsumedByInfo(
KeepRadiusOptions options) {
return new KeepRuleSubsumptionAnalysis(this).run(options);
}
public static class Builder implements KeepInfoCollectionEventConsumer {
private final Map<ProguardKeepRuleBase, RootSetKeepRadiusForRule> keepRadius =
new IdentityHashMap<>();
@Override
public void acceptKeepClassInfo(
DexType type, Consumer<? super KeepClassInfo.Joiner> keepInfoEffect) {
acceptKeepInfo(
type,
keepInfoEffect,
KeepClassInfo.newEmptyJoiner(),
RootSetKeepRadiusForRule::addMatchedClass);
}
@Override
public void acceptKeepFieldInfo(
DexField field, Consumer<? super KeepFieldInfo.Joiner> keepInfoEffect) {
acceptKeepInfo(
field,
keepInfoEffect,
KeepFieldInfo.newEmptyJoiner(),
RootSetKeepRadiusForRule::addMatchedField);
}
@Override
public void acceptKeepMethodInfo(
DexMethod method, Consumer<? super KeepMethodInfo.Joiner> keepInfoEffect) {
acceptKeepInfo(
method,
keepInfoEffect,
KeepMethodInfo.newEmptyJoiner(),
RootSetKeepRadiusForRule::addMatchedMethod);
}
private <R extends DexReference, J extends KeepInfo.Joiner<?, ?, ?>> void acceptKeepInfo(
R reference,
Consumer<? super J> keepInfoEffect,
J emptyJoiner,
BiConsumer<RootSetKeepRadiusForRule, R> addReferenceToRuleKeepRadius) {
keepInfoEffect.accept(emptyJoiner);
for (ProguardKeepRuleBase rule : emptyJoiner.getRules()) {
if (rule.isProguardIfRule()) {
// Perform attribution to the root -if rule.
rule = rule.asProguardIfRule().getParentOrThis();
}
RootSetKeepRadiusForRule ruleKeepRadius =
keepRadius.computeIfAbsent(rule, RootSetKeepRadiusForRule::new);
addReferenceToRuleKeepRadius.accept(ruleKeepRadius, reference);
}
}
public RootSetKeepRadius build(AppView<? extends AppInfoWithClassHierarchy> appView) {
// Add all rules so that the keep radius result also contains empty rules.
for (var rule : appView.options().getProguardConfiguration().getRules()) {
if (rule instanceof ProguardKeepRuleBase) {
keepRadius.computeIfAbsent((ProguardKeepRuleBase) rule, RootSetKeepRadiusForRule::new);
}
}
// Remove fake rules from output.
keepRadius.keySet().removeIf(Builder::isFakeKeepRule);
return new RootSetKeepRadius(keepRadius);
}
private static boolean isFakeKeepRule(ProguardKeepRuleBase rule) {
return rule instanceof KeepAnnotationFakeProguardRule
|| rule instanceof KeepClassMembersNoShrinkingOfInitializerOnSubclassesFakeProguardRule;
}
}
}