blob: 328b6ca0072c11379ec7a458483ecd3c0dcb7a80 [file] [log] [blame]
// Copyright (c) 2023, 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.keepanno.keeprules;
import static com.android.tools.r8.keepanno.keeprules.RulePrintingUtils.CHECK_DISCARD;
import static com.android.tools.r8.keepanno.keeprules.RulePrintingUtils.KEEP;
import static com.android.tools.r8.keepanno.keeprules.RulePrintingUtils.KEEP_ATTRIBUTES;
import static com.android.tools.r8.keepanno.keeprules.RulePrintingUtils.KEEP_CLASSES_WITH_MEMBERS;
import static com.android.tools.r8.keepanno.keeprules.RulePrintingUtils.KEEP_CLASS_MEMBERS;
import static com.android.tools.r8.keepanno.keeprules.RulePrintingUtils.printClassHeader;
import static com.android.tools.r8.keepanno.keeprules.RulePrintingUtils.printMemberClause;
import com.android.tools.r8.keepanno.ast.KeepAttribute;
import com.android.tools.r8.keepanno.ast.KeepBindings.KeepBindingSymbol;
import com.android.tools.r8.keepanno.ast.KeepClassItemPattern;
import com.android.tools.r8.keepanno.ast.KeepEdgeException;
import com.android.tools.r8.keepanno.ast.KeepEdgeMetaInfo;
import com.android.tools.r8.keepanno.ast.KeepMemberPattern;
import com.android.tools.r8.keepanno.ast.KeepOptions;
import com.android.tools.r8.keepanno.ast.KeepQualifiedClassNamePattern;
import com.android.tools.r8.keepanno.keeprules.KeepRuleExtractor.Holder;
import com.android.tools.r8.keepanno.keeprules.RulePrinter.BackReferencePrinter;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
public abstract class PgRule {
/**
* Group the rules such that unconditional rules appear first, followed by class rules and then
* member rules. The original order of the rules is retained within each of the groups.
*/
public static void groupByKinds(List<PgRule> rules) {
IdentityHashMap<PgRule, Integer> order = new IdentityHashMap<>();
rules.forEach(r -> order.put(r, order.size()));
rules.sort(
Comparator.comparingInt((PgRule p) -> p.hasCondition() ? 1 : 0)
.thenComparingInt(
p -> {
switch (p.getConsequenceKeepType()) {
case KEEP_ATTRIBUTES:
return 0;
case KEEP:
return 1;
case KEEP_CLASSES_WITH_MEMBERS:
return 2;
case KEEP_CLASS_MEMBERS:
return 3;
case CHECK_DISCARD:
return 4;
default:
throw new KeepEdgeException(
"Unexpected consequence keep type: " + p.getConsequenceKeepType());
}
})
.thenComparingInt(order::get));
}
public enum TargetKeepKind {
JUST_MEMBERS(RulePrintingUtils.KEEP_CLASS_MEMBERS),
CLASS_OR_MEMBERS(RulePrintingUtils.KEEP),
CLASS_AND_MEMBERS(RulePrintingUtils.KEEP_CLASSES_WITH_MEMBERS),
CHECK_DISCARD(RulePrintingUtils.CHECK_DISCARD);
private final String ruleKind;
TargetKeepKind(String ruleKind) {
this.ruleKind = ruleKind;
}
String getKeepRuleKind() {
return ruleKind;
}
boolean isKeepKind() {
return this != CHECK_DISCARD;
}
}
private static void printNonEmptyMembersPatternAsDefaultInitWorkaround(
StringBuilder builder, TargetKeepKind kind) {
if (kind.isKeepKind()) {
// If no members is given, compat R8 and legacy full mode will implicitly keep <init>().
// Add a keep of finalize which is a library method that would be kept in any case.
builder.append(" { void finalize(); }");
}
}
private final KeepEdgeMetaInfo metaInfo;
private final KeepOptions options;
private final KeepRuleExtractorOptions extractorOptions;
private PgRule(
KeepEdgeMetaInfo metaInfo, KeepOptions options, KeepRuleExtractorOptions extractorOptions) {
this.metaInfo = metaInfo;
this.options = options;
this.extractorOptions = extractorOptions;
}
public KeepEdgeMetaInfo getMetaInfo() {
return metaInfo;
}
public KeepRuleExtractorOptions getExtractorOptions() {
return extractorOptions;
}
// Helper to print the class-name pattern in a class-item.
public static BiConsumer<StringBuilder, KeepQualifiedClassNamePattern> classNamePrinter(
KeepQualifiedClassNamePattern classNamePattern) {
return (builder, className) -> {
assert className.equals(classNamePattern);
RulePrintingUtils.printClassName(
classNamePattern, RulePrinter.withoutBackReferences(builder));
};
}
void printKeepOptions(StringBuilder builder, KeepRuleExtractorOptions extractorOptions) {
RulePrintingUtils.printKeepOptions(builder, options, extractorOptions);
}
public void printRule(StringBuilder builder, KeepRuleExtractorOptions extractorOptions) {
RulePrintingUtils.printHeader(builder, metaInfo);
printCondition(builder);
printConsequence(builder, extractorOptions);
}
void printCondition(StringBuilder builder) {
if (hasCondition()) {
builder.append(RulePrintingUtils.IF).append(' ');
printConditionHolder(builder);
List<KeepBindingSymbol> members = getConditionMembers();
if (!members.isEmpty()) {
builder.append(" {");
for (KeepBindingSymbol member : members) {
builder.append(' ');
printConditionMember(builder, member);
}
builder.append(" }");
}
builder.append(' ');
}
}
void printConsequence(StringBuilder builder, KeepRuleExtractorOptions extractorOptions) {
builder.append(getConsequenceKeepType());
printKeepOptions(builder, extractorOptions);
builder.append(' ');
printTargetHolder(builder);
List<KeepBindingSymbol> members = getTargetMembers();
if (!members.isEmpty()) {
builder.append(" {");
for (KeepBindingSymbol member : members) {
builder.append(' ');
printTargetMember(builder, member);
}
builder.append(" }");
}
}
boolean hasCondition() {
return false;
}
List<KeepBindingSymbol> getConditionMembers() {
throw new KeepEdgeException("Unreachable");
}
abstract String getConsequenceKeepType();
abstract List<KeepBindingSymbol> getTargetMembers();
void printConditionHolder(StringBuilder builder) {
throw new KeepEdgeException("Unreachable");
}
void printConditionMember(StringBuilder builder, KeepBindingSymbol member) {
throw new KeepEdgeException("Unreachable");
}
abstract void printTargetHolder(StringBuilder builder);
abstract void printTargetMember(StringBuilder builder, KeepBindingSymbol member);
/**
* Representation of an unconditional rule to keep a class and methods.
*
* <pre>
* -keep[classeswithmembers] class <holder> [{ <members> }]
* </pre>
*
* and with no dependencies / back-references.
*/
static class PgUnconditionalRule extends PgRule {
private final KeepQualifiedClassNamePattern holderNamePattern;
private final KeepClassItemPattern holderPattern;
private final TargetKeepKind targetKeepKind;
private final List<KeepBindingSymbol> targetMembers;
private final Map<KeepBindingSymbol, KeepMemberPattern> memberPatterns;
public PgUnconditionalRule(
KeepEdgeMetaInfo metaInfo,
Holder holder,
KeepOptions options,
Map<KeepBindingSymbol, KeepMemberPattern> memberPatterns,
List<KeepBindingSymbol> targetMembers,
TargetKeepKind targetKeepKind,
KeepRuleExtractorOptions extractorOptions) {
super(metaInfo, options, extractorOptions);
assert !targetKeepKind.equals(TargetKeepKind.JUST_MEMBERS);
this.holderNamePattern = holder.getNamePattern();
this.holderPattern = holder.getClassItemPattern();
this.targetKeepKind = targetKeepKind;
this.memberPatterns = memberPatterns;
this.targetMembers = targetMembers;
}
@Override
String getConsequenceKeepType() {
return targetKeepKind.getKeepRuleKind();
}
@Override
List<KeepBindingSymbol> getTargetMembers() {
return targetMembers;
}
@Override
void printTargetHolder(StringBuilder builder) {
printClassHeader(builder, holderPattern, classNamePrinter(holderNamePattern));
if (getTargetMembers().isEmpty()) {
printNonEmptyMembersPatternAsDefaultInitWorkaround(builder, targetKeepKind);
}
}
@Override
void printTargetMember(StringBuilder builder, KeepBindingSymbol memberReference) {
KeepMemberPattern memberPattern = memberPatterns.get(memberReference);
printMemberClause(
memberPattern, RulePrinter.withoutBackReferences(builder), getExtractorOptions());
}
}
static class PgKeepAttributeRule extends PgRule {
private final Set<KeepAttribute> attributes;
public PgKeepAttributeRule(
KeepEdgeMetaInfo metaInfo,
Set<KeepAttribute> attributes,
KeepRuleExtractorOptions extractorOptions) {
super(metaInfo, null, extractorOptions);
assert !attributes.isEmpty();
this.attributes = attributes;
}
@Override
public void printRule(StringBuilder builder, KeepRuleExtractorOptions options) {
RulePrintingUtils.printHeader(builder, getMetaInfo());
builder.append(getConsequenceKeepType()).append(" ");
List<KeepAttribute> sorted = new ArrayList<>(attributes);
sorted.sort(KeepAttribute::compareTo);
builder.append(sorted.get(0).getPrintName());
for (int i = 1; i < sorted.size(); i++) {
builder.append(',').append(sorted.get(i).getPrintName());
}
}
@Override
String getConsequenceKeepType() {
return KEEP_ATTRIBUTES;
}
@Override
List<KeepBindingSymbol> getTargetMembers() {
throw new IllegalStateException();
}
@Override
void printTargetHolder(StringBuilder builder) {
throw new IllegalStateException();
}
@Override
void printTargetMember(StringBuilder builder, KeepBindingSymbol member) {
throw new IllegalStateException();
}
}
/**
* Representation of conditional rules but without dependencies between condition and target.
*
* <pre>
* -if class <class-condition> [{ <member-conditions> }]
* -keepX class <class-target> [{ <member-targets> }]
* </pre>
*
* and with no dependencies / back-references.
*/
static class PgConditionalRule extends PgRule {
final KeepClassItemPattern classCondition;
final KeepClassItemPattern classTarget;
final Map<KeepBindingSymbol, KeepMemberPattern> memberPatterns;
final List<KeepBindingSymbol> memberConditions;
private final List<KeepBindingSymbol> memberTargets;
private final TargetKeepKind keepKind;
public PgConditionalRule(
KeepEdgeMetaInfo metaInfo,
KeepOptions options,
Holder classCondition,
Holder classTarget,
Map<KeepBindingSymbol, KeepMemberPattern> memberPatterns,
List<KeepBindingSymbol> memberConditions,
List<KeepBindingSymbol> memberTargets,
TargetKeepKind keepKind,
KeepRuleExtractorOptions extractorOptions) {
super(metaInfo, options, extractorOptions);
this.classCondition = classCondition.getClassItemPattern();
this.classTarget = classTarget.getClassItemPattern();
this.memberPatterns = memberPatterns;
this.memberConditions = memberConditions;
this.memberTargets = memberTargets;
this.keepKind = keepKind;
}
@Override
boolean hasCondition() {
return true;
}
@Override
List<KeepBindingSymbol> getConditionMembers() {
return memberConditions;
}
@Override
void printConditionHolder(StringBuilder builder) {
printClassHeader(builder, classCondition, this::printClassName);
}
@Override
void printConditionMember(StringBuilder builder, KeepBindingSymbol member) {
KeepMemberPattern memberPattern = memberPatterns.get(member);
printMemberClause(
memberPattern, RulePrinter.withoutBackReferences(builder), getExtractorOptions());
}
@Override
void printTargetHolder(StringBuilder builder) {
printClassHeader(builder, classTarget, this::printClassName);
if (getTargetMembers().isEmpty()) {
PgRule.printNonEmptyMembersPatternAsDefaultInitWorkaround(builder, keepKind);
}
}
@Override
String getConsequenceKeepType() {
return keepKind.getKeepRuleKind();
}
@Override
List<KeepBindingSymbol> getTargetMembers() {
return memberTargets;
}
@Override
void printTargetMember(StringBuilder builder, KeepBindingSymbol member) {
KeepMemberPattern memberPattern = memberPatterns.get(member);
printMemberClause(
memberPattern, RulePrinter.withoutBackReferences(builder), getExtractorOptions());
}
private void printClassName(StringBuilder builder, KeepQualifiedClassNamePattern clazzName) {
RulePrintingUtils.printClassName(clazzName, RulePrinter.withoutBackReferences(builder));
}
}
/**
* Representation of a conditional rule that is match/instance dependent.
*
* <pre>
* -if class <class-pattern> [{ <member-condition>* }]
* -keepX class <class-backref> [{ <member-target | member-backref>* }]
* </pre>
*
* or if the only condition is the class itself and targeting members, just:
*
* <pre>
* -keepclassmembers <class-pattern> { <member-target> }
* </pre>
*/
static class PgDependentMembersRule extends PgRule {
private final KeepQualifiedClassNamePattern holderNamePattern;
private final KeepClassItemPattern holderPattern;
private final Map<KeepBindingSymbol, KeepMemberPattern> memberPatterns;
private final List<KeepBindingSymbol> memberConditions;
private final List<KeepBindingSymbol> memberTargets;
private final TargetKeepKind keepKind;
private int nextBackReferenceNumber = 1;
private String holderBackReferencePattern = null;
private final Map<KeepBindingSymbol, String> membersBackReferencePatterns = new HashMap<>();
public PgDependentMembersRule(
KeepEdgeMetaInfo metaInfo,
Holder holder,
KeepOptions options,
Map<KeepBindingSymbol, KeepMemberPattern> memberPatterns,
List<KeepBindingSymbol> memberConditions,
List<KeepBindingSymbol> memberTargets,
TargetKeepKind keepKind,
KeepRuleExtractorOptions extractorOptions) {
super(metaInfo, options, extractorOptions);
this.holderNamePattern = holder.getNamePattern();
this.holderPattern = holder.getClassItemPattern();
this.memberPatterns = memberPatterns;
this.memberConditions = memberConditions;
this.memberTargets = memberTargets;
this.keepKind = keepKind;
}
private int getNextBackReferenceNumber() {
return nextBackReferenceNumber++;
}
@Override
boolean hasCondition() {
// We can avoid an if-rule if the condition is simply the class and the target is just
// members.
boolean canUseDependentRule =
memberConditions.isEmpty() && keepKind == TargetKeepKind.JUST_MEMBERS;
return !canUseDependentRule;
}
@Override
String getConsequenceKeepType() {
return keepKind.getKeepRuleKind();
}
@Override
List<KeepBindingSymbol> getConditionMembers() {
return memberConditions;
}
@Override
List<KeepBindingSymbol> getTargetMembers() {
return memberTargets;
}
@Override
void printConditionHolder(StringBuilder b) {
printClassHeader(
b,
holderPattern,
(builder, classReference) -> {
BackReferencePrinter printer =
RulePrinter.withBackReferences(b, this::getNextBackReferenceNumber);
RulePrintingUtils.printClassName(holderNamePattern, printer);
holderBackReferencePattern = printer.getBackReference();
});
}
@Override
void printConditionMember(StringBuilder builder, KeepBindingSymbol member) {
KeepMemberPattern memberPattern = memberPatterns.get(member);
BackReferencePrinter printer =
RulePrinter.withBackReferences(builder, this::getNextBackReferenceNumber);
printMemberClause(memberPattern, printer, getExtractorOptions());
membersBackReferencePatterns.put(member, printer.getBackReference());
}
@Override
void printTargetHolder(StringBuilder builder) {
printClassHeader(
builder,
holderPattern,
(b, className) -> {
assert className.equals(holderNamePattern);
if (hasCondition()) {
b.append(holderBackReferencePattern);
} else {
assert holderBackReferencePattern == null;
RulePrintingUtils.printClassName(
holderNamePattern, RulePrinter.withoutBackReferences(builder));
}
});
if (getTargetMembers().isEmpty()) {
PgRule.printNonEmptyMembersPatternAsDefaultInitWorkaround(builder, keepKind);
}
}
@Override
void printTargetMember(StringBuilder builder, KeepBindingSymbol member) {
if (hasCondition()) {
String backref = membersBackReferencePatterns.get(member);
if (backref != null) {
builder.append(backref);
return;
}
}
KeepMemberPattern memberPattern = memberPatterns.get(member);
printMemberClause(
memberPattern, RulePrinter.withoutBackReferences(builder), getExtractorOptions());
}
}
}