[KeepAnno] Allow using kind with member bindings
This now allows a binding to be used together with kind
CLASS_AND_MEMBERS. It also moves the handling of bindings into the item
visitor base in preparation for normalizing the AST.
Bug: b/343389186
Change-Id: I2eb379e5ce5d1f9e4f68868441d6722216ee4ba8
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 f28d5c8..a4767a0 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
@@ -29,6 +29,7 @@
import com.android.tools.r8.keepanno.ast.KeepBindings.KeepBindingSymbol;
import com.android.tools.r8.keepanno.ast.KeepCheck;
import com.android.tools.r8.keepanno.ast.KeepCheck.KeepCheckKind;
+import com.android.tools.r8.keepanno.ast.KeepClassBindingReference;
import com.android.tools.r8.keepanno.ast.KeepClassItemPattern;
import com.android.tools.r8.keepanno.ast.KeepClassItemReference;
import com.android.tools.r8.keepanno.ast.KeepCondition;
@@ -48,6 +49,7 @@
import com.android.tools.r8.keepanno.ast.KeepItemReference;
import com.android.tools.r8.keepanno.ast.KeepMemberAccessPattern;
import com.android.tools.r8.keepanno.ast.KeepMemberAccessPattern.BuilderBase;
+import com.android.tools.r8.keepanno.ast.KeepMemberBindingReference;
import com.android.tools.r8.keepanno.ast.KeepMemberItemPattern;
import com.android.tools.r8.keepanno.ast.KeepMemberItemReference;
import com.android.tools.r8.keepanno.ast.KeepMemberPattern;
@@ -968,6 +970,12 @@
}
@Override
+ public boolean useBindingForClassAndMembers() {
+ // KeepForApi targets should be disjunctive CLASS_AND_MEMBERS
+ return false;
+ }
+
+ @Override
public void visitEnd() {
if (!getKind().isOnlyClass() && isDefaultMemberDeclaration()) {
// If no member declarations have been made, set public & protected as the default.
@@ -980,7 +988,7 @@
// Default constraints should retain the expected meta-data, such as signatures, annotations
// exception-throws etc.
KeepConstraints defaultForApiConstraints = KeepConstraints.all();
- Collection<KeepItemReference> items = getItemsWithoutBinding();
+ Collection<KeepItemReference> items = getItems();
for (KeepItemReference item : items) {
if (item.isBindingReference()) {
throw parsingContext.error("cannot reference bindings");
@@ -1216,19 +1224,26 @@
}
@Override
+ public boolean useBindingForClassAndMembers() {
+ // When annotating a class with UsedByReflection and including member patterns, don't
+ // require the member patterns to match to retain the class.
+ return true;
+ }
+
+ @Override
public void visitEnd() {
if (getKind() == null && !isDefaultMemberDeclaration()) {
// If no explict kind is set and member declarations have been made, keep the class too.
visitEnum(null, Kind.DESCRIPTOR, Kind.CLASS_AND_MEMBERS);
}
super.visitEnd();
- Collection<KeepItemReference> items = getItemsWithoutBinding();
- for (KeepItemReference item : items) {
+ for (KeepItemReference item : getItems()) {
+ KeepItemPattern itemPattern;
if (item.isBindingReference()) {
- // TODO(b/248408342): The edge can have preconditions so it should support bindings!
- throw parsingContext.error("cannot reference bindings");
+ itemPattern = getBindingsHelper().getItemForBinding(item.asBindingReference());
+ } else {
+ itemPattern = item.asItemPattern();
}
- KeepItemPattern itemPattern = item.asItemPattern();
KeepClassItemPattern holderPattern =
itemPattern.isClassItemPattern()
? itemPattern.asClassItemPattern()
@@ -1947,8 +1962,12 @@
public abstract UserBindingsHelper getBindingsHelper();
- // Constructed item available once visitEnd has been called.
- private KeepItemReference itemReference = null;
+ public boolean useBindingForClassAndMembers() {
+ return true;
+ }
+
+ // Constructed items available once visitEnd has been called.
+ private List<KeepItemReference> itemReferences = null;
KeepItemVisitorBase(ParsingContext parsingContext) {
super(parsingContext);
@@ -1957,88 +1976,21 @@
memberDeclaration = new MemberDeclarationParser(parsingContext);
}
- public Collection<KeepItemReference> getItemsWithoutBinding() {
- if (itemReference == null) {
- throw parsingContext.error("Item reference not finalized. Missing call to visitEnd()");
+ public Collection<KeepItemReference> getItems() {
+ if (itemReferences == null || kind == null) {
+ throw parsingContext.error("Items not finalized. Missing call to visitEnd()");
}
- if (itemReference.isBindingReference()) {
- return Collections.singletonList(itemReference);
- }
- // Kind is only null if item is a "binding reference".
- if (kind == null) {
- throw parsingContext.error("Unexpected state: unknown kind for an item pattern");
- }
- if (kind.includesClassAndMembers()) {
- assert !itemReference.isBindingReference();
- KeepItemPattern itemPattern = itemReference.asItemPattern();
- KeepClassItemReference classReference;
- KeepMemberItemPattern memberPattern;
- if (itemPattern.isClassItemPattern()) {
- classReference = itemPattern.asClassItemPattern().toClassItemReference();
- memberPattern =
- KeepMemberItemPattern.builder()
- .setClassReference(classReference)
- .setMemberPattern(KeepMemberPattern.allMembers())
- .build();
- } else {
- memberPattern = itemPattern.asMemberItemPattern();
- classReference = memberPattern.getClassReference();
- }
- return ImmutableList.of(classReference, memberPattern.toItemReference());
- } else {
- return Collections.singletonList(itemReference);
- }
- }
-
- public Collection<KeepItemReference> getItemsWithBinding() {
- if (itemReference == null) {
- throw parsingContext.error("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".
- if (kind == null) {
- throw parsingContext.error("Unexpected state: unknown kind for an item pattern");
- }
- if (kind.includesClassAndMembers()) {
- KeepItemPattern itemPattern = itemReference.asItemPattern();
- // Ensure we have a member item linked to the correct class.
- KeepMemberItemPattern memberItemPattern;
- if (itemPattern.isClassItemPattern()) {
- memberItemPattern =
- KeepMemberItemPattern.builder()
- .setClassReference(itemPattern.asClassItemPattern().toClassItemReference())
- .build();
- } else {
- memberItemPattern = itemPattern.asMemberItemPattern();
- }
- // If the class is not a binding, introduce the binding and rewrite the member.
- KeepClassItemReference classItemReference = memberItemPattern.getClassReference();
- if (classItemReference.isClassItemPattern()) {
- KeepClassItemPattern classItemPattern = classItemReference.asClassItemPattern();
- KeepBindingSymbol symbol =
- getBindingsHelper().defineFreshBinding("CLASS", classItemPattern);
- classItemReference = KeepBindingReference.forClass(symbol).toClassItemReference();
- memberItemPattern =
- KeepMemberItemPattern.builder()
- .copyFrom(memberItemPattern)
- .setClassReference(classItemReference)
- .build();
- }
- assert classItemReference.isBindingReference();
- assert memberItemPattern.getClassReference().equals(classItemReference);
- return ImmutableList.of(classItemReference, memberItemPattern.toItemReference());
- } else {
- return Collections.singletonList(itemReference);
- }
+ return itemReferences;
}
public KeepItemReference getItemReference() {
- if (itemReference == null) {
+ if (itemReferences == null) {
throw parsingContext.error("Item reference not finalized. Missing call to visitEnd()");
}
- return itemReference;
+ if (itemReferences.size() > 1) {
+ throw parsingContext.error("Ambiguous item reference.");
+ }
+ return itemReferences.get(0);
}
public ItemKind getKind() {
@@ -2097,16 +2049,58 @@
return super.visitArray(name);
}
+ private void visitEndWithMemberBindingReference() {
+ if (classDeclaration.isDeclared() || memberDeclaration.isDeclared()) {
+ throw parsingContext.error(
+ "Cannot define an item explicitly and via a member-binding reference");
+ }
+ KeepBindingSymbol symbol = getBindingsHelper().resolveUserBinding(memberBindingReference);
+ KeepMemberBindingReference memberBinding = KeepBindingReference.forMember(symbol);
+
+ // If no explicit kind is set, the member binding implies it is just members.
+ if (kind == null) {
+ kind = ItemKind.ONLY_MEMBERS;
+ }
+
+ if (!kind.includesClass()) {
+ itemReferences = Collections.singletonList(memberBinding.toItemReference());
+ return;
+ }
+
+ KeepClassItemReference holderReference =
+ getBindingsHelper()
+ .getItemForBinding(memberBinding)
+ .asMemberItemPattern()
+ .getClassReference();
+ itemReferences =
+ ImmutableList.of(ensureHolderForMember(holderReference), memberBinding.toItemReference());
+ }
+
+ private KeepClassItemReference ensureHolderForMember(KeepClassItemReference holderReference) {
+ if (useBindingForClassAndMembers() && !holderReference.isBindingReference()) {
+ // Ensure a binding between the member and its holder.
+ KeepBindingSymbol holderSymbol =
+ getBindingsHelper().defineFreshBinding("HOLDER", holderReference.asClassItemPattern());
+ return KeepClassItemReference.fromBindingReference(
+ KeepClassBindingReference.forClass(holderSymbol));
+ }
+ if (!useBindingForClassAndMembers() && holderReference.isBindingReference()) {
+ // Ensure *no* binding between the member and its holder.
+ KeepItemPattern holderPattern =
+ getBindingsHelper().getItemForBinding(holderReference.asBindingReference());
+ KeepBindingSymbol holderSymbol =
+ getBindingsHelper().defineFreshBinding("HOLDER", holderPattern);
+ return KeepClassItemReference.fromBindingReference(
+ KeepClassBindingReference.forClass(holderSymbol));
+ }
+ return holderReference;
+ }
+
@Override
public void visitEnd() {
// Item defined by binding reference.
if (memberBindingReference != null) {
- if (classDeclaration.isDeclared() || memberDeclaration.isDeclared() || kind != null) {
- throw parsingContext.error(
- "Cannot define an item explicitly and via a member-binding reference");
- }
- KeepBindingSymbol symbol = getBindingsHelper().resolveUserBinding(memberBindingReference);
- itemReference = KeepBindingReference.forMember(symbol).toItemReference();
+ visitEndWithMemberBindingReference();
return;
}
@@ -2131,7 +2125,7 @@
if (memberDeclaration.isDeclared()) {
throw parsingContext.error("Item pattern for members is incompatible with kind " + kind);
}
- itemReference = classDeclaration.getValue();
+ itemReferences = Collections.singletonList(classDeclaration.getValue());
return;
}
@@ -2167,7 +2161,13 @@
.setClassReference(classReference)
.setMemberPattern(memberPattern)
.build();
- itemReference = itemPattern.toItemReference();
+
+ if (kind.includesClass()) {
+ itemReferences =
+ ImmutableList.of(ensureHolderForMember(classReference), itemPattern.toItemReference());
+ } else {
+ itemReferences = Collections.singletonList(itemPattern.toItemReference());
+ }
}
}
@@ -2258,7 +2258,7 @@
super.visitEnd();
builder.setConstraints(
constraintsParser.getValueOrDefault(KeepConstraints.defaultConstraints()));
- for (KeepItemReference item : getItemsWithBinding()) {
+ for (KeepItemReference item : getItems()) {
parent.accept(builder.setItemReference(item).build());
}
}
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepMainViaBindingTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepMainViaBindingTest.java
new file mode 100644
index 0000000..9246d9d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepMainViaBindingTest.java
@@ -0,0 +1,59 @@
+// Copyright (c) 2024, 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 com.android.tools.r8.keepanno.annotations.KeepBinding;
+import com.android.tools.r8.keepanno.annotations.KeepEdge;
+import com.android.tools.r8.keepanno.annotations.KeepItemKind;
+import com.android.tools.r8.keepanno.annotations.KeepTarget;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+
+@RunWith(Parameterized.class)
+public class KeepMainViaBindingTest extends KeepAnnoTestBase {
+
+ static final String EXPECTED = StringUtils.lines("Hello, world!");
+
+ @Parameter public KeepAnnoParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static List<KeepAnnoParameters> data() {
+ return createParameters(
+ getTestParameters().withDefaultRuntimes().withApiLevel(AndroidApiLevel.B).build());
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForKeepAnno(parameters)
+ .addProgramClasses(getInputClasses())
+ .setExcludedOuterClass(getClass())
+ .run(TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ public List<Class<?>> getInputClasses() {
+ return ImmutableList.of(TestClass.class);
+ }
+
+ static class TestClass {
+
+ @KeepEdge(
+ bindings =
+ @KeepBinding(
+ bindingName = "MainMethod",
+ classConstant = TestClass.class,
+ methodName = "main"),
+ consequences =
+ @KeepTarget(kind = KeepItemKind.CLASS_AND_METHODS, memberFromBinding = "MainMethod"))
+ public static void main(String[] args) throws Exception {
+ System.out.println("Hello, world!");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/UsedByReflectionWithNonMatchingMemberPatternsTest.java b/src/test/java/com/android/tools/r8/keepanno/UsedByReflectionWithNonMatchingMemberPatternsTest.java
new file mode 100644
index 0000000..a77ec25
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/UsedByReflectionWithNonMatchingMemberPatternsTest.java
@@ -0,0 +1,53 @@
+// Copyright (c) 2024, 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 com.android.tools.r8.keepanno.annotations.KeepItemKind;
+import com.android.tools.r8.keepanno.annotations.UsedByReflection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+
+@RunWith(Parameterized.class)
+public class UsedByReflectionWithNonMatchingMemberPatternsTest extends KeepAnnoTestBase {
+
+ static final String EXPECTED = StringUtils.lines("Hello, world!");
+
+ @Parameter public KeepAnnoParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static List<KeepAnnoParameters> data() {
+ return createParameters(
+ getTestParameters().withDefaultRuntimes().withApiLevel(AndroidApiLevel.B).build());
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForKeepAnno(parameters)
+ .addProgramClasses(getInputClasses())
+ .setExcludedOuterClass(getClass())
+ // The fields part of the UsedByReflection will be unused.
+ .allowUnusedProguardConfigurationRules()
+ .run(TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ public List<Class<?>> getInputClasses() {
+ return ImmutableList.of(TestClass.class);
+ }
+
+ @UsedByReflection(kind = KeepItemKind.CLASS_AND_FIELDS)
+ static class TestClass {
+
+ @UsedByReflection
+ public static void main(String[] args) throws Exception {
+ System.out.println("Hello, world!");
+ }
+ }
+}