Builder based AST for keep edge descriptions.
Bug: b/248408342
Change-Id: I5ba73c0e5de47506d01f617b97dd61a96b46e0c2
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepCondition.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepCondition.java
new file mode 100644
index 0000000..318ab92
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepCondition.java
@@ -0,0 +1,81 @@
+// Copyright (c) 2022, 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.experimental.keepanno.ast;
+
+/**
+ * A keep condition is the content of an item in the set of preconditions.
+ *
+ * <p>It can be trivially true, or represent some program item pattern that must be present in the
+ * program residual. When an condition is denoted by a program item, the condition also specifies
+ * the extent of the item for which it is predicated on. The extent is given by a "usage kind" that
+ * can be either its "symbolic reference" or its "actual use".
+ */
+public abstract class KeepCondition {
+
+ /** A condition that is unconditionally true. */
+ public static KeepCondition trueCondition() {
+ return KeepConditionTrue.getInstance();
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+
+ private KeepUsageKind usageKind = KeepUsageKind.symbolicReference();
+ private KeepItemPattern itemPattern;
+
+ private Builder() {}
+
+ public Builder setUsageKind(KeepUsageKind usageKind) {
+ this.usageKind = usageKind;
+ return this;
+ }
+
+ public Builder setItem(KeepItemPattern itemPattern) {
+ this.itemPattern = itemPattern;
+ return this;
+ }
+
+ public KeepCondition build() {
+ return new KeepConditionItem(itemPattern);
+ }
+ }
+
+ private static class KeepConditionTrue extends KeepCondition {
+
+ private static KeepConditionTrue INSTANCE = null;
+
+ public static KeepConditionTrue getInstance() {
+ if (INSTANCE == null) {
+ INSTANCE = new KeepConditionTrue();
+ }
+ return INSTANCE;
+ }
+ }
+
+ private static class KeepConditionFalse extends KeepCondition {
+
+ private static KeepConditionFalse INSTANCE = null;
+
+ public static KeepConditionFalse getInstance() {
+ if (INSTANCE == null) {
+ INSTANCE = new KeepConditionFalse();
+ }
+ return INSTANCE;
+ }
+ }
+
+ private static class KeepConditionItem extends KeepCondition {
+
+ private final KeepItemPattern itemPattern;
+
+ private KeepConditionItem(KeepItemPattern itemPattern) {
+ this.itemPattern = itemPattern;
+ }
+ }
+
+ private KeepCondition() {}
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepConsequences.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepConsequences.java
new file mode 100644
index 0000000..764d734
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepConsequences.java
@@ -0,0 +1,48 @@
+// Copyright (c) 2022, 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.experimental.keepanno.ast;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Set of consequences of a keep edge.
+ *
+ * <p>The consequences are "targets" described by item patterns along with "keep options" which
+ * detail what aspects of the items must be retained.
+ *
+ * <p>The consequences come into effect if the preconditions of an edge are met.
+ */
+public final class KeepConsequences {
+
+ public static class Builder {
+
+ private List<KeepTarget> targets = new ArrayList<>();
+
+ private Builder() {}
+
+ public Builder addTarget(KeepTarget target) {
+ targets.add(target);
+ return this;
+ }
+
+ public KeepConsequences build() {
+ return new KeepConsequences(targets);
+ }
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ private final List<KeepTarget> targets;
+
+ private KeepConsequences(List<KeepTarget> targets) {
+ this.targets = targets;
+ }
+
+ public boolean isEmpty() {
+ return targets.isEmpty();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepEdge.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepEdge.java
new file mode 100644
index 0000000..d85f1e8
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepEdge.java
@@ -0,0 +1,49 @@
+// Copyright (c) 2022, 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.experimental.keepanno.ast;
+
+/**
+ * An edge in the keep graph.
+ *
+ * <p>An edge describes a set of preconditions and a set of consequences. If the preconditions are
+ * met, then the consequences are put into effect.
+ */
+public final class KeepEdge {
+
+ public static class Builder {
+ private KeepPreconditions preconditions = KeepPreconditions.always();
+ private KeepConsequences consequences;
+
+ private Builder() {}
+
+ public Builder setPreconditions(KeepPreconditions preconditions) {
+ this.preconditions = preconditions;
+ return this;
+ }
+
+ public Builder setConsequences(KeepConsequences consequences) {
+ this.consequences = consequences;
+ return this;
+ }
+
+ public KeepEdge build() {
+ if (consequences.isEmpty()) {
+ throw new KeepEdgeException("KeepEdge must have non-empty set of consequences.");
+ }
+ return new KeepEdge(preconditions, consequences);
+ }
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ private final KeepPreconditions precondition;
+ private final KeepConsequences consequences;
+
+ private KeepEdge(KeepPreconditions precondition, KeepConsequences consequences) {
+ this.precondition = precondition;
+ this.consequences = consequences;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepEdgeException.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepEdgeException.java
new file mode 100644
index 0000000..3345c8f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepEdgeException.java
@@ -0,0 +1,11 @@
+// Copyright (c) 2022, 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.experimental.keepanno.ast;
+
+public class KeepEdgeException extends RuntimeException {
+
+ public KeepEdgeException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepExtendsPattern.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepExtendsPattern.java
new file mode 100644
index 0000000..af7c35f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepExtendsPattern.java
@@ -0,0 +1,65 @@
+// Copyright (c) 2022, 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.experimental.keepanno.ast;
+
+/** Pattern for matching the "extends" or "implements" clause of a class. */
+public abstract class KeepExtendsPattern {
+
+ public static KeepExtendsPattern any() {
+ return KeepExtendsAnyPattern.getInstance();
+ }
+
+ public static class Builder {
+
+ private KeepExtendsPattern pattern;
+
+ private Builder() {}
+
+ public Builder any() {
+ pattern = KeepExtendsAnyPattern.getInstance();
+ return this;
+ }
+
+ public Builder classPattern(KeepQualifiedClassNamePattern pattern) {
+ this.pattern = new KeepExtendsClassPattern(pattern);
+ return this;
+ }
+ }
+
+ private static class KeepExtendsAnyPattern extends KeepExtendsPattern {
+
+ private static KeepExtendsAnyPattern INSTANCE = null;
+
+ public static KeepExtendsAnyPattern getInstance() {
+ return INSTANCE;
+ }
+
+ @Override
+ public boolean isAny() {
+ return true;
+ }
+ }
+
+ private static class KeepExtendsClassPattern extends KeepExtendsPattern {
+
+ private final KeepQualifiedClassNamePattern pattern;
+
+ public KeepExtendsClassPattern(KeepQualifiedClassNamePattern pattern) {
+ this.pattern = pattern;
+ }
+
+ @Override
+ public boolean isAny() {
+ return pattern.isAny();
+ }
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ private KeepExtendsPattern() {}
+
+ public abstract boolean isAny();
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepFieldPattern.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepFieldPattern.java
new file mode 100644
index 0000000..04e2697
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepFieldPattern.java
@@ -0,0 +1,13 @@
+// Copyright (c) 2022, 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.experimental.keepanno.ast;
+
+public class KeepFieldPattern extends KeepMemberPattern {
+
+ private KeepFieldPattern() {}
+
+ public boolean isAnyField() {
+ return false;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepItemPattern.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepItemPattern.java
new file mode 100644
index 0000000..c994aa5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepItemPattern.java
@@ -0,0 +1,95 @@
+// Copyright (c) 2022, 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.experimental.keepanno.ast;
+
+/**
+ * A pattern for matching items in the program.
+ *
+ * <p>An item pattern can be any item, or it can describe a family of classes or a family of members
+ * on a classes.
+ *
+ * <p>A pattern cannot describe both a class *and* a member of a class. Either it is a pattern on
+ * classes or it is a pattern on members. The distinction is defined by having a "none" member
+ * pattern.
+ */
+public abstract class KeepItemPattern {
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static KeepItemPattern any() {
+ return KeepItemAnyPattern.getInstance();
+ }
+
+ public static class Builder {
+
+ private KeepQualifiedClassNamePattern classNamePattern;
+ private KeepExtendsPattern extendsPattern = KeepExtendsPattern.any();
+ private KeepMembersPattern membersPattern = KeepMembersPattern.none();
+
+ private Builder() {}
+
+ public Builder any() {
+ classNamePattern = KeepQualifiedClassNamePattern.any();
+ extendsPattern = KeepExtendsPattern.any();
+ membersPattern = KeepMembersPattern.all();
+ return this;
+ }
+
+ public Builder setClassPattern(KeepQualifiedClassNamePattern qualifiedClassNamePattern) {
+ this.classNamePattern = qualifiedClassNamePattern;
+ return this;
+ }
+
+ public Builder setExtendsPattern(KeepExtendsPattern extendsPattern) {
+ this.extendsPattern = extendsPattern;
+ return this;
+ }
+
+ public Builder setMembersPattern(KeepMembersPattern membersPattern) {
+ this.membersPattern = membersPattern;
+ return this;
+ }
+
+ public KeepItemPattern build() {
+ if (classNamePattern == null) {
+ throw new KeepEdgeException("Class pattern must define a class name pattern.");
+ }
+ if (classNamePattern.isAny() && extendsPattern.isAny() && membersPattern.isAll()) {
+ return KeepItemPattern.any();
+ }
+ return new KeepClassPattern(classNamePattern, extendsPattern, membersPattern);
+ }
+ }
+
+ private static class KeepItemAnyPattern extends KeepItemPattern {
+
+ private static KeepItemAnyPattern INSTANCE = null;
+
+ public static KeepItemAnyPattern getInstance() {
+ if (INSTANCE == null) {
+ INSTANCE = new KeepItemAnyPattern();
+ }
+ return INSTANCE;
+ }
+ }
+
+ private static class KeepClassPattern extends KeepItemPattern {
+
+ private final KeepQualifiedClassNamePattern qualifiedClassPattern;
+ private final KeepExtendsPattern extendsPattern;
+ private final KeepMembersPattern membersPattern;
+ // TODO: class annotations
+
+ private KeepClassPattern(
+ KeepQualifiedClassNamePattern qualifiedClassPattern,
+ KeepExtendsPattern extendsPattern,
+ KeepMembersPattern membersPattern) {
+ this.qualifiedClassPattern = qualifiedClassPattern;
+ this.extendsPattern = extendsPattern;
+ this.membersPattern = membersPattern;
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMemberPattern.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMemberPattern.java
new file mode 100644
index 0000000..73bae7e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMemberPattern.java
@@ -0,0 +1,38 @@
+// Copyright (c) 2022, 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.experimental.keepanno.ast;
+
+public abstract class KeepMemberPattern {
+
+ public static KeepMemberPattern anyMember() {
+ return KeepMemberAnyPattern.getInstance();
+ }
+
+ private static class KeepMemberAnyPattern extends KeepMemberPattern {
+ private static KeepMemberAnyPattern INSTANCE = null;
+
+ public static KeepMemberAnyPattern getInstance() {
+ if (INSTANCE == null) {
+ INSTANCE = new KeepMemberAnyPattern();
+ }
+ return INSTANCE;
+ }
+
+ @Override
+ public boolean isAnyMember() {
+ return true;
+ }
+ }
+
+ public boolean isAnyMember() {
+ return false;
+ }
+
+ abstract static class Builder<T extends Builder<T>> {
+
+ public abstract T self();
+
+ Builder() {}
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMembersPattern.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMembersPattern.java
new file mode 100644
index 0000000..cccab80
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMembersPattern.java
@@ -0,0 +1,138 @@
+// Copyright (c) 2022, 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.experimental.keepanno.ast;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public abstract class KeepMembersPattern {
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static KeepMembersPattern none() {
+ return KeepMembersNonePattern.getInstance();
+ }
+
+ public static KeepMembersPattern all() {
+ return KeepMembersAllPattern.getInstance();
+ }
+
+ public static class Builder {
+
+ private boolean anyMethod = false;
+ private boolean anyField = false;
+ private List<KeepMethodPattern> methods = new ArrayList<>();
+ private List<KeepFieldPattern> fields = new ArrayList<>();
+
+ public Builder addMethodPattern(KeepMethodPattern methodPattern) {
+ if (anyMethod) {
+ return this;
+ }
+ if (methodPattern.isAnyMethod()) {
+ methods.clear();
+ anyMethod = true;
+ }
+ methods.add(methodPattern);
+ return this;
+ }
+
+ public Builder addFieldPattern(KeepFieldPattern fieldPattern) {
+ if (anyField) {
+ return this;
+ }
+ if (fieldPattern.isAnyField()) {
+ fields.clear();
+ anyField = true;
+ }
+ fields.add(fieldPattern);
+ return this;
+ }
+
+ public KeepMembersPattern build() {
+ if (methods.isEmpty() && fields.isEmpty()) {
+ return KeepMembersPattern.none();
+ }
+ if (anyMethod && anyField) {
+ return KeepMembersPattern.all();
+ }
+ return new KeepMembersSomePattern(methods, fields);
+ }
+ }
+
+ private static class KeepMembersAllPattern extends KeepMembersPattern {
+
+ private static KeepMembersAllPattern INSTANCE = null;
+
+ public static KeepMembersAllPattern getInstance() {
+ if (INSTANCE == null) {
+ INSTANCE = new KeepMembersAllPattern();
+ }
+ return INSTANCE;
+ }
+
+ @Override
+ public boolean isAll() {
+ return true;
+ }
+
+ @Override
+ public boolean isNone() {
+ return true;
+ }
+ }
+
+ private static class KeepMembersNonePattern extends KeepMembersPattern {
+
+ private static KeepMembersNonePattern INSTANCE = null;
+
+ public static KeepMembersNonePattern getInstance() {
+ if (INSTANCE == null) {
+ INSTANCE = new KeepMembersNonePattern();
+ }
+ return INSTANCE;
+ }
+
+ @Override
+ public boolean isAll() {
+ return false;
+ }
+
+ @Override
+ public boolean isNone() {
+ return true;
+ }
+ }
+
+ private static class KeepMembersSomePattern extends KeepMembersPattern {
+
+ private final List<KeepMethodPattern> methods;
+ private final List<KeepFieldPattern> fields;
+
+ private KeepMembersSomePattern(List<KeepMethodPattern> methods, List<KeepFieldPattern> fields) {
+ assert !methods.isEmpty() || !fields.isEmpty();
+ this.methods = methods;
+ this.fields = fields;
+ }
+
+ @Override
+ public boolean isAll() {
+ // Since there is at least one none-all field or method this is not a match all.
+ return false;
+ }
+
+ @Override
+ public boolean isNone() {
+ // Since there is at least one field or method this is not a match none.
+ return false;
+ }
+ }
+
+ private KeepMembersPattern() {}
+
+ public abstract boolean isAll();
+
+ public abstract boolean isNone();
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodNamePattern.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodNamePattern.java
new file mode 100644
index 0000000..141046a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodNamePattern.java
@@ -0,0 +1,38 @@
+// Copyright (c) 2022, 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.experimental.keepanno.ast;
+
+public class KeepMethodNamePattern {
+
+ public static KeepMethodNamePattern any() {
+ return KeepMethodNameAnyPattern.getInstance();
+ }
+
+ public static KeepMethodNamePattern initializer() {
+ return new KeepMethodNameExactPattern("<init>");
+ }
+
+ public static KeepMethodNamePattern exact(String methodName) {
+ return new KeepMethodNameExactPattern(methodName);
+ }
+
+ private static class KeepMethodNameAnyPattern extends KeepMethodNamePattern {
+ private static KeepMethodNameAnyPattern INSTANCE = null;
+
+ public static KeepMethodNameAnyPattern getInstance() {
+ if (INSTANCE == null) {
+ INSTANCE = new KeepMethodNameAnyPattern();
+ }
+ return INSTANCE;
+ }
+ }
+
+ private static class KeepMethodNameExactPattern extends KeepMethodNamePattern {
+ private final String name;
+
+ public KeepMethodNameExactPattern(String name) {
+ this.name = name;
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodParametersPattern.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodParametersPattern.java
new file mode 100644
index 0000000..66de66d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodParametersPattern.java
@@ -0,0 +1,37 @@
+// Copyright (c) 2022, 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.experimental.keepanno.ast;
+
+public abstract class KeepMethodParametersPattern {
+
+ public static KeepMethodParametersPattern any() {
+ return Any.getInstance();
+ }
+
+ public static KeepMethodParametersPattern none() {
+ return None.getInstance();
+ }
+
+ private static class None extends KeepMethodParametersPattern {
+ private static None INSTANCE = null;
+
+ public static None getInstance() {
+ if (INSTANCE == null) {
+ INSTANCE = new None();
+ }
+ return INSTANCE;
+ }
+ }
+
+ private static class Any extends KeepMethodParametersPattern {
+ private static Any INSTANCE = null;
+
+ public static Any getInstance() {
+ if (INSTANCE == null) {
+ INSTANCE = new Any();
+ }
+ return INSTANCE;
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodPattern.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodPattern.java
new file mode 100644
index 0000000..8ac3c82
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodPattern.java
@@ -0,0 +1,64 @@
+// Copyright (c) 2022, 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.experimental.keepanno.ast;
+
+public final class KeepMethodPattern extends KeepMemberPattern {
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder extends KeepMemberPattern.Builder<Builder> {
+
+ private KeepMethodNamePattern namePattern = null;
+ private KeepMethodReturnTypePattern returnTypePattern = KeepMethodReturnTypePattern.any();
+ private KeepMethodParametersPattern parametersPattern = KeepMethodParametersPattern.any();
+
+ private Builder() {}
+
+ @Override
+ public Builder self() {
+ return this;
+ }
+
+ public Builder setNamePattern(KeepMethodNamePattern namePattern) {
+ this.namePattern = namePattern;
+ return self();
+ }
+
+ public Builder setReturnTypeVoid() {
+ returnTypePattern = KeepMethodReturnTypePattern.voidType();
+ return self();
+ }
+
+ public Builder setParametersPattern(KeepMethodParametersPattern parametersPattern) {
+ this.parametersPattern = parametersPattern;
+ return self();
+ }
+
+ public KeepMethodPattern build() {
+ if (namePattern == null) {
+ throw new KeepEdgeException("Method pattern must declar a name pattern");
+ }
+ return new KeepMethodPattern(namePattern, returnTypePattern, parametersPattern);
+ }
+ }
+
+ private final KeepMethodNamePattern namePattern;
+ private final KeepMethodReturnTypePattern returnTypePattern;
+ private final KeepMethodParametersPattern parametersPattern;
+
+ private KeepMethodPattern(
+ KeepMethodNamePattern namePattern,
+ KeepMethodReturnTypePattern returnTypePattern,
+ KeepMethodParametersPattern parametersPattern) {
+ this.namePattern = namePattern;
+ this.returnTypePattern = returnTypePattern;
+ this.parametersPattern = parametersPattern;
+ }
+
+ public boolean isAnyMethod() {
+ return false;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodReturnTypePattern.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodReturnTypePattern.java
new file mode 100644
index 0000000..acad630
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodReturnTypePattern.java
@@ -0,0 +1,37 @@
+// Copyright (c) 2022, 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.experimental.keepanno.ast;
+
+public class KeepMethodReturnTypePattern {
+
+ public static KeepMethodReturnTypePattern any() {
+ return Any.getInstance();
+ }
+
+ public static KeepMethodReturnTypePattern voidType() {
+ return VoidType.getInstance();
+ }
+
+ private static class VoidType extends KeepMethodReturnTypePattern {
+ private static VoidType INSTANCE = null;
+
+ public static VoidType getInstance() {
+ if (INSTANCE == null) {
+ INSTANCE = new VoidType();
+ }
+ return INSTANCE;
+ }
+ }
+
+ private static class Any extends KeepMethodReturnTypePattern {
+ private static Any INSTANCE = null;
+
+ public static Any getInstance() {
+ if (INSTANCE == null) {
+ INSTANCE = new Any();
+ }
+ return INSTANCE;
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepOptions.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepOptions.java
new file mode 100644
index 0000000..3c3aa11
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepOptions.java
@@ -0,0 +1,37 @@
+// Copyright (c) 2022, 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.experimental.keepanno.ast;
+
+import java.util.Collections;
+import java.util.Set;
+
+public final class KeepOptions {
+
+ public enum KeepOption {
+ SHRINKING,
+ OPTIMIZING,
+ OBFUSCATING
+ }
+
+ public static KeepOptions keepAll() {
+ if (ALLOW_NONE_INSTANCE == null) {
+ ALLOW_NONE_INSTANCE = new KeepOptions(true, Collections.emptySet());
+ }
+ return ALLOW_NONE_INSTANCE;
+ }
+
+ private static KeepOptions ALLOW_NONE_INSTANCE = null;
+
+ private final boolean allowIfSet;
+ private final Set<KeepOption> options;
+
+ private KeepOptions(boolean allowIfSet, Set<KeepOption> options) {
+ this.allowIfSet = allowIfSet;
+ this.options = options;
+ }
+
+ public boolean allow(KeepOption option) {
+ return options.contains(option) == allowIfSet;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepPackagePattern.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepPackagePattern.java
new file mode 100644
index 0000000..e5353c0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepPackagePattern.java
@@ -0,0 +1,108 @@
+// Copyright (c) 2022, 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.experimental.keepanno.ast;
+
+public abstract class KeepPackagePattern {
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static KeepPackagePattern any() {
+ return KeepPackageAnyPattern.getInstance();
+ }
+
+ public static KeepPackagePattern top() {
+ return KeepPackageTopPattern.getInstance();
+ }
+
+ public static KeepPackagePattern exact(String fullPackage) {
+ return KeepPackagePattern.builder().exact(fullPackage).build();
+ }
+
+ public static class Builder {
+
+ private KeepPackagePattern pattern;
+
+ public Builder any() {
+ pattern = KeepPackageAnyPattern.getInstance();
+ return this;
+ }
+
+ public Builder top() {
+ pattern = KeepPackageTopPattern.getInstance();
+ return this;
+ }
+
+ public Builder exact(String fullPackage) {
+ pattern =
+ fullPackage.isEmpty()
+ ? KeepPackagePattern.top()
+ : new KeepPackageExactPattern(fullPackage);
+ return this;
+ }
+
+ public KeepPackagePattern build() {
+ if (pattern == null) {
+ throw new KeepEdgeException("Invalid package pattern: null");
+ }
+ return pattern;
+ }
+ }
+
+ private static final class KeepPackageAnyPattern extends KeepPackagePattern {
+
+ private static KeepPackageAnyPattern INSTANCE = null;
+
+ public static KeepPackageAnyPattern getInstance() {
+ if (INSTANCE == null) {
+ INSTANCE = new KeepPackageAnyPattern();
+ }
+ return INSTANCE;
+ }
+
+ private KeepPackageAnyPattern() {}
+
+ @Override
+ public boolean isAny() {
+ return true;
+ }
+ }
+
+ private static final class KeepPackageTopPattern extends KeepPackagePattern {
+
+ private static KeepPackageTopPattern INSTANCE = null;
+
+ public static KeepPackageTopPattern getInstance() {
+ if (INSTANCE == null) {
+ INSTANCE = new KeepPackageTopPattern();
+ }
+ return INSTANCE;
+ }
+
+ private KeepPackageTopPattern() {}
+
+ @Override
+ public boolean isAny() {
+ return false;
+ }
+ }
+
+ private static final class KeepPackageExactPattern extends KeepPackagePattern {
+
+ private final String fullPackage;
+
+ private KeepPackageExactPattern(String fullPackage) {
+ this.fullPackage = fullPackage;
+ // TODO: Verify valid package identifiers.
+ }
+
+ @Override
+ public boolean isAny() {
+ return false;
+ }
+ }
+
+ public abstract boolean isAny();
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepPreconditions.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepPreconditions.java
new file mode 100644
index 0000000..ea347b4
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepPreconditions.java
@@ -0,0 +1,55 @@
+// Copyright (c) 2022, 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.experimental.keepanno.ast;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public abstract class KeepPreconditions {
+
+ private static class KeepPreconditionsAlways extends KeepPreconditions {
+
+ private static KeepPreconditionsAlways INSTANCE = null;
+
+ public static KeepPreconditionsAlways getInstance() {
+ if (INSTANCE == null) {
+ INSTANCE = new KeepPreconditionsAlways();
+ }
+ return INSTANCE;
+ }
+ }
+
+ private static class KeepPreconditionsSome extends KeepPreconditions {
+
+ private final List<KeepCondition> preconditions;
+
+ private KeepPreconditionsSome(List<KeepCondition> preconditions) {
+ this.preconditions = preconditions;
+ }
+ }
+
+ public static class Builder {
+
+ private List<KeepCondition> preconditions = new ArrayList<>();
+
+ private Builder() {}
+
+ public Builder addCondition(KeepCondition condition) {
+ preconditions.add(condition);
+ return this;
+ }
+
+ public KeepPreconditions build() {
+ return new KeepPreconditionsSome(preconditions);
+ }
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static KeepPreconditions always() {
+ return KeepPreconditionsAlways.getInstance();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepQualifiedClassNamePattern.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepQualifiedClassNamePattern.java
new file mode 100644
index 0000000..5a9151f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepQualifiedClassNamePattern.java
@@ -0,0 +1,72 @@
+// Copyright (c) 2022, 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.experimental.keepanno.ast;
+
+public class KeepQualifiedClassNamePattern {
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static KeepQualifiedClassNamePattern any() {
+ return KeepQualifiedClassNamePattern.builder()
+ .setPackagePattern(KeepPackagePattern.any())
+ .setNamePattern(KeepUnqualfiedClassNamePattern.any())
+ .build();
+ }
+
+ public static KeepQualifiedClassNamePattern exact(String qualifiedClassName) {
+ int pkgSeparator = qualifiedClassName.lastIndexOf('.');
+ if (pkgSeparator == 0) {
+ throw new KeepEdgeException("Unexpected '.' at index 0 in '" + qualifiedClassName + "'");
+ }
+ if (pkgSeparator > 0) {
+ return KeepQualifiedClassNamePattern.builder()
+ .setPackagePattern(
+ KeepPackagePattern.exact(qualifiedClassName.substring(0, pkgSeparator)))
+ .setNamePattern(
+ KeepUnqualfiedClassNamePattern.exact(qualifiedClassName.substring(pkgSeparator + 1)))
+ .build();
+ }
+ return KeepQualifiedClassNamePattern.builder()
+ .setPackagePattern(KeepPackagePattern.top())
+ .setNamePattern(KeepUnqualfiedClassNamePattern.exact(qualifiedClassName))
+ .build();
+ }
+
+ public static class Builder {
+
+ private KeepPackagePattern packagePattern;
+ private KeepUnqualfiedClassNamePattern namePattern;
+
+ private Builder() {}
+
+ public Builder setPackagePattern(KeepPackagePattern packagePattern) {
+ this.packagePattern = packagePattern;
+ return this;
+ }
+
+ public Builder setNamePattern(KeepUnqualfiedClassNamePattern namePattern) {
+ this.namePattern = namePattern;
+ return this;
+ }
+
+ public KeepQualifiedClassNamePattern build() {
+ return new KeepQualifiedClassNamePattern(packagePattern, namePattern);
+ }
+ }
+
+ private final KeepPackagePattern packagePattern;
+ private final KeepUnqualfiedClassNamePattern namePattern;
+
+ public KeepQualifiedClassNamePattern(
+ KeepPackagePattern packagePattern, KeepUnqualfiedClassNamePattern namePattern) {
+ this.packagePattern = packagePattern;
+ this.namePattern = namePattern;
+ }
+
+ public boolean isAny() {
+ return packagePattern.isAny() && namePattern.isAny();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepTarget.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepTarget.java
new file mode 100644
index 0000000..078fab6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepTarget.java
@@ -0,0 +1,48 @@
+// Copyright (c) 2022, 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.experimental.keepanno.ast;
+
+public class KeepTarget {
+
+ public static KeepTarget any() {
+ return KeepTarget.builder().setItem(KeepItemPattern.any()).build();
+ }
+
+ public static class Builder {
+
+ private KeepItemPattern item;
+ private KeepOptions options = KeepOptions.keepAll();
+
+ private Builder() {}
+
+ public Builder setItem(KeepItemPattern item) {
+ this.item = item;
+ return this;
+ }
+
+ public Builder setOptions(KeepOptions options) {
+ this.options = options;
+ return this;
+ }
+
+ public KeepTarget build() {
+ if (item == null) {
+ throw new KeepEdgeException("Target must define an item pattern");
+ }
+ return new KeepTarget(item, options);
+ }
+ }
+
+ private final KeepItemPattern item;
+ private final KeepOptions options;
+
+ private KeepTarget(KeepItemPattern item, KeepOptions options) {
+ this.item = item;
+ this.options = options;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepUnqualfiedClassNamePattern.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepUnqualfiedClassNamePattern.java
new file mode 100644
index 0000000..50f94a6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepUnqualfiedClassNamePattern.java
@@ -0,0 +1,76 @@
+// Copyright (c) 2022, 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.experimental.keepanno.ast;
+
+public abstract class KeepUnqualfiedClassNamePattern {
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static KeepUnqualfiedClassNamePattern any() {
+ return KeepClassNameAnyPattern.getInstance();
+ }
+
+ public static KeepUnqualfiedClassNamePattern exact(String className) {
+ return builder().exact(className).build();
+ }
+
+ public static class Builder {
+
+ private KeepUnqualfiedClassNamePattern pattern;
+
+ public Builder any() {
+ pattern = KeepClassNameAnyPattern.getInstance();
+ return this;
+ }
+
+ public Builder exact(String className) {
+ pattern = new KeepClassNameExactPattern(className);
+ return this;
+ }
+
+ public KeepUnqualfiedClassNamePattern build() {
+ if (pattern == null) {
+ throw new KeepEdgeException("Invalid class name pattern: null");
+ }
+ return pattern;
+ }
+ }
+
+ private static class KeepClassNameAnyPattern extends KeepUnqualfiedClassNamePattern {
+
+ private static KeepClassNameAnyPattern INSTANCE = null;
+
+ public static KeepClassNameAnyPattern getInstance() {
+ if (INSTANCE == null) {
+ INSTANCE = new KeepClassNameAnyPattern();
+ }
+ return INSTANCE;
+ }
+
+ private KeepClassNameAnyPattern() {}
+
+ @Override
+ public boolean isAny() {
+ return true;
+ }
+ }
+
+ private static class KeepClassNameExactPattern extends KeepUnqualfiedClassNamePattern {
+
+ private final String className;
+
+ private KeepClassNameExactPattern(String className) {
+ this.className = className;
+ }
+
+ @Override
+ public boolean isAny() {
+ return false;
+ }
+ }
+
+ public abstract boolean isAny();
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepUsageKind.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepUsageKind.java
new file mode 100644
index 0000000..0e4a9e9
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepUsageKind.java
@@ -0,0 +1,70 @@
+// Copyright (c) 2022, 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.experimental.keepanno.ast;
+
+public abstract class KeepUsageKind {
+
+ /**
+ * Symbolically referenced classes, method and fields.
+ *
+ * <p>A symbolic reference of class, method or field is a reference that may not make use of the
+ * item for its "runtime" effect.
+ *
+ * <p>Symbolic class references could be class constants, checkcast, instanceof as well as the use
+ * of the class type in type hierarchies and annotations. The use of a class reference does not
+ * imply that the class is ever instantiated as an object instance.
+ *
+ * <p>For methods, a reference may be in use by the need to retain the method without it actually
+ * ever being called. This may be the case for methods with overrides where removal of a method
+ * could cause a semantic change to the program. Similar for fields.
+ */
+ public static KeepUsageKind symbolicReference() {
+ return SymbolicReference.getInstance();
+ }
+
+ /**
+ * Actual usages include instantiated classes, executed methods and accessed fields.
+ *
+ * <p>An actual use is stronger than "symbolic reference" and thus implies that the item is also
+ * "referenced". Thus, the set of all used items is a subset of the referenced items.
+ *
+ * <p>An actual use of class means that the class has instantiated instances. For methods, it
+ * means that the method is invoked and executed in the typical sense. For fields, it means that
+ * the field is read from or written to.
+ *
+ * <p>Note, it is possible for a static method or field to be used but its holder class still only
+ * being "referenced" as there are no instances of the class.
+ */
+ public static KeepUsageKind actualUse() {
+ return ActualUse.getInstance();
+ }
+
+ private static class SymbolicReference extends KeepUsageKind {
+ private static SymbolicReference INSTANCE = null;
+
+ public static SymbolicReference getInstance() {
+ if (INSTANCE == null) {
+ INSTANCE = new SymbolicReference();
+ }
+ return INSTANCE;
+ }
+
+ private SymbolicReference() {}
+ }
+
+ private static class ActualUse extends KeepUsageKind {
+ private static ActualUse INSTANCE = null;
+
+ public static ActualUse getInstance() {
+ if (INSTANCE == null) {
+ INSTANCE = new ActualUse();
+ }
+ return INSTANCE;
+ }
+
+ private ActualUse() {}
+ }
+
+ private KeepUsageKind() {}
+}
diff --git a/src/test/java/com/android/tools/r8/experimental/keepanno/ast/KeepEdgeApiTest.java b/src/test/java/com/android/tools/r8/experimental/keepanno/ast/KeepEdgeApiTest.java
new file mode 100644
index 0000000..f4a1b8d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/experimental/keepanno/ast/KeepEdgeApiTest.java
@@ -0,0 +1,83 @@
+// Copyright (c) 2022, 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.experimental.keepanno.ast;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class KeepEdgeApiTest extends TestBase {
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withNoneRuntime().build();
+ }
+
+ public KeepEdgeApiTest(TestParameters parameters) {
+ parameters.assertNoneRuntime();
+ }
+
+ @Test
+ public void testKeepAll() {
+ // Equivalent of: -keep class * { *; }
+ KeepEdge edge =
+ KeepEdge.builder()
+ .setConsequences(
+ KeepConsequences.builder()
+ .addTarget(KeepTarget.builder().setItem(KeepItemPattern.any()).build())
+ .build())
+ .build();
+ }
+
+ @Test
+ public void testKeepClass() throws Exception {
+ // Equivalent of: -keep class com.example.Foo {}
+ KeepQualifiedClassNamePattern clazz = KeepQualifiedClassNamePattern.exact("com.example.Foo");
+ KeepItemPattern item = KeepItemPattern.builder().setClassPattern(clazz).build();
+ KeepTarget target = KeepTarget.builder().setItem(item).build();
+ KeepConsequences consequences = KeepConsequences.builder().addTarget(target).build();
+ KeepEdge edge = KeepEdge.builder().setConsequences(consequences).build();
+ }
+
+ @Test
+ public void testKeepInitIfReferenced() throws Exception {
+ // Equivalent of: -if class com.example.Foo -keep class com.example.Foo { void <init>(); }
+ KeepQualifiedClassNamePattern classPattern =
+ KeepQualifiedClassNamePattern.exact("com.example.Foo");
+ KeepEdge.builder()
+ .setPreconditions(
+ KeepPreconditions.builder()
+ .addCondition(
+ KeepCondition.builder()
+ .setUsageKind(KeepUsageKind.symbolicReference())
+ .setItem(KeepItemPattern.builder().setClassPattern(classPattern).build())
+ .build())
+ .build())
+ .setConsequences(
+ KeepConsequences.builder()
+ .addTarget(
+ KeepTarget.builder()
+ .setItem(
+ KeepItemPattern.builder()
+ .setClassPattern(classPattern)
+ .setMembersPattern(
+ KeepMembersPattern.builder()
+ .addMethodPattern(
+ KeepMethodPattern.builder()
+ .setNamePattern(KeepMethodNamePattern.initializer())
+ .setParametersPattern(
+ KeepMethodParametersPattern.none())
+ .setReturnTypeVoid()
+ .build())
+ .build())
+ .build())
+ .build())
+ .build())
+ .build();
+ }
+}