blob: 412a91e3ce1176d00fbe88dff2be79dfa87188df [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.printClassHeader;
import static com.android.tools.r8.keepanno.keeprules.RulePrintingUtils.printMemberClause;
import com.android.tools.r8.keepanno.ast.KeepClassReference;
import com.android.tools.r8.keepanno.ast.KeepEdgeException;
import com.android.tools.r8.keepanno.ast.KeepEdgeMetaInfo;
import com.android.tools.r8.keepanno.ast.KeepItemPattern;
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.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
public abstract class PgRule {
public enum TargetKeepKind {
JUST_MEMBERS(RulePrintingUtils.KEEP_CLASS_MEMBERS),
CLASS_OR_MEMBERS(RulePrintingUtils.KEEP),
CLASS_AND_MEMBERS(RulePrintingUtils.KEEP_CLASSES_WITH_MEMBERS);
private final String ruleKind;
TargetKeepKind(String ruleKind) {
this.ruleKind = ruleKind;
}
String getKeepRuleKind() {
return ruleKind;
}
}
private static void printNonEmptyMembersPatternAsDefaultInitWorkaround(StringBuilder builder) {
// 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 PgRule(KeepEdgeMetaInfo metaInfo, KeepOptions options) {
this.metaInfo = metaInfo;
this.options = options;
}
// Helper to print the class-name pattern in a class-item.
// The item is assumed to either be a binding (where the binding is a class with
// the supplied class-name pattern), or a class-item that has the class-name pattern itself (e.g.,
// without a binding indirection).
public static BiConsumer<StringBuilder, KeepClassReference> classReferencePrinter(
KeepQualifiedClassNamePattern classNamePattern) {
return (builder, classReference) -> {
assert classReference.isBindingReference()
|| classReference.asClassNamePattern().equals(classNamePattern);
RulePrintingUtils.printClassName(
classNamePattern, RulePrinter.withoutBackReferences(builder));
};
}
void printKeepOptions(StringBuilder builder) {
RulePrintingUtils.printKeepOptions(builder, options);
}
public void printRule(StringBuilder builder) {
RulePrintingUtils.printHeader(builder, metaInfo);
printCondition(builder);
printConsequence(builder);
}
void printCondition(StringBuilder builder) {
if (hasCondition()) {
builder.append(RulePrintingUtils.IF).append(' ');
printConditionHolder(builder);
List<String> members = getConditionMembers();
if (!members.isEmpty()) {
builder.append(" {");
for (String member : members) {
builder.append(' ');
printConditionMember(builder, member);
}
builder.append(" }");
}
builder.append(' ');
}
}
void printConsequence(StringBuilder builder) {
builder.append(getConsequenceKeepType());
printKeepOptions(builder);
builder.append(' ');
printTargetHolder(builder);
List<String> members = getTargetMembers();
if (!members.isEmpty()) {
builder.append(" {");
for (String member : members) {
builder.append(' ');
printTargetMember(builder, member);
}
builder.append(" }");
}
}
boolean hasCondition() {
return false;
}
List<String> getConditionMembers() {
throw new KeepEdgeException("Unreachable");
}
abstract String getConsequenceKeepType();
abstract List<String> getTargetMembers();
void printConditionHolder(StringBuilder builder) {
throw new KeepEdgeException("Unreachable");
}
void printConditionMember(StringBuilder builder, String member) {
throw new KeepEdgeException("Unreachable");
}
abstract void printTargetHolder(StringBuilder builder);
abstract void printTargetMember(StringBuilder builder, String 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 KeepItemPattern holderPattern;
private final TargetKeepKind targetKeepKind;
private final List<String> targetMembers;
private final Map<String, KeepMemberPattern> memberPatterns;
public PgUnconditionalRule(
KeepEdgeMetaInfo metaInfo,
Holder holder,
KeepOptions options,
Map<String, KeepMemberPattern> memberPatterns,
List<String> targetMembers,
TargetKeepKind targetKeepKind) {
super(metaInfo, options);
assert !targetKeepKind.equals(TargetKeepKind.JUST_MEMBERS);
this.holderNamePattern = holder.namePattern;
this.holderPattern = holder.itemPattern;
this.targetKeepKind = targetKeepKind;
this.memberPatterns = memberPatterns;
this.targetMembers = targetMembers;
}
@Override
String getConsequenceKeepType() {
return targetKeepKind.getKeepRuleKind();
}
@Override
List<String> getTargetMembers() {
return targetMembers;
}
@Override
void printTargetHolder(StringBuilder builder) {
printClassHeader(builder, holderPattern, classReferencePrinter(holderNamePattern));
if (getTargetMembers().isEmpty()) {
printNonEmptyMembersPatternAsDefaultInitWorkaround(builder);
}
}
@Override
void printTargetMember(StringBuilder builder, String memberReference) {
KeepMemberPattern memberPattern = memberPatterns.get(memberReference);
printMemberClause(memberPattern, RulePrinter.withoutBackReferences(builder));
}
}
/**
* 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 KeepItemPattern classCondition;
final KeepItemPattern classTarget;
final Map<String, KeepMemberPattern> memberPatterns;
final List<String> memberConditions;
private final List<String> memberTargets;
private final TargetKeepKind keepKind;
public PgConditionalRule(
KeepEdgeMetaInfo metaInfo,
KeepOptions options,
Holder classCondition,
Holder classTarget,
Map<String, KeepMemberPattern> memberPatterns,
List<String> memberConditions,
List<String> memberTargets,
TargetKeepKind keepKind) {
super(metaInfo, options);
this.classCondition = classCondition.itemPattern;
this.classTarget = classTarget.itemPattern;
this.memberPatterns = memberPatterns;
this.memberConditions = memberConditions;
this.memberTargets = memberTargets;
this.keepKind = keepKind;
}
@Override
boolean hasCondition() {
return true;
}
@Override
List<String> getConditionMembers() {
return memberConditions;
}
@Override
void printConditionHolder(StringBuilder builder) {
printClassHeader(builder, classCondition, this::printClassName);
}
@Override
void printConditionMember(StringBuilder builder, String member) {
KeepMemberPattern memberPattern = memberPatterns.get(member);
printMemberClause(memberPattern, RulePrinter.withoutBackReferences(builder));
}
@Override
void printTargetHolder(StringBuilder builder) {
printClassHeader(builder, classTarget, this::printClassName);
if (getTargetMembers().isEmpty()) {
PgRule.printNonEmptyMembersPatternAsDefaultInitWorkaround(builder);
}
}
@Override
String getConsequenceKeepType() {
return keepKind.getKeepRuleKind();
}
@Override
List<String> getTargetMembers() {
return memberTargets;
}
@Override
void printTargetMember(StringBuilder builder, String member) {
KeepMemberPattern memberPattern = memberPatterns.get(member);
printMemberClause(memberPattern, RulePrinter.withoutBackReferences(builder));
}
private void printClassName(StringBuilder builder, KeepClassReference clazz) {
RulePrintingUtils.printClassName(
clazz.asClassNamePattern(), 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 KeepItemPattern holderPattern;
private final Map<String, KeepMemberPattern> memberPatterns;
private final List<String> memberConditions;
private final List<String> memberTargets;
private final TargetKeepKind keepKind;
private int nextBackReferenceNumber = 1;
private String holderBackReferencePattern = null;
private Map<String, String> membersBackReferencePatterns = new HashMap<>();
public PgDependentMembersRule(
KeepEdgeMetaInfo metaInfo,
Holder holder,
KeepOptions options,
Map<String, KeepMemberPattern> memberPatterns,
List<String> memberConditions,
List<String> memberTargets,
TargetKeepKind keepKind) {
super(metaInfo, options);
this.holderNamePattern = holder.namePattern;
this.holderPattern = holder.itemPattern;
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<String> getConditionMembers() {
return memberConditions;
}
@Override
List<String> 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, String member) {
KeepMemberPattern memberPattern = memberPatterns.get(member);
BackReferencePrinter printer =
RulePrinter.withBackReferences(builder, this::getNextBackReferenceNumber);
printMemberClause(memberPattern, printer);
membersBackReferencePatterns.put(member, printer.getBackReference());
}
@Override
void printTargetHolder(StringBuilder builder) {
printClassHeader(
builder,
holderPattern,
(b, reference) -> {
assert reference.isBindingReference()
|| reference.asClassNamePattern().equals(holderNamePattern);
if (hasCondition()) {
b.append(holderBackReferencePattern);
} else {
assert holderBackReferencePattern == null;
RulePrintingUtils.printClassName(
holderNamePattern, RulePrinter.withoutBackReferences(builder));
}
});
if (getTargetMembers().isEmpty()) {
PgRule.printNonEmptyMembersPatternAsDefaultInitWorkaround(builder);
}
}
@Override
void printTargetMember(StringBuilder builder, String 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));
}
}
}