[KeepAnno] Define bindings as a static structural property
With this change the kind `CLASS_AND_MEMBERS` becomes a derived
form that is translated into a shared binding for the class and
members.
With this change the following internal properties have changed:
- An exact class name is never treated as a binding alias. The
bindings are now considered conjunctive constraints on the item
being matched. Thus two distinct bindings of the same class name
allow to express two disjunctive targets on that class.
- Kind on AST items is unused and can be removed in a followup.
- Items are now class or method (or *any*). It would make sense to
follow up with a further strengthening of this in the AST.
- The removal of the disjunctive forms via binding aliases results
in the output of multiple rules where before those would be in
an equivalent single rule. Follow up work should consider
improving on that.
Bug: b/248408342
Change-Id: I9f9055ce22dfa05bb4586fb9d5af538d50f21a5c
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java
index c988dc3..ad6caa7 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java
@@ -19,6 +19,7 @@
import com.android.tools.r8.keepanno.ast.AnnotationConstants.UsedByReflection;
import com.android.tools.r8.keepanno.ast.AnnotationConstants.UsesReflection;
import com.android.tools.r8.keepanno.ast.KeepBindings;
+import com.android.tools.r8.keepanno.ast.KeepBindings.BindingSymbol;
import com.android.tools.r8.keepanno.ast.KeepCheck;
import com.android.tools.r8.keepanno.ast.KeepCheck.KeepCheckKind;
import com.android.tools.r8.keepanno.ast.KeepClassReference;
@@ -49,11 +50,15 @@
import com.android.tools.r8.keepanno.ast.KeepQualifiedClassNamePattern;
import com.android.tools.r8.keepanno.ast.KeepTarget;
import com.android.tools.r8.keepanno.ast.KeepTypePattern;
+import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import java.util.function.BiPredicate;
import java.util.function.Consumer;
@@ -70,9 +75,9 @@
public static int ASM_VERSION = ASM9;
- public static Set<KeepDeclaration> readKeepEdges(byte[] classFileBytes) {
+ public static List<KeepDeclaration> readKeepEdges(byte[] classFileBytes) {
ClassReader reader = new ClassReader(classFileBytes);
- Set<KeepDeclaration> declarations = new HashSet<>();
+ List<KeepDeclaration> declarations = new ArrayList<>();
reader.accept(new KeepEdgeClassVisitor(declarations::add), ClassReader.SKIP_CODE);
return declarations;
}
@@ -336,10 +341,34 @@
}
}
+ private static class UserBindingsHelper {
+ private final KeepBindings.Builder builder = KeepBindings.builder();
+ private final Map<String, BindingSymbol> userNames = new HashMap<>();
+
+ public BindingSymbol resolveUserBinding(String name) {
+ return userNames.computeIfAbsent(name, builder::create);
+ }
+
+ public void defineUserBinding(String name, KeepItemPattern item) {
+ builder.addBinding(resolveUserBinding(name), item);
+ }
+
+ public BindingSymbol defineFreshBinding(String name, KeepItemPattern item) {
+ BindingSymbol symbol = builder.generateFreshSymbol(name);
+ builder.addBinding(symbol, item);
+ return symbol;
+ }
+
+ public KeepBindings build() {
+ return builder.build();
+ }
+ }
+
private static class KeepEdgeVisitor extends AnnotationVisitorBase {
private final Parent<KeepEdge> parent;
private final KeepEdge.Builder builder = KeepEdge.builder();
private final KeepEdgeMetaInfo.Builder metaInfoBuilder = KeepEdgeMetaInfo.builder();
+ private final UserBindingsHelper bindingsHelper = new UserBindingsHelper();
KeepEdgeVisitor(Parent<KeepEdge> parent, Consumer<KeepEdgeMetaInfo.Builder> addContext) {
this.parent = parent;
@@ -363,20 +392,23 @@
@Override
public AnnotationVisitor visitArray(String name) {
if (name.equals(Edge.bindings)) {
- return new KeepBindingsVisitor(getAnnotationName(), builder::setBindings);
+ return new KeepBindingsVisitor(getAnnotationName(), bindingsHelper);
}
if (name.equals(Edge.preconditions)) {
- return new KeepPreconditionsVisitor(getAnnotationName(), builder::setPreconditions);
+ return new KeepPreconditionsVisitor(
+ getAnnotationName(), builder::setPreconditions, bindingsHelper);
}
if (name.equals(Edge.consequences)) {
- return new KeepConsequencesVisitor(getAnnotationName(), builder::setConsequences);
+ return new KeepConsequencesVisitor(
+ getAnnotationName(), builder::setConsequences, bindingsHelper);
}
return super.visitArray(name);
}
@Override
public void visitEnd() {
- parent.accept(builder.setMetaInfo(metaInfoBuilder.build()).build());
+ parent.accept(
+ builder.setMetaInfo(metaInfoBuilder.build()).setBindings(bindingsHelper.build()).build());
}
}
@@ -393,6 +425,7 @@
private final KeepEdge.Builder builder = KeepEdge.builder();
private final KeepConsequences.Builder consequences = KeepConsequences.builder();
private final KeepEdgeMetaInfo.Builder metaInfoBuilder = KeepEdgeMetaInfo.builder();
+ private final UserBindingsHelper bindingsHelper = new UserBindingsHelper();
ForApiClassVisitor(
Parent<KeepEdge> parent, Consumer<KeepEdgeMetaInfo.Builder> addContext, String className) {
@@ -406,6 +439,11 @@
}
@Override
+ public UserBindingsHelper getBindingsHelper() {
+ return bindingsHelper;
+ }
+
+ @Override
public String getAnnotationName() {
return ForApi.CLASS.getSimpleName();
}
@@ -426,41 +464,47 @@
getAnnotationName(),
additionalConsequences -> {
additionalConsequences.forEachTarget(consequences::addTarget);
- });
+ },
+ bindingsHelper);
}
return super.visitArray(name);
}
@Override
public void visitEnd() {
- if (!getKind().equals(KeepItemKind.ONLY_CLASS) && isDefaultMemberDeclaration()) {
+ if (!getKind().equals(Kind.ONLY_CLASS) && isDefaultMemberDeclaration()) {
// If no member declarations have been made, set public & protected as the default.
AnnotationVisitor v = visitArray(Item.memberAccess);
v.visitEnum(null, MemberAccess.DESCRIPTOR, MemberAccess.PUBLIC);
v.visitEnum(null, MemberAccess.DESCRIPTOR, MemberAccess.PROTECTED);
}
super.visitEnd();
- KeepItemReference item = getItemReference();
- if (item.isBindingReference()) {
- throw new KeepEdgeException("@KeepForApi cannot reference bindings");
+ Collection<KeepItemReference> items = getItemsWithoutBinding();
+ for (KeepItemReference item : items) {
+ if (item.isBindingReference()) {
+ throw new KeepEdgeException("@KeepForApi cannot reference bindings");
+ }
+ KeepItemPattern itemPattern = item.asItemPattern();
+ String descriptor = AnnotationConstants.getDescriptorFromClassTypeName(className);
+ String itemDescriptor =
+ itemPattern.getClassReference().asClassNamePattern().getExactDescriptor();
+ if (!descriptor.equals(itemDescriptor)) {
+ throw new KeepEdgeException("@KeepForApi must reference its class context " + className);
+ }
+ if (itemPattern.getKind().equals(KeepItemKind.ONLY_MEMBERS)) {
+ if (items.size() == 1) {
+ throw new KeepEdgeException("@KeepForApi kind must include its class");
+ }
+ }
+ if (!itemPattern.getExtendsPattern().isAny()) {
+ throw new KeepEdgeException("@KeepForApi cannot define an 'extends' pattern.");
+ }
+ consequences.addTarget(KeepTarget.builder().setItemReference(item).build());
}
- KeepItemPattern itemPattern = item.asItemPattern();
- String descriptor = AnnotationConstants.getDescriptorFromClassTypeName(className);
- String itemDescriptor =
- itemPattern.getClassReference().asClassNamePattern().getExactDescriptor();
- if (!descriptor.equals(itemDescriptor)) {
- throw new KeepEdgeException("@KeepForApi must reference its class context " + className);
- }
- if (itemPattern.getKind().equals(KeepItemKind.ONLY_MEMBERS)) {
- throw new KeepEdgeException("@KeepForApi kind must include its class");
- }
- if (!itemPattern.getExtendsPattern().isAny()) {
- throw new KeepEdgeException("@KeepForApi cannot define an 'extends' pattern.");
- }
- consequences.addTarget(KeepTarget.builder().setItemPattern(itemPattern).build());
parent.accept(
builder
.setMetaInfo(metaInfoBuilder.build())
+ .setBindings(bindingsHelper.build())
.setConsequences(consequences.build())
.build());
}
@@ -475,7 +519,7 @@
private final Parent<KeepEdge> parent;
private final KeepEdge.Builder builder = KeepEdge.builder();
private final KeepEdgeMetaInfo.Builder metaInfoBuilder = KeepEdgeMetaInfo.builder();
-
+ private final UserBindingsHelper bindingsHelper = new UserBindingsHelper();
private final KeepConsequences.Builder consequences = KeepConsequences.builder();
ForApiMemberVisitor(
@@ -484,14 +528,19 @@
KeepItemPattern context) {
this.parent = parent;
addContext.accept(metaInfoBuilder);
+ BindingSymbol contextBinding = bindingsHelper.defineFreshBinding("CONTEXT", context);
+ // Create a binding for the context such that the class and member are shared.
consequences.addTarget(
KeepTarget.builder()
.setItemPattern(
KeepItemPattern.builder()
- .copyFrom(context)
- .setKind(KeepItemKind.CLASS_AND_MEMBERS)
+ .setClassReference(KeepClassReference.fromBindingReference(contextBinding))
.build())
.build());
+ consequences.addTarget(
+ KeepTarget.builder()
+ .setItemReference(KeepItemReference.fromBindingReference(contextBinding))
+ .build());
}
@Override
@@ -515,7 +564,8 @@
getAnnotationName(),
additionalConsequences -> {
additionalConsequences.forEachTarget(consequences::addTarget);
- });
+ },
+ bindingsHelper);
}
return super.visitArray(name);
}
@@ -525,6 +575,7 @@
parent.accept(
builder
.setMetaInfo(metaInfoBuilder.build())
+ .setBindings(bindingsHelper.build())
.setConsequences(consequences.build())
.build());
}
@@ -544,6 +595,7 @@
private final KeepEdge.Builder builder = KeepEdge.builder();
private final KeepConsequences.Builder consequences = KeepConsequences.builder();
private final KeepEdgeMetaInfo.Builder metaInfoBuilder = KeepEdgeMetaInfo.builder();
+ private final UserBindingsHelper bindingsHelper = new UserBindingsHelper();
UsedByReflectionClassVisitor(
String annotationDescriptor,
@@ -559,6 +611,11 @@
}
@Override
+ public UserBindingsHelper getBindingsHelper() {
+ return bindingsHelper;
+ }
+
+ @Override
public String getAnnotationName() {
int sep = annotationDescriptor.lastIndexOf('/');
return annotationDescriptor.substring(sep + 1, annotationDescriptor.length() - 1);
@@ -576,14 +633,16 @@
@Override
public AnnotationVisitor visitArray(String name) {
if (name.equals(Edge.preconditions)) {
- return new KeepPreconditionsVisitor(getAnnotationName(), builder::setPreconditions);
+ return new KeepPreconditionsVisitor(
+ getAnnotationName(), builder::setPreconditions, bindingsHelper);
}
if (name.equals(UsedByReflection.additionalTargets)) {
return new KeepConsequencesVisitor(
getAnnotationName(),
additionalConsequences -> {
additionalConsequences.forEachTarget(consequences::addTarget);
- });
+ },
+ bindingsHelper);
}
return super.visitArray(name);
}
@@ -595,30 +654,35 @@
visitEnum(null, Kind.DESCRIPTOR, Kind.CLASS_AND_MEMBERS);
}
super.visitEnd();
- KeepItemReference item = getItemReference();
- if (item.isBindingReference()) {
- // TODO(b/248408342): The edge can have preconditions so it should support bindings!
- throw new KeepEdgeException("@" + getAnnotationName() + " cannot reference bindings");
+ Collection<KeepItemReference> items = getItemsWithoutBinding();
+ for (KeepItemReference item : items) {
+ if (item.isBindingReference()) {
+ // TODO(b/248408342): The edge can have preconditions so it should support bindings!
+ throw new KeepEdgeException("@" + getAnnotationName() + " cannot reference bindings");
+ }
+ KeepItemPattern itemPattern = item.asItemPattern();
+ String descriptor = AnnotationConstants.getDescriptorFromClassTypeName(className);
+ String itemDescriptor =
+ itemPattern.getClassReference().asClassNamePattern().getExactDescriptor();
+ if (!descriptor.equals(itemDescriptor)) {
+ throw new KeepEdgeException(
+ "@" + getAnnotationName() + " must reference its class context " + className);
+ }
+ if (itemPattern.getKind().equals(KeepItemKind.ONLY_MEMBERS)) {
+ if (items.size() == 1) {
+ throw new KeepEdgeException("@" + getAnnotationName() + " kind must include its class");
+ }
+ }
+ if (!itemPattern.getExtendsPattern().isAny()) {
+ throw new KeepEdgeException(
+ "@" + getAnnotationName() + " cannot define an 'extends' pattern.");
+ }
+ consequences.addTarget(KeepTarget.builder().setItemPattern(itemPattern).build());
}
- KeepItemPattern itemPattern = item.asItemPattern();
- String descriptor = AnnotationConstants.getDescriptorFromClassTypeName(className);
- String itemDescriptor =
- itemPattern.getClassReference().asClassNamePattern().getExactDescriptor();
- if (!descriptor.equals(itemDescriptor)) {
- throw new KeepEdgeException(
- "@" + getAnnotationName() + " must reference its class context " + className);
- }
- if (itemPattern.getKind().equals(KeepItemKind.ONLY_MEMBERS)) {
- throw new KeepEdgeException("@" + getAnnotationName() + " kind must include its class");
- }
- if (!itemPattern.getExtendsPattern().isAny()) {
- throw new KeepEdgeException(
- "@" + getAnnotationName() + " cannot define an 'extends' pattern.");
- }
- consequences.addTarget(KeepTarget.builder().setItemPattern(itemPattern).build());
parent.accept(
builder
.setMetaInfo(metaInfoBuilder.build())
+ .setBindings(bindingsHelper.build())
.setConsequences(consequences.build())
.build());
}
@@ -635,7 +699,7 @@
private final KeepItemPattern context;
private final KeepEdge.Builder builder = KeepEdge.builder();
private final KeepEdgeMetaInfo.Builder metaInfoBuilder = KeepEdgeMetaInfo.builder();
-
+ private final UserBindingsHelper bindingsHelper = new UserBindingsHelper();
private final KeepConsequences.Builder consequences = KeepConsequences.builder();
private KeepItemKind kind = KeepItemKind.ONLY_MEMBERS;
@@ -691,14 +755,16 @@
@Override
public AnnotationVisitor visitArray(String name) {
if (name.equals(Edge.preconditions)) {
- return new KeepPreconditionsVisitor(getAnnotationName(), builder::setPreconditions);
+ return new KeepPreconditionsVisitor(
+ getAnnotationName(), builder::setPreconditions, bindingsHelper);
}
if (name.equals(UsedByReflection.additionalTargets)) {
return new KeepConsequencesVisitor(
getAnnotationName(),
additionalConsequences -> {
additionalConsequences.forEachTarget(consequences::addTarget);
- });
+ },
+ bindingsHelper);
}
return super.visitArray(name);
}
@@ -708,13 +774,21 @@
if (kind.equals(KeepItemKind.ONLY_CLASS)) {
throw new KeepEdgeException("@" + getAnnotationName() + " kind must include its member");
}
- consequences.addTarget(
- KeepTarget.builder()
- .setItemPattern(KeepItemPattern.builder().copyFrom(context).setKind(kind).build())
- .build());
+ assert context.getKind() == KeepItemKind.ONLY_MEMBERS;
+ if (kind.equals(KeepItemKind.CLASS_AND_MEMBERS)) {
+ consequences.addTarget(
+ KeepTarget.builder()
+ .setItemPattern(
+ KeepItemPattern.builder()
+ .setClassReference(context.getClassReference())
+ .build())
+ .build());
+ }
+ consequences.addTarget(KeepTarget.builder().setItemPattern(context).build());
parent.accept(
builder
.setMetaInfo(metaInfoBuilder.build())
+ .setBindings(bindingsHelper.build())
.setConsequences(consequences.build())
.build());
}
@@ -725,6 +799,7 @@
private final KeepEdge.Builder builder = KeepEdge.builder();
private final KeepPreconditions.Builder preconditions = KeepPreconditions.builder();
private final KeepEdgeMetaInfo.Builder metaInfoBuilder = KeepEdgeMetaInfo.builder();
+ private final UserBindingsHelper bindingsHelper = new UserBindingsHelper();
UsesReflectionVisitor(
Parent<KeepEdge> parent,
@@ -752,14 +827,16 @@
@Override
public AnnotationVisitor visitArray(String name) {
if (name.equals(AnnotationConstants.UsesReflection.value)) {
- return new KeepConsequencesVisitor(getAnnotationName(), builder::setConsequences);
+ return new KeepConsequencesVisitor(
+ getAnnotationName(), builder::setConsequences, bindingsHelper);
}
if (name.equals(AnnotationConstants.UsesReflection.additionalPreconditions)) {
return new KeepPreconditionsVisitor(
getAnnotationName(),
additionalPreconditions -> {
additionalPreconditions.forEach(preconditions::addCondition);
- });
+ },
+ bindingsHelper);
}
return super.visitArray(name);
}
@@ -769,6 +846,7 @@
parent.accept(
builder
.setMetaInfo(metaInfoBuilder.build())
+ .setBindings(bindingsHelper.build())
.setPreconditions(preconditions.build())
.build());
}
@@ -776,12 +854,11 @@
private static class KeepBindingsVisitor extends AnnotationVisitorBase {
private final String annotationName;
- private final Parent<KeepBindings> parent;
- private final KeepBindings.Builder builder = KeepBindings.builder();
+ private final UserBindingsHelper helper;
- public KeepBindingsVisitor(String annotationName, Parent<KeepBindings> parent) {
+ public KeepBindingsVisitor(String annotationName, UserBindingsHelper helper) {
this.annotationName = annotationName;
- this.parent = parent;
+ this.helper = helper;
}
@Override
@@ -792,25 +869,25 @@
@Override
public AnnotationVisitor visitAnnotation(String name, String descriptor) {
if (descriptor.equals(AnnotationConstants.Binding.DESCRIPTOR)) {
- return new KeepBindingVisitor(builder);
+ return new KeepBindingVisitor(helper);
}
return super.visitAnnotation(name, descriptor);
}
-
- @Override
- public void visitEnd() {
- parent.accept(builder.build());
- }
}
private static class KeepPreconditionsVisitor extends AnnotationVisitorBase {
private final String annotationName;
private final Parent<KeepPreconditions> parent;
private final KeepPreconditions.Builder builder = KeepPreconditions.builder();
+ private final UserBindingsHelper bindingsHelper;
- public KeepPreconditionsVisitor(String annotationName, Parent<KeepPreconditions> parent) {
+ public KeepPreconditionsVisitor(
+ String annotationName,
+ Parent<KeepPreconditions> parent,
+ UserBindingsHelper bindingsHelper) {
this.annotationName = annotationName;
this.parent = parent;
+ this.bindingsHelper = bindingsHelper;
}
@Override
@@ -821,7 +898,7 @@
@Override
public AnnotationVisitor visitAnnotation(String name, String descriptor) {
if (descriptor.equals(Condition.DESCRIPTOR)) {
- return new KeepConditionVisitor(builder::addCondition);
+ return new KeepConditionVisitor(builder::addCondition, bindingsHelper);
}
return super.visitAnnotation(name, descriptor);
}
@@ -836,10 +913,13 @@
private final String annotationName;
private final Parent<KeepConsequences> parent;
private final KeepConsequences.Builder builder = KeepConsequences.builder();
+ private final UserBindingsHelper bindingsHelper;
- public KeepConsequencesVisitor(String annotationName, Parent<KeepConsequences> parent) {
+ public KeepConsequencesVisitor(
+ String annotationName, Parent<KeepConsequences> parent, UserBindingsHelper bindingsHelper) {
this.annotationName = annotationName;
this.parent = parent;
+ this.bindingsHelper = bindingsHelper;
}
@Override
@@ -850,7 +930,7 @@
@Override
public AnnotationVisitor visitAnnotation(String name, String descriptor) {
if (descriptor.equals(Target.DESCRIPTOR)) {
- return KeepTargetVisitor.create(builder::addTarget);
+ return KeepTargetVisitor.create(builder::addTarget, bindingsHelper);
}
return super.visitAnnotation(name, descriptor);
}
@@ -904,6 +984,11 @@
KeepItemVisitorBase itemVisitor =
new KeepItemVisitorBase() {
@Override
+ public UserBindingsHelper getBindingsHelper() {
+ throw new KeepEdgeException("Bindings not supported in @" + getAnnotationName());
+ }
+
+ @Override
public String getAnnotationName() {
return superVisitor.getAnnotationName();
}
@@ -1052,6 +1137,13 @@
}
private static class ClassDeclaration extends SingleDeclaration<KeepClassReference> {
+
+ private final Supplier<UserBindingsHelper> getBindingsHelper;
+
+ public ClassDeclaration(Supplier<UserBindingsHelper> getBindingsHelper) {
+ this.getBindingsHelper = getBindingsHelper;
+ }
+
@Override
String kind() {
return "class";
@@ -1069,7 +1161,8 @@
@Override
KeepClassReference parse(String name, Object value) {
if (name.equals(Item.classFromBinding) && value instanceof String) {
- return KeepClassReference.fromBindingReference((String) value);
+ BindingSymbol symbol = getBindingsHelper.get().resolveUserBinding((String) value);
+ return KeepClassReference.fromBindingReference(symbol);
}
if (name.equals(Item.classConstant) && value instanceof Type) {
return wrap(KeepQualifiedClassNamePattern.exact(((Type) value).getClassName()));
@@ -1320,11 +1413,13 @@
private abstract static class KeepItemVisitorBase extends AnnotationVisitorBase {
private String memberBindingReference = null;
- private KeepItemKind kind = null;
- private final ClassDeclaration classDeclaration = new ClassDeclaration();
+ private String kind = null;
+ private final ClassDeclaration classDeclaration = new ClassDeclaration(this::getBindingsHelper);
private final ExtendsDeclaration extendsDeclaration = new ExtendsDeclaration();
private final MemberDeclaration memberDeclaration;
+ public abstract UserBindingsHelper getBindingsHelper();
+
// Constructed item available once visitEnd has been called.
private KeepItemReference itemReference = null;
@@ -1332,6 +1427,75 @@
memberDeclaration = new MemberDeclaration(this::getAnnotationName);
}
+ public Collection<KeepItemReference> getItemsWithoutBinding() {
+ if (itemReference == null) {
+ throw new KeepEdgeException("Item reference not finalized. Missing call to visitEnd()");
+ }
+ if (Kind.CLASS_AND_MEMBERS.equals(kind)) {
+ // If kind is set then visitEnd ensures that this cannot be a binding reference.
+ assert !itemReference.isBindingReference();
+ KeepItemPattern itemPattern = itemReference.asItemPattern();
+ KeepItemPattern classPattern;
+ if (itemPattern.isClassItemPattern()) {
+ classPattern = itemPattern;
+ } else {
+ classPattern =
+ KeepItemPattern.builder()
+ .copyFrom(itemPattern)
+ .setKind(KeepItemKind.ONLY_CLASS)
+ .setMemberPattern(KeepMemberPattern.none())
+ .build();
+ }
+ KeepItemPattern memberPattern =
+ KeepItemPattern.builder()
+ .copyFrom(itemPattern)
+ .setKind(KeepItemKind.ONLY_MEMBERS)
+ .build();
+ return ImmutableList.of(
+ KeepItemReference.fromItemPattern(classPattern),
+ KeepItemReference.fromItemPattern(memberPattern));
+ } else {
+ return Collections.singletonList(itemReference);
+ }
+ }
+
+ public Collection<KeepItemReference> getItemsWithBinding() {
+ if (itemReference == null) {
+ throw new KeepEdgeException("Item reference not finalized. Missing call to visitEnd()");
+ }
+ if (itemReference.isBindingReference()) {
+ return Collections.singletonList(itemReference);
+ }
+ // Kind is only null if item is a "binding reference".
+ assert kind != null;
+ if (Kind.CLASS_AND_MEMBERS.equals(kind)) {
+ KeepItemPattern itemPattern = itemReference.asItemPattern();
+ KeepItemPattern classPattern;
+ if (itemPattern.isClassItemPattern()) {
+ classPattern = itemPattern;
+ } else {
+ classPattern =
+ KeepItemPattern.builder()
+ .copyFrom(itemPattern)
+ .setKind(KeepItemKind.ONLY_CLASS)
+ .setMemberPattern(KeepMemberPattern.none())
+ .build();
+ }
+ BindingSymbol symbol = getBindingsHelper().defineFreshBinding("CLASS", classPattern);
+ KeepItemPattern memberPattern =
+ KeepItemPattern.builder()
+ .copyFrom(itemPattern)
+ .setClassReference(KeepClassReference.fromBindingReference(symbol))
+ .setKind(KeepItemKind.ONLY_MEMBERS)
+ .build();
+ return ImmutableList.of(
+ KeepItemReference.fromItemPattern(classPattern),
+ KeepItemReference.fromItemPattern(memberPattern));
+ } else {
+ return Collections.singletonList(itemReference);
+ }
+ }
+
public KeepItemReference getItemReference() {
if (itemReference == null) {
throw new KeepEdgeException("Item reference not finalized. Missing call to visitEnd()");
@@ -1339,7 +1503,7 @@
return itemReference;
}
- public KeepItemKind getKind() {
+ public String getKind() {
return kind;
}
@@ -1357,13 +1521,9 @@
// The default value is obtained by not assigning a kind (e.g., null in the builder).
break;
case Kind.ONLY_CLASS:
- kind = KeepItemKind.ONLY_CLASS;
- break;
case Kind.ONLY_MEMBERS:
- kind = KeepItemKind.ONLY_MEMBERS;
- break;
case Kind.CLASS_AND_MEMBERS:
- kind = KeepItemKind.CLASS_AND_MEMBERS;
+ kind = value;
break;
default:
super.visitEnum(name, descriptor, value);
@@ -1403,21 +1563,21 @@
throw new KeepEdgeException(
"Cannot define an item explicitly and via a member-binding reference");
}
- itemReference = KeepItemReference.fromBindingReference(memberBindingReference);
+ BindingSymbol symbol = getBindingsHelper().resolveUserBinding(memberBindingReference);
+ itemReference = KeepItemReference.fromBindingReference(symbol);
} else {
KeepMemberPattern memberPattern = memberDeclaration.getValue();
// If the kind is not set (default) then the content of the members determines the kind.
if (kind == null) {
- kind = memberPattern.isNone() ? KeepItemKind.ONLY_CLASS : KeepItemKind.ONLY_MEMBERS;
+ kind = memberPattern.isNone() ? Kind.ONLY_CLASS : Kind.ONLY_MEMBERS;
}
// If the kind is a member kind and no member pattern is set then set members to all.
- if (!kind.equals(KeepItemKind.ONLY_CLASS) && memberPattern.isNone()) {
+ if (!kind.equals(Kind.ONLY_CLASS) && memberPattern.isNone()) {
memberPattern = KeepMemberPattern.allMembers();
}
itemReference =
KeepItemReference.fromItemPattern(
KeepItemPattern.builder()
- .setKind(kind)
.setClassReference(classDeclaration.getValue())
.setExtendsPattern(extendsDeclaration.getValue())
.setMemberPattern(memberPattern)
@@ -1428,11 +1588,16 @@
private static class KeepBindingVisitor extends KeepItemVisitorBase {
- private final KeepBindings.Builder builder;
+ private final UserBindingsHelper helper;
private String bindingName;
- public KeepBindingVisitor(KeepBindings.Builder builder) {
- this.builder = builder;
+ public KeepBindingVisitor(UserBindingsHelper helper) {
+ this.helper = helper;
+ }
+
+ @Override
+ public UserBindingsHelper getBindingsHelper() {
+ return helper;
}
@Override
@@ -1453,7 +1618,7 @@
public void visitEnd() {
super.visitEnd();
KeepItemReference item = getItemReference();
- // The language currently disallows aliasing bindings, thus a binding should directly be
+ // The language currently disallows aliasing bindings, thus a binding cannot directly be
// defined by a reference to another binding.
if (item.isBindingReference()) {
throw new KeepEdgeException(
@@ -1463,7 +1628,7 @@
+ bindingName
+ "'");
}
- builder.addBinding(bindingName, item.asItemPattern());
+ helper.defineUserBinding(bindingName, item.asItemPattern());
}
}
@@ -1543,13 +1708,20 @@
private final KeepTarget.Builder builder = KeepTarget.builder();
private final OptionsDeclaration optionsDeclaration =
new OptionsDeclaration(getAnnotationName());
+ private final UserBindingsHelper bindingsHelper;
- static KeepTargetVisitor create(Parent<KeepTarget> parent) {
- return new KeepTargetVisitor(parent);
+ static KeepTargetVisitor create(Parent<KeepTarget> parent, UserBindingsHelper bindingsHelper) {
+ return new KeepTargetVisitor(parent, bindingsHelper);
}
- private KeepTargetVisitor(Parent<KeepTarget> parent) {
+ private KeepTargetVisitor(Parent<KeepTarget> parent, UserBindingsHelper bindingsHelper) {
this.parent = parent;
+ this.bindingsHelper = bindingsHelper;
+ }
+
+ @Override
+ public UserBindingsHelper getBindingsHelper() {
+ return bindingsHelper;
}
@Override
@@ -1569,16 +1741,25 @@
@Override
public void visitEnd() {
super.visitEnd();
- parent.accept(builder.setItemReference(getItemReference()).build());
+ for (KeepItemReference item : getItemsWithBinding()) {
+ parent.accept(builder.setItemReference(item).build());
+ }
}
}
private static class KeepConditionVisitor extends KeepItemVisitorBase {
private final Parent<KeepCondition> parent;
+ private final UserBindingsHelper bindingsHelper;
- public KeepConditionVisitor(Parent<KeepCondition> parent) {
+ public KeepConditionVisitor(Parent<KeepCondition> parent, UserBindingsHelper bindingsHelper) {
this.parent = parent;
+ this.bindingsHelper = bindingsHelper;
+ }
+
+ @Override
+ public UserBindingsHelper getBindingsHelper() {
+ return bindingsHelper;
}
@Override
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepBindings.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepBindings.java
index 21692c9..f94af20 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepBindings.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepBindings.java
@@ -5,6 +5,7 @@
import java.util.Collections;
import java.util.HashMap;
+import java.util.IdentityHashMap;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;
@@ -17,9 +18,9 @@
private static final KeepBindings NONE_INSTANCE = new KeepBindings(Collections.emptyMap());
- private final Map<String, Binding> bindings;
+ private final Map<BindingSymbol, Binding> bindings;
- private KeepBindings(Map<String, Binding> bindings) {
+ private KeepBindings(Map<BindingSymbol, Binding> bindings) {
assert bindings != null;
this.bindings = bindings;
}
@@ -28,7 +29,7 @@
return NONE_INSTANCE;
}
- public Binding get(String bindingReference) {
+ public Binding get(BindingSymbol bindingReference) {
return bindings.get(bindingReference);
}
@@ -40,7 +41,7 @@
return bindings.isEmpty();
}
- public void forEach(BiConsumer<String, KeepItemPattern> fn) {
+ public void forEach(BiConsumer<BindingSymbol, KeepItemPattern> fn) {
bindings.forEach((name, binding) -> fn.accept(name, binding.getItem()));
}
@@ -57,7 +58,7 @@
// If the outer-most item has been judged to be "any" then we internally only need to check
// that the class-name pattern itself is "any". The class-name could potentially reference names
// of other item bindings so this is a recursive search.
- private boolean isAnyClassNamePattern(String bindingName) {
+ private boolean isAnyClassNamePattern(BindingSymbol bindingName) {
KeepClassReference classReference = get(bindingName).getItem().getClassReference();
return classReference.isBindingReference()
? isAnyClassNamePattern(classReference.asBindingReference())
@@ -75,8 +76,31 @@
/**
* A unique binding.
*
- * <p>The uniqueness / identity of a binding is critical as a binding denotes a concrete match in
- * the precondition of a rule. In terms of proguard keep rules it provides the difference of:
+ * <p>The uniqueness / identity of a binding is critical as a binding denotes both the static
+ * structural constraint on an item and potentially the identity of a concrete match in the
+ * precondition of a rule.
+ *
+ * <p>In terms of proguard keep rules it provides the difference of structural constraints
+ * illustrated by:
+ *
+ * <pre>
+ * -keepclasswithmembers class *Foo { void bar(); void baz(); }
+ * </pre>
+ *
+ * and
+ *
+ * <pre>
+ * -keepclasswithmembers class *Foo { void bar(); }
+ * -keepclasswithmembers class *Foo { void baz(); }
+ * </pre>
+ *
+ * The former which only matches classes with the Foo suffix that have both bar and baz can be
+ * expressed with a binding of *Foo and the two method items expressed by referencing the shared
+ * class. Without the binding the targets will give rise to the latter rules which are independent
+ * of each other.
+ *
+ * <p>In terms of proguard keep rules it also provides the difference in back-referencing into
+ * preconditions:
*
* <pre>
* -if class *Foo -keep class *Foo { void <init>(...); }
@@ -121,37 +145,104 @@
}
}
- public static class Builder {
- private final Map<String, KeepItemPattern> bindings = new HashMap<>();
+ public static class BindingSymbol {
+ private final String hint;
+ private String suffix = "";
- public Builder addBinding(String name, KeepItemPattern itemPattern) {
- if (name == null || itemPattern == null) {
- throw new KeepEdgeException("Invalid binding of '" + name + "'");
- }
- KeepItemPattern old = bindings.put(name, itemPattern);
+ public BindingSymbol(String hint) {
+ this.hint = hint;
+ }
+
+ private void setSuffix(String suffix) {
+ this.suffix = suffix;
+ }
+
+ @Override
+ public String toString() {
+ return hint + suffix;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return this == obj;
+ }
+
+ @Override
+ public int hashCode() {
+ return System.identityHashCode(this);
+ }
+ }
+
+ public static class Builder {
+
+ private final Map<String, BindingSymbol> reserved = new HashMap<>();
+ private final Map<BindingSymbol, KeepItemPattern> bindings = new IdentityHashMap<>();
+
+ public BindingSymbol generateFreshSymbol(String hint) {
+ // Allocate a fresh non-forgeable symbol. The actual name is chosen at build time.
+ return new BindingSymbol(hint);
+ }
+
+ public BindingSymbol create(String name) {
+ BindingSymbol symbol = new BindingSymbol(name);
+ BindingSymbol old = reserved.put(name, symbol);
if (old != null) {
- throw new KeepEdgeException("Multiple definitions for binding '" + name + "'");
+ throw new KeepEdgeException("Multiple bindings with name '" + name + "'");
+ }
+ return symbol;
+ }
+
+ public Builder addBinding(BindingSymbol symbol, KeepItemPattern itemPattern) {
+ if (symbol == null || itemPattern == null) {
+ throw new KeepEdgeException("Invalid binding of '" + symbol + "'");
+ }
+ KeepItemPattern old = bindings.put(symbol, itemPattern);
+ if (old != null) {
+ throw new KeepEdgeException("Multiple definitions for binding '" + symbol + "'");
}
return this;
}
+ public BindingSymbol getClassBinding(BindingSymbol bindingSymbol) {
+ KeepItemPattern pattern = bindings.get(bindingSymbol);
+ if (pattern.isClassItemPattern()) {
+ return bindingSymbol;
+ }
+ return pattern.getClassReference().asBindingReference();
+ }
+
public KeepBindings build() {
if (bindings.isEmpty()) {
return NONE_INSTANCE;
}
- Map<String, Binding> definitions = new HashMap<>(bindings.size());
- for (String name : bindings.keySet()) {
- definitions.put(name, verifyAndCreateBinding(name));
+ Map<BindingSymbol, Binding> definitions = new HashMap<>(bindings.size());
+ for (BindingSymbol symbol : bindings.keySet()) {
+ // The reserved symbols are a subset of all symbols. Those that are not yet reserved denote
+ // symbols that must be "unique" in the set of symbols, but that do not have a specific
+ // name. Now that all symbols are known we can give each of these a unique name.
+ BindingSymbol defined = reserved.get(symbol.toString());
+ if (defined != symbol) {
+ // For each undefined symbol we try to use the "hint" as its name, if the name is already
+ // reserved for another symbol then we search for the first non-reserved name with an
+ // integer suffix.
+ int i = 0;
+ while (defined != null) {
+ symbol.setSuffix(Integer.toString(++i));
+ defined = reserved.get(symbol.toString());
+ }
+ reserved.put(symbol.toString(), symbol);
+ }
+ definitions.put(symbol, verifyAndCreateBinding(symbol));
}
return new KeepBindings(definitions);
}
- private Binding verifyAndCreateBinding(String bindingDefinitionName) {
- KeepItemPattern pattern = bindings.get(bindingDefinitionName);
- for (String bindingReference : pattern.getBindingReferences()) {
+ private Binding verifyAndCreateBinding(BindingSymbol bindingDefinitionSymbol) {
+ KeepItemPattern pattern = bindings.get(bindingDefinitionSymbol);
+ for (BindingSymbol bindingReference : pattern.getBindingReferences()) {
// Currently, it is not possible to define mutually recursive items, so we only need
// to check against self.
- if (bindingReference.equals(bindingDefinitionName)) {
+ if (bindingReference.equals(bindingDefinitionSymbol)) {
throw new KeepEdgeException("Recursive binding for name '" + bindingReference + "'");
}
if (!bindings.containsKey(bindingReference)) {
@@ -159,7 +250,7 @@
"Undefined binding for name '"
+ bindingReference
+ "' referenced in binding of '"
- + bindingDefinitionName
+ + bindingDefinitionSymbol
+ "'");
}
}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepClassReference.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepClassReference.java
index 7397be7..250152a 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepClassReference.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepClassReference.java
@@ -3,13 +3,14 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.keepanno.ast;
+import com.android.tools.r8.keepanno.ast.KeepBindings.BindingSymbol;
import java.util.Collection;
import java.util.Collections;
import java.util.function.Predicate;
public abstract class KeepClassReference {
- public static KeepClassReference fromBindingReference(String bindingReference) {
+ public static KeepClassReference fromBindingReference(BindingSymbol bindingReference) {
return new BindingReference(bindingReference);
}
@@ -26,7 +27,7 @@
return asClassNamePattern() != null;
}
- public String asBindingReference() {
+ public BindingSymbol asBindingReference() {
return null;
}
@@ -34,29 +35,29 @@
return null;
}
- public abstract Collection<String> getBindingReferences();
+ public abstract Collection<BindingSymbol> getBindingReferences();
- public boolean isAny(Predicate<String> onReference) {
+ public boolean isAny(Predicate<BindingSymbol> onReference) {
return isBindingReference()
? onReference.test(asBindingReference())
: asClassNamePattern().isAny();
}
private static class BindingReference extends KeepClassReference {
- private final String bindingReference;
+ private final BindingSymbol bindingReference;
- private BindingReference(String bindingReference) {
+ private BindingReference(BindingSymbol bindingReference) {
assert bindingReference != null;
this.bindingReference = bindingReference;
}
@Override
- public String asBindingReference() {
+ public BindingSymbol asBindingReference() {
return bindingReference;
}
@Override
- public Collection<String> getBindingReferences() {
+ public Collection<BindingSymbol> getBindingReferences() {
return Collections.singletonList(bindingReference);
}
@@ -79,7 +80,7 @@
@Override
public String toString() {
- return bindingReference;
+ return bindingReference.toString();
}
}
@@ -97,7 +98,7 @@
}
@Override
- public Collection<String> getBindingReferences() {
+ public Collection<BindingSymbol> getBindingReferences() {
return Collections.emptyList();
}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepDeclaration.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepDeclaration.java
index 62cb663..bfcad37 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepDeclaration.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepDeclaration.java
@@ -21,4 +21,14 @@
public KeepCheck asKeepCheck() {
return null;
}
+
+ @Override
+ public final boolean equals(Object obj) {
+ throw new RuntimeException();
+ }
+
+ @Override
+ public final int hashCode() {
+ throw new RuntimeException();
+ }
}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdge.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdge.java
index 656f40e..b2e541a 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdge.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdge.java
@@ -3,8 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.keepanno.ast;
-import java.util.Objects;
-
/**
* An edge in the keep graph.
*
@@ -169,24 +167,6 @@
}
@Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
- KeepEdge keepEdge = (KeepEdge) o;
- return preconditions.equals(keepEdge.preconditions)
- && consequences.equals(keepEdge.consequences);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(preconditions, consequences);
- }
-
- @Override
public String toString() {
return "KeepEdge{" + "preconditions=" + preconditions + ", consequences=" + consequences + '}';
}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepItemPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepItemPattern.java
index 6477a4c..06541d8 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepItemPattern.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepItemPattern.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.keepanno.ast;
+import com.android.tools.r8.keepanno.ast.KeepBindings.BindingSymbol;
import java.util.Collection;
import java.util.Objects;
import java.util.function.Predicate;
@@ -128,7 +129,7 @@
this.memberPattern = memberPattern;
}
- public boolean isAny(Predicate<String> onReference) {
+ public boolean isAny(Predicate<BindingSymbol> onReference) {
return kind.equals(KeepItemKind.CLASS_AND_MEMBERS)
&& extendsPattern.isAny()
&& memberPattern.isAllMembers()
@@ -151,7 +152,7 @@
return memberPattern;
}
- public Collection<String> getBindingReferences() {
+ public Collection<BindingSymbol> getBindingReferences() {
return classReference.getBindingReferences();
}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepItemReference.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepItemReference.java
index 63a111b..660ef07 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepItemReference.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepItemReference.java
@@ -3,9 +3,11 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.keepanno.ast;
+import com.android.tools.r8.keepanno.ast.KeepBindings.BindingSymbol;
+
public abstract class KeepItemReference {
- public static KeepItemReference fromBindingReference(String bindingReference) {
+ public static KeepItemReference fromBindingReference(BindingSymbol bindingReference) {
return new BindingReference(bindingReference);
}
@@ -21,7 +23,7 @@
return asItemPattern() != null;
}
- public String asBindingReference() {
+ public BindingSymbol asBindingReference() {
return null;
}
@@ -32,15 +34,15 @@
public abstract KeepItemPattern lookupItemPattern(KeepBindings bindings);
private static class BindingReference extends KeepItemReference {
- private final String bindingReference;
+ private final BindingSymbol bindingReference;
- private BindingReference(String bindingReference) {
+ private BindingReference(BindingSymbol bindingReference) {
assert bindingReference != null;
this.bindingReference = bindingReference;
}
@Override
- public String asBindingReference() {
+ public BindingSymbol asBindingReference() {
return bindingReference;
}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepEdgeBindingMinimizer.java b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepEdgeBindingMinimizer.java
deleted file mode 100644
index 0d0b853..0000000
--- a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepEdgeBindingMinimizer.java
+++ /dev/null
@@ -1,139 +0,0 @@
-// 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 com.android.tools.r8.keepanno.ast.KeepBindings;
-import com.android.tools.r8.keepanno.ast.KeepBindings.Builder;
-import com.android.tools.r8.keepanno.ast.KeepClassReference;
-import com.android.tools.r8.keepanno.ast.KeepCondition;
-import com.android.tools.r8.keepanno.ast.KeepConsequences;
-import com.android.tools.r8.keepanno.ast.KeepEdge;
-import com.android.tools.r8.keepanno.ast.KeepItemPattern;
-import com.android.tools.r8.keepanno.ast.KeepItemReference;
-import com.android.tools.r8.keepanno.ast.KeepPreconditions;
-import com.android.tools.r8.keepanno.ast.KeepTarget;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Compute the minimal set of unique bindings.
- *
- * <p>This will check if two bindings define the same exact type in which case they can and will use
- * the same binding definition.
- *
- * <p>TODO(b/248408342): Consider extending this to also identify aliased members.
- */
-public class KeepEdgeBindingMinimizer {
-
- public static KeepEdge run(KeepEdge edge) {
- KeepEdgeBindingMinimizer minimizer = new KeepEdgeBindingMinimizer();
- return minimizer.minimize(edge);
- }
-
- Map<String, List<String>> descriptorToUniqueBindings = new HashMap<>();
- Map<String, String> aliases = new HashMap<>();
-
- private KeepEdge minimize(KeepEdge edge) {
- computeAliases(edge);
- if (aliases.isEmpty()) {
- return edge;
- }
- return KeepEdge.builder()
- .setMetaInfo(edge.getMetaInfo())
- .setBindings(computeNewBindings(edge.getBindings()))
- .setPreconditions(computeNewPreconditions(edge.getPreconditions()))
- .setConsequences(computeNewConsequences(edge.getConsequences()))
- .build();
- }
-
- private void computeAliases(KeepEdge edge) {
- edge.getBindings()
- .forEach(
- (name, pattern) -> {
- if (pattern.isClassItemPattern()
- && pattern.getClassReference().asClassNamePattern().isExact()) {
- String descriptor =
- pattern.getClassReference().asClassNamePattern().getExactDescriptor();
- List<String> others =
- descriptorToUniqueBindings.computeIfAbsent(descriptor, k -> new ArrayList<>());
- String alias = findEqualBinding(pattern, others, edge);
- if (alias != null) {
- aliases.put(name, alias);
- } else {
- others.add(name);
- }
- }
- });
- }
-
- private String findEqualBinding(KeepItemPattern pattern, List<String> others, KeepEdge edge) {
- for (String otherName : others) {
- KeepItemPattern otherItem = edge.getBindings().get(otherName).getItem();
- if (pattern.equals(otherItem)) {
- return otherName;
- }
- }
- return null;
- }
-
- private String getBinding(String bindingName) {
- return aliases.getOrDefault(bindingName, bindingName);
- }
-
- private KeepBindings computeNewBindings(KeepBindings bindings) {
- Builder builder = KeepBindings.builder();
- bindings.forEach(
- (name, item) -> {
- if (!aliases.containsKey(name)) {
- builder.addBinding(name, computeNewItemPattern(item));
- }
- });
- return builder.build();
- }
-
- private KeepPreconditions computeNewPreconditions(KeepPreconditions preconditions) {
- if (preconditions.isAlways()) {
- return preconditions;
- }
- KeepPreconditions.Builder builder = KeepPreconditions.builder();
- preconditions.forEach(
- condition ->
- builder.addCondition(
- KeepCondition.builder()
- .setItemReference(computeNewItemReference(condition.getItem()))
- .build()));
- return builder.build();
- }
-
- private KeepConsequences computeNewConsequences(KeepConsequences consequences) {
- KeepConsequences.Builder builder = KeepConsequences.builder();
- consequences.forEachTarget(
- target ->
- builder.addTarget(
- KeepTarget.builder()
- .setOptions(target.getOptions())
- .setItemReference(computeNewItemReference(target.getItem()))
- .build()));
- return builder.build();
- }
-
- private KeepItemReference computeNewItemReference(KeepItemReference item) {
- return item.isBindingReference()
- ? KeepItemReference.fromBindingReference(getBinding(item.asBindingReference()))
- : KeepItemReference.fromItemPattern(computeNewItemPattern(item.asItemPattern()));
- }
-
- private KeepItemPattern computeNewItemPattern(KeepItemPattern pattern) {
- String classBinding = pattern.getClassReference().asBindingReference();
- if (classBinding == null) {
- return pattern;
- }
- return KeepItemPattern.builder()
- .copyFrom(pattern)
- .setClassReference(KeepClassReference.fromBindingReference(getBinding(classBinding)))
- .build();
- }
-}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepEdgeNormalizer.java b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepEdgeNormalizer.java
index 87ed1ce..cf79e0f 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepEdgeNormalizer.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepEdgeNormalizer.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.keepanno.keeprules;
import com.android.tools.r8.keepanno.ast.KeepBindings;
+import com.android.tools.r8.keepanno.ast.KeepBindings.BindingSymbol;
import com.android.tools.r8.keepanno.ast.KeepClassReference;
import com.android.tools.r8.keepanno.ast.KeepCondition;
import com.android.tools.r8.keepanno.ast.KeepConsequences;
@@ -13,8 +14,6 @@
import com.android.tools.r8.keepanno.ast.KeepItemReference;
import com.android.tools.r8.keepanno.ast.KeepPreconditions;
import com.android.tools.r8.keepanno.ast.KeepTarget;
-import java.util.ArrayList;
-import java.util.List;
/**
* Normalize a keep edge with respect to its bindings. This will systematically introduce a binding
@@ -24,64 +23,21 @@
public class KeepEdgeNormalizer {
private static final String syntheticBindingPrefix = "SyntheticBinding";
- private static final char syntheticBindingSuffix = 'X';
public static KeepEdge normalize(KeepEdge edge) {
// Check that all referenced bindings are defined.
KeepEdgeNormalizer normalizer = new KeepEdgeNormalizer(edge);
- KeepEdge normalized = normalizer.run();
- KeepEdge minimized = KeepEdgeBindingMinimizer.run(normalized);
- return minimized;
+ return normalizer.run();
}
private final KeepEdge edge;
- private String freshBindingNamePrefix;
- private int nextFreshBindingNameIndex = 1;
-
private final KeepBindings.Builder bindingsBuilder = KeepBindings.builder();
private final KeepPreconditions.Builder preconditionsBuilder = KeepPreconditions.builder();
private final KeepConsequences.Builder consequencesBuilder = KeepConsequences.builder();
private KeepEdgeNormalizer(KeepEdge edge) {
this.edge = edge;
- findValidFreshBindingPrefix();
- }
-
- private void findValidFreshBindingPrefix() {
- List<String> existingSuffixes = new ArrayList<>();
- edge.getBindings()
- .forEach(
- (name, ignore) -> {
- if (name.startsWith(syntheticBindingPrefix)) {
- existingSuffixes.add(name.substring(syntheticBindingPrefix.length()));
- }
- });
- if (!existingSuffixes.isEmpty()) {
- int suffixLength = 0;
- for (String existingSuffix : existingSuffixes) {
- suffixLength = Math.max(suffixLength, getRepeatedSuffixLength(existingSuffix));
- }
- StringBuilder suffix = new StringBuilder();
- for (int i = 0; i <= suffixLength; i++) {
- suffix.append(syntheticBindingSuffix);
- }
- freshBindingNamePrefix = syntheticBindingPrefix + suffix;
- } else {
- freshBindingNamePrefix = syntheticBindingPrefix;
- }
- }
-
- private int getRepeatedSuffixLength(String string) {
- int i = 0;
- while (i < string.length() && string.charAt(i) == syntheticBindingSuffix) {
- i++;
- }
- return i;
- }
-
- private String nextFreshBindingName() {
- return freshBindingNamePrefix + (nextFreshBindingNameIndex++);
}
private KeepEdge run() {
@@ -119,8 +75,14 @@
if (item.isBindingReference()) {
return item;
}
- KeepItemPattern newItemPattern = normalizeItemPattern(item.asItemPattern());
- String bindingName = nextFreshBindingName();
+ KeepItemPattern itemPattern = item.asItemPattern();
+ if (itemPattern.isClassItemPattern() && itemPattern.getClassReference().isBindingReference()) {
+ BindingSymbol classBinding =
+ bindingsBuilder.getClassBinding(itemPattern.getClassReference().asBindingReference());
+ return KeepItemReference.fromBindingReference(classBinding);
+ }
+ KeepItemPattern newItemPattern = normalizeItemPattern(itemPattern);
+ BindingSymbol bindingName = bindingsBuilder.generateFreshSymbol(syntheticBindingPrefix);
bindingsBuilder.addBinding(bindingName, newItemPattern);
return KeepItemReference.fromBindingReference(bindingName);
}
@@ -141,7 +103,7 @@
// change the item.
return classReference;
}
- String bindingName = nextFreshBindingName();
+ BindingSymbol bindingName = bindingsBuilder.generateFreshSymbol(syntheticBindingPrefix);
KeepClassReference bindingReference = KeepClassReference.fromBindingReference(bindingName);
KeepItemPattern newClassPattern = getClassItemPattern(pattern);
bindingsBuilder.addBinding(bindingName, newClassPattern);
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepRuleExtractor.java b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepRuleExtractor.java
index efb5a7f..aaafed3 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepRuleExtractor.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepRuleExtractor.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.keepanno.keeprules;
import com.android.tools.r8.keepanno.ast.KeepBindings;
+import com.android.tools.r8.keepanno.ast.KeepBindings.BindingSymbol;
import com.android.tools.r8.keepanno.ast.KeepCheck;
import com.android.tools.r8.keepanno.ast.KeepCheck.KeepCheckKind;
import com.android.tools.r8.keepanno.ast.KeepCondition;
@@ -48,7 +49,8 @@
}
public void extract(KeepDeclaration declaration) {
- Collection<PgRule> rules = split(declaration);
+ List<PgRule> rules = split(declaration);
+ PgRule.groupByKinds(rules);
StringBuilder builder = new StringBuilder();
for (PgRule rule : rules) {
rule.printRule(builder);
@@ -57,36 +59,35 @@
ruleConsumer.accept(builder.toString());
}
- private static Collection<PgRule> split(KeepDeclaration declaration) {
+ private static List<PgRule> split(KeepDeclaration declaration) {
if (declaration.isKeepCheck()) {
return generateCheckRules(declaration.asKeepCheck());
}
return doSplit(KeepEdgeNormalizer.normalize(declaration.asKeepEdge()));
}
- private static Collection<PgRule> generateCheckRules(KeepCheck check) {
+ private static List<PgRule> generateCheckRules(KeepCheck check) {
KeepItemPattern itemPattern = check.getItemPattern();
boolean isRemovedPattern = check.getKind() == KeepCheckKind.REMOVED;
List<PgRule> rules = new ArrayList<>(isRemovedPattern ? 2 : 1);
Holder holder;
- Map<String, KeepMemberPattern> memberPatterns;
- List<String> targetMembers;
+ Map<BindingSymbol, KeepMemberPattern> memberPatterns;
+ List<BindingSymbol> targetMembers;
+ KeepBindings.Builder builder = KeepBindings.builder();
+ BindingSymbol symbol = builder.generateFreshSymbol("CLASS");
if (itemPattern.isClassItemPattern()) {
- KeepBindings bindings =
- KeepBindings.builder().addBinding("CLASS", check.getItemPattern()).build();
- holder = Holder.create("CLASS", bindings);
+ builder.addBinding(symbol, check.getItemPattern());
memberPatterns = Collections.emptyMap();
targetMembers = Collections.emptyList();
} else {
- KeepBindings bindings =
- KeepBindings.builder()
- .addBinding("CLASS", KeepEdgeNormalizer.getClassItemPattern(check.getItemPattern()))
- .build();
- holder = Holder.create("CLASS", bindings);
+ builder.addBinding(symbol, KeepEdgeNormalizer.getClassItemPattern(check.getItemPattern()));
KeepMemberPattern memberPattern = itemPattern.getMemberPattern();
- memberPatterns = Collections.singletonMap("MEMBER", memberPattern);
- targetMembers = Collections.singletonList("MEMBER");
+ // This does not actually allocate a binding as the mapping is maintained in 'memberPatterns'.
+ BindingSymbol memberSymbol = new BindingSymbol("MEMBERS");
+ memberPatterns = Collections.singletonMap(memberSymbol, memberPattern);
+ targetMembers = Collections.singletonList(memberSymbol);
}
+ holder = Holder.create(symbol, builder.build());
// Add a -checkdiscard rule for the class or members.
rules.add(
new PgUnconditionalRule(
@@ -103,13 +104,14 @@
if (itemPattern.isClassItemPattern()) {
// A check removal on a class means that the entire class is removed, thus soft-pin the
// class and *all* of its members.
+ BindingSymbol memberSymbol = new BindingSymbol("MEMBERS");
rules.add(
new PgUnconditionalRule(
check.getMetaInfo(),
holder,
allowShrinking,
- Collections.singletonMap("MEMBERS", KeepMemberPattern.allMembers()),
- Collections.singletonList("MEMBERS"),
+ Collections.singletonMap(memberSymbol, KeepMemberPattern.allMembers()),
+ Collections.singletonList(memberSymbol),
TargetKeepKind.CLASS_OR_MEMBERS));
} else {
// A check removal on members just soft-pins the members.
@@ -137,7 +139,7 @@
final KeepItemPattern itemPattern;
final KeepQualifiedClassNamePattern namePattern;
- static Holder create(String bindingName, KeepBindings bindings) {
+ static Holder create(BindingSymbol bindingName, KeepBindings bindings) {
KeepItemPattern itemPattern = bindings.get(bindingName).getItem();
assert itemPattern.isClassItemPattern();
KeepQualifiedClassNamePattern namePattern = getClassNamePattern(itemPattern, bindings);
@@ -153,10 +155,10 @@
private static class BindingUsers {
final Holder holder;
- final Set<String> conditionRefs = new HashSet<>();
- final Map<KeepOptions, Set<String>> targetRefs = new HashMap<>();
+ final Set<BindingSymbol> conditionRefs = new HashSet<>();
+ final Map<KeepOptions, Set<BindingSymbol>> targetRefs = new HashMap<>();
- static BindingUsers create(String bindingName, KeepBindings bindings) {
+ static BindingUsers create(BindingSymbol bindingName, KeepBindings bindings) {
return new BindingUsers(Holder.create(bindingName, bindings));
}
@@ -177,17 +179,18 @@
}
}
- private static Collection<PgRule> doSplit(KeepEdge edge) {
+ private static List<PgRule> doSplit(KeepEdge edge) {
List<PgRule> rules = new ArrayList<>();
// First step after normalizing is to group up all conditions and targets on their target class.
// Here we use the normalized binding as the notion of identity on a class.
KeepBindings bindings = edge.getBindings();
- Map<String, BindingUsers> bindingUsers = new HashMap<>();
+ Map<BindingSymbol, BindingUsers> bindingUsers = new HashMap<>();
edge.getPreconditions()
.forEach(
condition -> {
- String classReference = getClassItemBindingReference(condition.getItem(), bindings);
+ BindingSymbol classReference =
+ getClassItemBindingReference(condition.getItem(), bindings);
assert classReference != null;
bindingUsers
.computeIfAbsent(classReference, k -> BindingUsers.create(k, bindings))
@@ -196,7 +199,8 @@
edge.getConsequences()
.forEachTarget(
target -> {
- String classReference = getClassItemBindingReference(target.getItem(), bindings);
+ BindingSymbol classReference =
+ getClassItemBindingReference(target.getItem(), bindings);
assert classReference != null;
bindingUsers
.computeIfAbsent(classReference, k -> BindingUsers.create(k, bindings))
@@ -258,11 +262,11 @@
return rules;
}
- private static List<String> computeConditions(
- Set<String> conditions,
+ private static List<BindingSymbol> computeConditions(
+ Set<BindingSymbol> conditions,
KeepBindings bindings,
- Map<String, KeepMemberPattern> memberPatterns) {
- List<String> conditionMembers = new ArrayList<>();
+ Map<BindingSymbol, KeepMemberPattern> memberPatterns) {
+ List<BindingSymbol> conditionMembers = new ArrayList<>();
conditions.forEach(
conditionReference -> {
KeepItemPattern item = bindings.get(conditionReference).getItem();
@@ -278,21 +282,19 @@
@FunctionalInterface
private interface OnTargetCallback {
void accept(
- Map<String, KeepMemberPattern> memberPatterns,
- List<String> memberTargets,
+ Map<BindingSymbol, KeepMemberPattern> memberPatterns,
+ List<BindingSymbol> memberTargets,
TargetKeepKind keepKind);
}
private static void computeTargets(
- Set<String> targets,
+ Set<BindingSymbol> targets,
KeepBindings bindings,
- Map<String, KeepMemberPattern> memberPatterns,
+ Map<BindingSymbol, KeepMemberPattern> memberPatterns,
OnTargetCallback callback) {
- boolean keepClassTarget = false;
- List<String> disjunctiveTargetMembers = new ArrayList<>();
- List<String> classConjunctiveTargetMembers = new ArrayList<>();
-
- for (String targetReference : targets) {
+ TargetKeepKind keepKind = TargetKeepKind.JUST_MEMBERS;
+ List<BindingSymbol> targetMembers = new ArrayList<>();
+ for (BindingSymbol targetReference : targets) {
KeepItemPattern item = bindings.get(targetReference).getItem();
if (bindings.isAny(item)) {
// If the target is "any item" then it contains any other target pattern.
@@ -304,44 +306,16 @@
return;
}
if (item.isClassItemPattern()) {
- keepClassTarget = true;
+ keepKind = TargetKeepKind.CLASS_AND_MEMBERS;
} else {
memberPatterns.putIfAbsent(targetReference, item.getMemberPattern());
- if (item.isClassAndMemberPattern()) {
- // If a target is a "class and member" target then it must be added as a separate rule.
- classConjunctiveTargetMembers.add(targetReference);
- } else {
- assert item.isMemberItemPattern();
- disjunctiveTargetMembers.add(targetReference);
- }
+ targetMembers.add(targetReference);
}
}
-
- // The class is targeted, so that part of a class-and-member conjunction is satisfied.
- // The conjunctive members can thus be moved to the disjunctive set.
- if (keepClassTarget) {
- disjunctiveTargetMembers.addAll(classConjunctiveTargetMembers);
- classConjunctiveTargetMembers.clear();
+ if (targetMembers.isEmpty()) {
+ keepKind = TargetKeepKind.CLASS_OR_MEMBERS;
}
-
- if (!disjunctiveTargetMembers.isEmpty()) {
- TargetKeepKind keepKind =
- keepClassTarget ? TargetKeepKind.CLASS_OR_MEMBERS : TargetKeepKind.JUST_MEMBERS;
- callback.accept(memberPatterns, disjunctiveTargetMembers, keepKind);
- } else if (keepClassTarget) {
- callback.accept(
- Collections.emptyMap(), Collections.emptyList(), TargetKeepKind.CLASS_OR_MEMBERS);
- }
-
- if (!classConjunctiveTargetMembers.isEmpty()) {
- assert !keepClassTarget;
- for (String targetReference : classConjunctiveTargetMembers) {
- callback.accept(
- memberPatterns,
- Collections.singletonList(targetReference),
- TargetKeepKind.CLASS_AND_MEMBERS);
- }
- }
+ callback.accept(memberPatterns, targetMembers, keepKind);
}
private static void createUnconditionalRules(
@@ -350,7 +324,7 @@
KeepEdgeMetaInfo metaInfo,
KeepBindings bindings,
KeepOptions options,
- Set<String> targets) {
+ Set<BindingSymbol> targets) {
computeTargets(
targets,
bindings,
@@ -382,10 +356,18 @@
Holder targetHolder,
KeepBindings bindings,
KeepOptions options,
- Set<String> conditions,
- Set<String> targets) {
- Map<String, KeepMemberPattern> memberPatterns = new HashMap<>();
- List<String> conditionMembers = computeConditions(conditions, bindings, memberPatterns);
+ Set<BindingSymbol> conditions,
+ Set<BindingSymbol> targets) {
+ if (conditionHolder.namePattern.isExact()
+ && conditionHolder.itemPattern.equals(targetHolder.itemPattern)) {
+ // If the targets are conditional on its holder, the rule can be simplified as a dependent
+ // rule. Note that this is only valid on an *exact* class matching as otherwise any
+ // wildcard is allowed to be matched independently on the left and right of the edge.
+ createDependentRules(rules, targetHolder, metaInfo, bindings, options, conditions, targets);
+ return;
+ }
+ Map<BindingSymbol, KeepMemberPattern> memberPatterns = new HashMap<>();
+ List<BindingSymbol> conditionMembers = computeConditions(conditions, bindings, memberPatterns);
computeTargets(
targets,
bindings,
@@ -409,23 +391,24 @@
KeepEdgeMetaInfo metaInfo,
KeepBindings bindings,
KeepOptions options,
- Set<String> conditions,
- Set<String> targets) {
- Map<String, KeepMemberPattern> memberPatterns = new HashMap<>();
- List<String> conditionMembers = computeConditions(conditions, bindings, memberPatterns);
+ Set<BindingSymbol> conditions,
+ Set<BindingSymbol> targets) {
+ Map<BindingSymbol, KeepMemberPattern> memberPatterns = new HashMap<>();
+ List<BindingSymbol> conditionMembers = computeConditions(conditions, bindings, memberPatterns);
computeTargets(
targets,
bindings,
memberPatterns,
(ignore, targetMembers, targetKeepKind) -> {
- List<String> nonAllMemberTargets = new ArrayList<>(targetMembers.size());
- for (String targetMember : targetMembers) {
+ List<BindingSymbol> nonAllMemberTargets = new ArrayList<>(targetMembers.size());
+ for (BindingSymbol targetMember : targetMembers) {
KeepMemberPattern memberPattern = memberPatterns.get(targetMember);
if (memberPattern.isGeneralMember() && conditionMembers.contains(targetMember)) {
// This pattern is on "members in general" and it is bound by a condition.
// Since backrefs can't reference a *-member we split this target in two, one for
// fields and one for methods.
- HashMap<String, KeepMemberPattern> copyWithMethod = new HashMap<>(memberPatterns);
+ HashMap<BindingSymbol, KeepMemberPattern> copyWithMethod =
+ new HashMap<>(memberPatterns);
copyWithMethod.put(targetMember, copyMethodFromMember(memberPattern));
rules.add(
new PgDependentMembersRule(
@@ -436,7 +419,8 @@
conditionMembers,
Collections.singletonList(targetMember),
targetKeepKind));
- HashMap<String, KeepMemberPattern> copyWithField = new HashMap<>(memberPatterns);
+ HashMap<BindingSymbol, KeepMemberPattern> copyWithField =
+ new HashMap<>(memberPatterns);
copyWithField.put(targetMember, copyFieldFromMember(memberPattern));
rules.add(
new PgDependentMembersRule(
@@ -486,10 +470,10 @@
bindings.get(itemPattern.getClassReference().asBindingReference()).getItem(), bindings);
}
- private static String getClassItemBindingReference(
+ private static BindingSymbol getClassItemBindingReference(
KeepItemReference itemReference, KeepBindings bindings) {
- String classReference = null;
- for (String reference : getTransitiveBindingReferences(itemReference, bindings)) {
+ BindingSymbol classReference = null;
+ for (BindingSymbol reference : getTransitiveBindingReferences(itemReference, bindings)) {
if (bindings.get(reference).getItem().isClassItemPattern()) {
if (classReference != null) {
throw new KeepEdgeException("Unexpected reference to multiple class bindings");
@@ -500,13 +484,13 @@
return classReference;
}
- private static Set<String> getTransitiveBindingReferences(
+ private static Set<BindingSymbol> getTransitiveBindingReferences(
KeepItemReference itemReference, KeepBindings bindings) {
- Set<String> references = new HashSet<>(2);
- Deque<String> worklist = new ArrayDeque<>();
+ Set<BindingSymbol> references = new HashSet<>(2);
+ Deque<BindingSymbol> worklist = new ArrayDeque<>();
worklist.addAll(getBindingReference(itemReference));
while (!worklist.isEmpty()) {
- String bindingReference = worklist.pop();
+ BindingSymbol bindingReference = worklist.pop();
if (references.add(bindingReference)) {
worklist.addAll(getBindingReference(bindings.get(bindingReference).getItem()));
}
@@ -514,14 +498,14 @@
return references;
}
- private static Collection<String> getBindingReference(KeepItemReference itemReference) {
+ private static Collection<BindingSymbol> getBindingReference(KeepItemReference itemReference) {
if (itemReference.isBindingReference()) {
return Collections.singletonList(itemReference.asBindingReference());
}
return getBindingReference(itemReference.asItemPattern());
}
- private static Collection<String> getBindingReference(KeepItemPattern itemPattern) {
+ private static Collection<BindingSymbol> getBindingReference(KeepItemPattern itemPattern) {
return itemPattern.getClassReference().isBindingReference()
? Collections.singletonList(itemPattern.getClassReference().asBindingReference())
: Collections.emptyList();
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/PgRule.java b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/PgRule.java
index e0ad65e..5d55174 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/PgRule.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/PgRule.java
@@ -3,9 +3,14 @@
// 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_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.KeepBindings.BindingSymbol;
import com.android.tools.r8.keepanno.ast.KeepClassReference;
import com.android.tools.r8.keepanno.ast.KeepEdgeException;
import com.android.tools.r8.keepanno.ast.KeepEdgeMetaInfo;
@@ -15,12 +20,43 @@
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.Comparator;
import java.util.HashMap;
+import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
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:
+ return 0;
+ case KEEP_CLASSES_WITH_MEMBERS:
+ return 1;
+ case KEEP_CLASS_MEMBERS:
+ return 2;
+ case CHECK_DISCARD:
+ return 3;
+ 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),
@@ -87,10 +123,10 @@
if (hasCondition()) {
builder.append(RulePrintingUtils.IF).append(' ');
printConditionHolder(builder);
- List<String> members = getConditionMembers();
+ List<BindingSymbol> members = getConditionMembers();
if (!members.isEmpty()) {
builder.append(" {");
- for (String member : members) {
+ for (BindingSymbol member : members) {
builder.append(' ');
printConditionMember(builder, member);
}
@@ -105,10 +141,10 @@
printKeepOptions(builder);
builder.append(' ');
printTargetHolder(builder);
- List<String> members = getTargetMembers();
+ List<BindingSymbol> members = getTargetMembers();
if (!members.isEmpty()) {
builder.append(" {");
- for (String member : members) {
+ for (BindingSymbol member : members) {
builder.append(' ');
printTargetMember(builder, member);
}
@@ -120,25 +156,25 @@
return false;
}
- List<String> getConditionMembers() {
+ List<BindingSymbol> getConditionMembers() {
throw new KeepEdgeException("Unreachable");
}
abstract String getConsequenceKeepType();
- abstract List<String> getTargetMembers();
+ abstract List<BindingSymbol> getTargetMembers();
void printConditionHolder(StringBuilder builder) {
throw new KeepEdgeException("Unreachable");
}
- void printConditionMember(StringBuilder builder, String member) {
+ void printConditionMember(StringBuilder builder, BindingSymbol member) {
throw new KeepEdgeException("Unreachable");
}
abstract void printTargetHolder(StringBuilder builder);
- abstract void printTargetMember(StringBuilder builder, String member);
+ abstract void printTargetMember(StringBuilder builder, BindingSymbol member);
/**
* Representation of an unconditional rule to keep a class and methods.
@@ -153,15 +189,15 @@
private final KeepQualifiedClassNamePattern holderNamePattern;
private final KeepItemPattern holderPattern;
private final TargetKeepKind targetKeepKind;
- private final List<String> targetMembers;
- private final Map<String, KeepMemberPattern> memberPatterns;
+ private final List<BindingSymbol> targetMembers;
+ private final Map<BindingSymbol, KeepMemberPattern> memberPatterns;
public PgUnconditionalRule(
KeepEdgeMetaInfo metaInfo,
Holder holder,
KeepOptions options,
- Map<String, KeepMemberPattern> memberPatterns,
- List<String> targetMembers,
+ Map<BindingSymbol, KeepMemberPattern> memberPatterns,
+ List<BindingSymbol> targetMembers,
TargetKeepKind targetKeepKind) {
super(metaInfo, options);
assert !targetKeepKind.equals(TargetKeepKind.JUST_MEMBERS);
@@ -178,7 +214,7 @@
}
@Override
- List<String> getTargetMembers() {
+ List<BindingSymbol> getTargetMembers() {
return targetMembers;
}
@@ -191,7 +227,7 @@
}
@Override
- void printTargetMember(StringBuilder builder, String memberReference) {
+ void printTargetMember(StringBuilder builder, BindingSymbol memberReference) {
KeepMemberPattern memberPattern = memberPatterns.get(memberReference);
printMemberClause(memberPattern, RulePrinter.withoutBackReferences(builder));
}
@@ -211,9 +247,9 @@
final KeepItemPattern classCondition;
final KeepItemPattern classTarget;
- final Map<String, KeepMemberPattern> memberPatterns;
- final List<String> memberConditions;
- private final List<String> memberTargets;
+ final Map<BindingSymbol, KeepMemberPattern> memberPatterns;
+ final List<BindingSymbol> memberConditions;
+ private final List<BindingSymbol> memberTargets;
private final TargetKeepKind keepKind;
public PgConditionalRule(
@@ -221,9 +257,9 @@
KeepOptions options,
Holder classCondition,
Holder classTarget,
- Map<String, KeepMemberPattern> memberPatterns,
- List<String> memberConditions,
- List<String> memberTargets,
+ Map<BindingSymbol, KeepMemberPattern> memberPatterns,
+ List<BindingSymbol> memberConditions,
+ List<BindingSymbol> memberTargets,
TargetKeepKind keepKind) {
super(metaInfo, options);
this.classCondition = classCondition.itemPattern;
@@ -240,7 +276,7 @@
}
@Override
- List<String> getConditionMembers() {
+ List<BindingSymbol> getConditionMembers() {
return memberConditions;
}
@@ -250,7 +286,7 @@
}
@Override
- void printConditionMember(StringBuilder builder, String member) {
+ void printConditionMember(StringBuilder builder, BindingSymbol member) {
KeepMemberPattern memberPattern = memberPatterns.get(member);
printMemberClause(memberPattern, RulePrinter.withoutBackReferences(builder));
}
@@ -269,12 +305,12 @@
}
@Override
- List<String> getTargetMembers() {
+ List<BindingSymbol> getTargetMembers() {
return memberTargets;
}
@Override
- void printTargetMember(StringBuilder builder, String member) {
+ void printTargetMember(StringBuilder builder, BindingSymbol member) {
KeepMemberPattern memberPattern = memberPatterns.get(member);
printMemberClause(memberPattern, RulePrinter.withoutBackReferences(builder));
}
@@ -303,22 +339,22 @@
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 Map<BindingSymbol, KeepMemberPattern> memberPatterns;
+ private final List<BindingSymbol> memberConditions;
+ private final List<BindingSymbol> memberTargets;
private final TargetKeepKind keepKind;
private int nextBackReferenceNumber = 1;
private String holderBackReferencePattern = null;
- private Map<String, String> membersBackReferencePatterns = new HashMap<>();
+ private final Map<BindingSymbol, String> membersBackReferencePatterns = new HashMap<>();
public PgDependentMembersRule(
KeepEdgeMetaInfo metaInfo,
Holder holder,
KeepOptions options,
- Map<String, KeepMemberPattern> memberPatterns,
- List<String> memberConditions,
- List<String> memberTargets,
+ Map<BindingSymbol, KeepMemberPattern> memberPatterns,
+ List<BindingSymbol> memberConditions,
+ List<BindingSymbol> memberTargets,
TargetKeepKind keepKind) {
super(metaInfo, options);
this.holderNamePattern = holder.namePattern;
@@ -348,12 +384,12 @@
}
@Override
- List<String> getConditionMembers() {
+ List<BindingSymbol> getConditionMembers() {
return memberConditions;
}
@Override
- List<String> getTargetMembers() {
+ List<BindingSymbol> getTargetMembers() {
return memberTargets;
}
@@ -371,7 +407,7 @@
}
@Override
- void printConditionMember(StringBuilder builder, String member) {
+ void printConditionMember(StringBuilder builder, BindingSymbol member) {
KeepMemberPattern memberPattern = memberPatterns.get(member);
BackReferencePrinter printer =
RulePrinter.withBackReferences(builder, this::getNextBackReferenceNumber);
@@ -401,7 +437,7 @@
}
@Override
- void printTargetMember(StringBuilder builder, String member) {
+ void printTargetMember(StringBuilder builder, BindingSymbol member) {
if (hasCondition()) {
String backref = membersBackReferencePatterns.get(member);
if (backref != null) {
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 4b8b6e3..fc1122a 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -765,7 +765,8 @@
for (ProgramResourceProvider provider : getAppBuilder().getProgramResourceProviders()) {
for (ProgramResource resource : provider.getProgramResources()) {
if (resource.getKind() == Kind.CF) {
- Set<KeepDeclaration> declarations = KeepEdgeReader.readKeepEdges(resource.getBytes());
+ List<KeepDeclaration> declarations =
+ KeepEdgeReader.readKeepEdges(resource.getBytes());
KeepRuleExtractor extractor =
new KeepRuleExtractor(
rule -> {
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepEdgeAnnotationsTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepEdgeAnnotationsTest.java
index 72ad93c..aa1c6a6 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepEdgeAnnotationsTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepEdgeAnnotationsTest.java
@@ -41,7 +41,6 @@
import java.util.Collection;
import java.util.Collections;
import java.util.List;
-import java.util.Set;
import java.util.stream.Collectors;
import org.junit.Ignore;
import org.junit.Test;
@@ -203,7 +202,7 @@
@Test
public void testAsmReader() throws Exception {
assumeTrue(parameters.isCfRuntime());
- Set<KeepEdge> expectedEdges = KeepSourceEdges.getExpectedEdges(source);
+ List<KeepEdge> expectedEdges = KeepSourceEdges.getExpectedEdges(source);
ClassReference clazz = Reference.classFromClass(source);
// Original bytes of the test class.
byte[] original = ToolHelper.getClassAsBytes(source);
@@ -228,13 +227,13 @@
.transform();
// Read the edges from each version.
- Set<KeepDeclaration> originalEdges = KeepEdgeReader.readKeepEdges(original);
- Set<KeepDeclaration> strippedEdges = KeepEdgeReader.readKeepEdges(stripped);
- Set<KeepDeclaration> readdedEdges = KeepEdgeReader.readKeepEdges(readded);
+ List<KeepDeclaration> originalEdges = KeepEdgeReader.readKeepEdges(original);
+ List<KeepDeclaration> strippedEdges = KeepEdgeReader.readKeepEdges(stripped);
+ List<KeepDeclaration> readdedEdges = KeepEdgeReader.readKeepEdges(readded);
// The edges are compared to the "expected" ast to ensure we don't hide failures in reading or
// writing.
- assertEquals(Collections.emptySet(), strippedEdges);
+ assertEquals(Collections.emptyList(), strippedEdges);
assertEquals(expectedEdges, originalEdges);
assertEquals(expectedEdges, readdedEdges);
}
@@ -259,7 +258,7 @@
assertThat(synthesizedEdgesClass.annotation(Edge.CLASS.getTypeName()), isPresent());
String entry = ZipUtils.zipEntryNameForClass(synthesizedEdgesClass.getFinalReference());
byte[] bytes = ZipUtils.readSingleEntry(data, entry);
- Set<KeepDeclaration> keepEdges = KeepEdgeReader.readKeepEdges(bytes);
+ List<KeepDeclaration> keepEdges = KeepEdgeReader.readKeepEdges(bytes);
assertEquals(KeepSourceEdges.getExpectedEdges(source), keepEdges);
}
}
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepInvalidForApiTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepInvalidForApiTest.java
index 97fd716..88d7afa 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepInvalidForApiTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepInvalidForApiTest.java
@@ -21,7 +21,6 @@
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
-import java.util.Set;
import org.hamcrest.Matcher;
import org.junit.Test;
import org.junit.function.ThrowingRunnable;
@@ -41,7 +40,7 @@
}
private static List<String> extractRuleForClass(Class<?> clazz) throws IOException {
- Set<KeepDeclaration> keepEdges =
+ List<KeepDeclaration> keepEdges =
KeepEdgeReader.readKeepEdges(ToolHelper.getClassAsBytes(clazz));
List<String> rules = new ArrayList<>();
KeepRuleExtractor extractor = new KeepRuleExtractor(rules::add);
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepInvalidTargetTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepInvalidTargetTest.java
index 943f9b7..b95f700 100644
--- a/src/test/java/com/android/tools/r8/keepanno/KeepInvalidTargetTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepInvalidTargetTest.java
@@ -22,7 +22,6 @@
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
-import java.util.Set;
import org.hamcrest.Matcher;
import org.junit.Test;
import org.junit.function.ThrowingRunnable;
@@ -42,7 +41,7 @@
}
private static List<String> extractRuleForClass(Class<?> clazz) throws IOException {
- Set<KeepDeclaration> keepEdges =
+ List<KeepDeclaration> keepEdges =
KeepEdgeReader.readKeepEdges(ToolHelper.getClassAsBytes(clazz));
List<String> rules = new ArrayList<>();
KeepRuleExtractor extractor = new KeepRuleExtractor(rules::add);
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepStaticBindingTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepStaticBindingTest.java
new file mode 100644
index 0000000..8c8c806
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepStaticBindingTest.java
@@ -0,0 +1,102 @@
+// 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;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.keepanno.annotations.KeepBinding;
+import com.android.tools.r8.keepanno.annotations.KeepEdge;
+import com.android.tools.r8.keepanno.annotations.KeepTarget;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class KeepStaticBindingTest extends TestBase {
+
+ static final String EXPECTED = StringUtils.lines("Hello!");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDefaultRuntimes().withApiLevel(AndroidApiLevel.B).build();
+ }
+
+ public KeepStaticBindingTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testReference() throws Exception {
+ testForRuntime(parameters)
+ .addProgramClasses(getInputClasses())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testWithRuleExtraction() throws Exception {
+ testForR8(parameters.getBackend())
+ .enableExperimentalKeepAnnotations()
+ .addProgramClasses(getInputClasses())
+ .setMinApi(parameters)
+ .compile()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED)
+ .inspect(this::checkOutput);
+ }
+
+ public List<Class<?>> getInputClasses() {
+ return ImmutableList.of(TestClass.class, Unused.class);
+ }
+
+ private void checkOutput(CodeInspector inspector) {
+ assertThat(inspector.clazz(TestClass.class), isPresent());
+ assertThat(inspector.clazz(TestClass.class).mainMethod(), isPresent());
+ assertThat(inspector.clazz(Unused.class), isAbsent());
+ }
+
+ /**
+ * This conditional rule expresses that if any class in the program has a live "bar" method then
+ * that same classes "foo" method is to be kept. The binding(s) establishes the relation between
+ * the holder of the two methods.
+ */
+ @KeepEdge(
+ bindings = {
+ @KeepBinding(
+ bindingName = "ClassWithMain",
+ methodName = "main",
+ methodReturnType = "void",
+ methodParameters = {"java.lang.String[]"})
+ },
+ consequences = {
+ @KeepTarget(classFromBinding = "ClassWithMain"),
+ @KeepTarget(memberFromBinding = "ClassWithMain")
+ })
+ static class TestClass {
+
+ public static void main(String[] args) {
+ System.out.println("Hello!");
+ }
+ }
+
+ static class Unused {
+ // No matching main method, so should not match the rule.
+
+ public static void main() {
+ // Non-matching main.
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/ast/KeepEdgeAstTest.java b/src/test/java/com/android/tools/r8/keepanno/ast/KeepEdgeAstTest.java
index 5ffc342..64f9378 100644
--- a/src/test/java/com/android/tools/r8/keepanno/ast/KeepEdgeAstTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/ast/KeepEdgeAstTest.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.keepanno.ast.KeepBindings.BindingSymbol;
import com.android.tools.r8.keepanno.ast.KeepOptions.KeepOption;
import com.android.tools.r8.keepanno.keeprules.KeepRuleExtractor;
import com.android.tools.r8.utils.StringUtils;
@@ -159,14 +160,60 @@
.build();
assertEquals(
StringUtils.unixLines(
- "-if class " + CLASS + " -keep class " + CLASS + " { void <init>(); }"),
+ "-keepclassmembers class " + CLASS + " { void <init>(); }",
+ "-if class " + CLASS + " -keep class " + CLASS + " { void finalize(); }"),
extract(edge));
}
+ @Test
+ public void testKeepInstanceAndInitIfReferencedWithBinding() {
+ KeepBindings.Builder bindings = KeepBindings.builder();
+ BindingSymbol classSymbol = bindings.create("CLASS");
+ KeepEdge edge =
+ KeepEdge.builder()
+ .setBindings(bindings.addBinding(classSymbol, classItem(CLASS)).build())
+ .setPreconditions(
+ KeepPreconditions.builder()
+ .addCondition(
+ KeepCondition.builder().setItemReference(itemBinding(classSymbol)).build())
+ .build())
+ .setConsequences(
+ KeepConsequences.builder()
+ .addTarget(target(itemBinding(classSymbol)))
+ .addTarget(
+ target(
+ KeepItemPattern.builder()
+ .setClassReference(classBinding(classSymbol))
+ .setMemberPattern(defaultInitializerPattern())
+ .build()))
+ .build())
+ .build();
+ assertEquals(
+ StringUtils.unixLines(
+ "-if class "
+ + CLASS
+ + " -keepclasseswithmembers class "
+ + CLASS
+ + " { void <init>(); }"),
+ extract(edge));
+ }
+
+ private KeepItemReference itemBinding(BindingSymbol bindingName) {
+ return KeepItemReference.fromBindingReference(bindingName);
+ }
+
+ private KeepClassReference classBinding(BindingSymbol bindingName) {
+ return KeepClassReference.fromBindingReference(bindingName);
+ }
+
private KeepTarget target(KeepItemPattern item) {
return KeepTarget.builder().setItemPattern(item).build();
}
+ private KeepTarget target(KeepItemReference item) {
+ return KeepTarget.builder().setItemReference(item).build();
+ }
+
private KeepItemPattern classItem(String typeName) {
return buildClassItem(typeName).build();
}
@@ -175,6 +222,7 @@
return KeepItemPattern.builder().setClassPattern(KeepQualifiedClassNamePattern.exact(typeName));
}
+
private KeepMemberPattern defaultInitializerPattern() {
return KeepMethodPattern.builder()
.setNamePattern(KeepMethodNamePattern.initializer())
diff --git a/src/test/java/com/android/tools/r8/keepanno/testsource/KeepSourceEdges.java b/src/test/java/com/android/tools/r8/keepanno/testsource/KeepSourceEdges.java
index 8958148..ac50217 100644
--- a/src/test/java/com/android/tools/r8/keepanno/testsource/KeepSourceEdges.java
+++ b/src/test/java/com/android/tools/r8/keepanno/testsource/KeepSourceEdges.java
@@ -20,7 +20,6 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
-import java.util.Set;
/**
* Utility to get the AST edges for the various annotation test sources.
@@ -32,9 +31,9 @@
private static class SourceData {
final Class<?> clazz;
final String expected;
- final Set<KeepEdge> edges;
+ final List<KeepEdge> edges;
- public SourceData(Class<?> clazz, String expected, Set<KeepEdge> edges) {
+ public SourceData(Class<?> clazz, String expected, List<KeepEdge> edges) {
this.clazz = clazz;
this.expected = expected;
this.edges = edges;
@@ -59,7 +58,7 @@
getKeepDependentFieldSourceEdges()));
}
- public static Set<KeepEdge> getExpectedEdges(Class<?> clazz) {
+ public static List<KeepEdge> getExpectedEdges(Class<?> clazz) {
for (SourceData source : SOURCES) {
if (source.clazz == clazz) {
return source.edges;
@@ -81,9 +80,9 @@
return StringUtils.lines("A is alive!");
}
- public static Set<KeepEdge> getKeepClassAndDefaultConstructorSourceEdges() {
+ public static List<KeepEdge> getKeepClassAndDefaultConstructorSourceEdges() {
Class<?> clazz = KeepClassAndDefaultConstructorSource.A.class;
- return Collections.singleton(
+ return Collections.singletonList(
mkEdge(mkConsequences(mkTarget(mkClass(clazz)), mkTarget(mkMethod(clazz, "<init>")))));
}
@@ -91,8 +90,8 @@
return StringUtils.lines("The values match!");
}
- public static Set<KeepEdge> getKeepFieldSourceEdges() {
- return Collections.singleton(
+ public static List<KeepEdge> getKeepFieldSourceEdges() {
+ return Collections.singletonList(
mkEdge(mkConsequences(mkTarget(mkField(KeepFieldSource.A.class, "f")))));
}
@@ -100,8 +99,8 @@
return getKeepFieldSourceExpected();
}
- public static Set<KeepEdge> getKeepDependentFieldSourceEdges() {
- return Collections.singleton(
+ public static List<KeepEdge> getKeepDependentFieldSourceEdges() {
+ return Collections.singletonList(
mkDepEdge(
mkPreconditions(mkCondition(mkMethod(KeepDependentFieldSource.class, "main"))),
mkConsequences(mkTarget(mkField(KeepDependentFieldSource.A.class, "f")))));