Reland "ASM-based reader/writer and Java preprocessor for keep annotations."
Bug: b/248408342
This reverts commit 5189431b2affc70400b7df1fae2dac363d54469e.
Change-Id: I204081bad423a746a071bbe75745c571e9f9ebb9
diff --git a/build.gradle b/build.gradle
index eca19a7..8dbce66 100644
--- a/build.gradle
+++ b/build.gradle
@@ -198,6 +198,11 @@
}
output.resourcesDir = 'build/classes/kotlinR8TestResources'
}
+ keepanno {
+ java {
+ srcDirs = ['src/keepanno/main/java']
+ }
+ }
}
// Ensure importing into IntelliJ IDEA use the same output directories as Gradle. In tests we
@@ -207,7 +212,7 @@
idea {
sourceSets.all { SourceSet sources ->
module {
- if (sources.name == "main") {
+ if (sources.name == "main" || sources.name == "keepanno") {
sourceDirs += sources.java.srcDirs
outputDir sources.output.classesDirs[0]
} else {
@@ -311,6 +316,11 @@
apiUsageSampleCompile "com.google.guava:guava:$guavaVersion"
kotlinR8TestResourcesCompileOnly "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
errorprone("com.google.errorprone:error_prone_core:$errorproneVersion")
+
+ keepannoCompile group: 'org.ow2.asm', name: 'asm', version: asmVersion
+ keepannoCompile "com.google.guava:guava:$guavaVersion"
+ testCompile sourceSets.keepanno.output
+ testRuntime sourceSets.keepanno.output
}
def r8LibPath = "$buildDir/libs/r8lib.jar"
@@ -1093,6 +1103,7 @@
task testJarSources(type: Jar, dependsOn: [testClasses, buildLibraryDesugarConversions]) {
archiveFileName = "r8testsbase.jar"
from sourceSets.test.output
+ from sourceSets.keepanno.output
// We only want to include tests that use R8 when generating keep rules for applymapping.
include "com/android/tools/r8/**"
include "android/**"
diff --git a/src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/annotations/KeepCondition.java b/src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/annotations/KeepCondition.java
new file mode 100644
index 0000000..6dd46b2
--- /dev/null
+++ b/src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/annotations/KeepCondition.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.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.ANNOTATION_TYPE)
+@Retention(RetentionPolicy.CLASS)
+public @interface KeepCondition {}
diff --git a/src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/annotations/KeepConstants.java b/src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/annotations/KeepConstants.java
new file mode 100644
index 0000000..c12ba98
--- /dev/null
+++ b/src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/annotations/KeepConstants.java
@@ -0,0 +1,34 @@
+// 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.annotations;
+
+/**
+ * Utility class for referencing the various keep annotations and their structure.
+ *
+ * <p>Use of these references avoids poluting the Java namespace with imports of the java
+ * annotations which overlap in name with the actual semantic AST types.
+ */
+public final class KeepConstants {
+
+ public static String getDescriptor(Class<?> clazz) {
+ return "L" + clazz.getTypeName().replace('.', '/') + ";";
+ }
+
+ public static String getBinaryNameFromClassTypeName(String classTypeName) {
+ return classTypeName.replace('.', '/');
+ }
+
+ public static final class Edge {
+ public static final Class<KeepEdge> CLASS = KeepEdge.class;
+ public static final String DESCRIPTOR = getDescriptor(CLASS);
+ public static final String preconditions = "preconditions";
+ public static final String consequences = "consequences";
+ }
+
+ public static final class Target {
+ public static final Class<KeepTarget> CLASS = KeepTarget.class;
+ public static final String DESCRIPTOR = getDescriptor(CLASS);
+ public static final String classConstant = "classConstant";
+ }
+}
diff --git a/src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/annotations/KeepEdge.java b/src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/annotations/KeepEdge.java
new file mode 100644
index 0000000..d1ae40a
--- /dev/null
+++ b/src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/annotations/KeepEdge.java
@@ -0,0 +1,17 @@
+// 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.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.CLASS)
+public @interface KeepEdge {
+ KeepCondition[] preconditions() default {};
+
+ KeepTarget[] consequences();
+}
diff --git a/src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/annotations/KeepTarget.java b/src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/annotations/KeepTarget.java
new file mode 100644
index 0000000..f272568
--- /dev/null
+++ b/src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/annotations/KeepTarget.java
@@ -0,0 +1,19 @@
+// 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.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.ANNOTATION_TYPE)
+@Retention(RetentionPolicy.CLASS)
+public @interface KeepTarget {
+ Class<?> classConstant() default Object.class;
+
+ String classTypeName() default "";
+
+ String methodName() default "";
+}
diff --git a/src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/asm/KeepEdgeReader.java b/src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/asm/KeepEdgeReader.java
new file mode 100644
index 0000000..3c705c7
--- /dev/null
+++ b/src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/asm/KeepEdgeReader.java
@@ -0,0 +1,172 @@
+// 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.asm;
+
+import com.android.tools.r8.experimental.keepanno.annotations.KeepConstants.Edge;
+import com.android.tools.r8.experimental.keepanno.annotations.KeepConstants.Target;
+import com.android.tools.r8.experimental.keepanno.ast.KeepConsequences;
+import com.android.tools.r8.experimental.keepanno.ast.KeepEdge;
+import com.android.tools.r8.experimental.keepanno.ast.KeepEdgeException;
+import com.android.tools.r8.experimental.keepanno.ast.KeepItemPattern;
+import com.android.tools.r8.experimental.keepanno.ast.KeepPreconditions;
+import com.android.tools.r8.experimental.keepanno.ast.KeepQualifiedClassNamePattern;
+import com.android.tools.r8.experimental.keepanno.ast.KeepTarget;
+import java.util.HashSet;
+import java.util.Set;
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+public class KeepEdgeReader implements Opcodes {
+
+ public static int ASM_VERSION = ASM9;
+
+ public static Set<KeepEdge> readKeepEdges(byte[] classFileBytes) {
+ ClassReader reader = new ClassReader(classFileBytes);
+ Set<KeepEdge> edges = new HashSet<>();
+ reader.accept(new KeepEdgeClassVisitor(edges::add), ClassReader.SKIP_CODE);
+ return edges;
+ }
+
+ private static class KeepEdgeClassVisitor extends ClassVisitor {
+ private final Parent<KeepEdge> parent;
+
+ KeepEdgeClassVisitor(Parent<KeepEdge> parent) {
+ super(ASM_VERSION);
+ this.parent = parent;
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
+ // Skip any visible annotations as @KeepEdge is not runtime visible.
+ if (!visible && descriptor.equals(Edge.DESCRIPTOR)) {
+ return new KeepEdgeVisitor(parent);
+ }
+ return null;
+ }
+ }
+
+ // Interface for providing AST result(s) for a sub-tree back up to its parent.
+ private interface Parent<T> {
+ void accept(T result);
+ }
+
+ private abstract static class AnnotationVisitorBase extends AnnotationVisitor {
+
+ AnnotationVisitorBase() {
+ super(ASM_VERSION);
+ }
+
+ @Override
+ public void visit(String name, Object value) {
+ throw new KeepEdgeException("Unexpected value in @KeepEdge: " + name + " = " + value);
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String name, String descriptor) {
+ throw new KeepEdgeException("Unexpected annotation in @KeepEdge: " + name);
+ }
+
+ @Override
+ public void visitEnum(String name, String descriptor, String value) {
+ throw new KeepEdgeException("Unexpected enum in @KeepEdge: " + name);
+ }
+
+ @Override
+ public AnnotationVisitor visitArray(String name) {
+ throw new KeepEdgeException("Unexpected array in @KeepEdge: " + name);
+ }
+ }
+
+ private static class KeepEdgeVisitor extends AnnotationVisitorBase {
+ private final Parent<KeepEdge> parent;
+ private final KeepEdge.Builder builder = KeepEdge.builder();
+
+ KeepEdgeVisitor(Parent<KeepEdge> parent) {
+ this.parent = parent;
+ }
+
+ @Override
+ public AnnotationVisitor visitArray(String name) {
+ if (name.equals(Edge.preconditions)) {
+ return new KeepPreconditionsVisitor(builder::setPreconditions);
+ }
+ if (name.equals(Edge.consequences)) {
+ return new KeepConsequencesVisitor(builder::setConsequences);
+ }
+ return super.visitArray(name);
+ }
+
+ @Override
+ public void visitEnd() {
+ parent.accept(builder.build());
+ }
+ }
+
+ private static class KeepPreconditionsVisitor extends AnnotationVisitorBase {
+ private final Parent<KeepPreconditions> parent;
+
+ public KeepPreconditionsVisitor(Parent<KeepPreconditions> parent) {
+ this.parent = parent;
+ }
+ }
+
+ private static class KeepConsequencesVisitor extends AnnotationVisitorBase {
+ private final Parent<KeepConsequences> parent;
+ private final KeepConsequences.Builder builder = KeepConsequences.builder();
+
+ public KeepConsequencesVisitor(Parent<KeepConsequences> parent) {
+ this.parent = parent;
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String name, String descriptor) {
+ if (descriptor.equals(Target.DESCRIPTOR)) {
+ return new KeepTargetVisitor(builder::addTarget);
+ }
+ return super.visitAnnotation(name, descriptor);
+ }
+
+ @Override
+ public void visitEnd() {
+ parent.accept(builder.build());
+ }
+ }
+
+ private static class KeepTargetVisitor extends AnnotationVisitorBase {
+ private final Parent<KeepTarget> parent;
+ private String classConstant = null;
+
+ public KeepTargetVisitor(Parent<KeepTarget> parent) {
+ this.parent = parent;
+ }
+
+ @Override
+ public void visit(String name, Object value) {
+ if (name.equals(Target.classConstant) && value instanceof Type) {
+ classConstant = ((Type) value).getClassName();
+ return;
+ }
+ super.visit(name, value);
+ }
+
+ @Override
+ public void visitEnd() {
+ if (classConstant != null) {
+ KeepTarget target =
+ KeepTarget.builder()
+ .setItem(
+ KeepItemPattern.builder()
+ .setClassPattern(KeepQualifiedClassNamePattern.exact(classConstant))
+ .build())
+ .build();
+ parent.accept(target);
+ return;
+ }
+ throw new KeepEdgeException("Unexpected failure to build @KeepTarget");
+ }
+ }
+}
diff --git a/src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/asm/KeepEdgeWriter.java b/src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/asm/KeepEdgeWriter.java
new file mode 100644
index 0000000..bda02d8
--- /dev/null
+++ b/src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/asm/KeepEdgeWriter.java
@@ -0,0 +1,75 @@
+// 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.asm;
+
+import com.android.tools.r8.experimental.keepanno.annotations.KeepConstants;
+import com.android.tools.r8.experimental.keepanno.annotations.KeepConstants.Edge;
+import com.android.tools.r8.experimental.keepanno.ast.KeepConsequences;
+import com.android.tools.r8.experimental.keepanno.ast.KeepEdge;
+import com.android.tools.r8.experimental.keepanno.ast.KeepPreconditions;
+import com.android.tools.r8.experimental.keepanno.ast.KeepQualifiedClassNamePattern;
+import com.android.tools.r8.experimental.keepanno.utils.Unimplemented;
+import java.util.function.BiFunction;
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+public class KeepEdgeWriter implements Opcodes {
+
+ public static void writeEdge(KeepEdge edge, ClassVisitor visitor) {
+ writeEdge(edge, visitor::visitAnnotation);
+ }
+
+ public static void writeEdge(
+ KeepEdge edge, BiFunction<String, Boolean, AnnotationVisitor> getVisitor) {
+ new KeepEdgeWriter().writeEdge(edge, getVisitor.apply(Edge.DESCRIPTOR, false));
+ }
+
+ private void writeEdge(KeepEdge edge, AnnotationVisitor visitor) {
+ writePreconditions(visitor, edge.getPreconditions());
+ writeConsequences(visitor, edge.getConsequences());
+ visitor.visitEnd();
+ }
+
+ private void writePreconditions(AnnotationVisitor visitor, KeepPreconditions preconditions) {
+ if (preconditions.isAlways()) {
+ return;
+ }
+ throw new Unimplemented();
+ }
+
+ private void writeConsequences(AnnotationVisitor visitor, KeepConsequences consequences) {
+ assert !consequences.isEmpty();
+ String ignoredArrayValueName = null;
+ AnnotationVisitor arrayVisitor = visitor.visitArray(KeepConstants.Edge.consequences);
+ consequences.forEachTarget(
+ target -> {
+ AnnotationVisitor targetVisitor =
+ arrayVisitor.visitAnnotation(ignoredArrayValueName, KeepConstants.Target.DESCRIPTOR);
+ // No options imply keep all.
+ if (!target.getOptions().isKeepAll()) {
+ throw new Unimplemented();
+ }
+ target
+ .getItem()
+ .match(
+ () -> {
+ throw new Unimplemented();
+ },
+ clazz -> {
+ KeepQualifiedClassNamePattern namePattern = clazz.getClassNamePattern();
+ if (namePattern.isExact()) {
+ Type typeConstant = Type.getType(namePattern.getExactDescriptor());
+ targetVisitor.visit(KeepConstants.Target.classConstant, typeConstant);
+ } else {
+ throw new Unimplemented();
+ }
+ return null;
+ });
+ targetVisitor.visitEnd();
+ });
+ arrayVisitor.visitEnd();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepCondition.java b/src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepCondition.java
similarity index 99%
rename from src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepCondition.java
rename to src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepCondition.java
index acd327d..ce3f502 100644
--- a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepCondition.java
+++ b/src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepCondition.java
@@ -41,5 +41,5 @@
public KeepItemPattern getItemPattern() {
return itemPattern;
- }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepConsequences.java b/src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepConsequences.java
similarity index 78%
rename from src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepConsequences.java
rename to src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepConsequences.java
index 6a227fd..524192e 100644
--- a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepConsequences.java
+++ b/src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepConsequences.java
@@ -40,6 +40,7 @@
private final List<KeepTarget> targets;
private KeepConsequences(List<KeepTarget> targets) {
+ assert targets != null;
this.targets = targets;
}
@@ -50,4 +51,21 @@
public void forEachTarget(Consumer<KeepTarget> fn) {
targets.forEach(fn);
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ KeepConsequences that = (KeepConsequences) o;
+ return targets.equals(that.targets);
+ }
+
+ @Override
+ public int hashCode() {
+ return targets.hashCode();
+ }
}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepEdge.java b/src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepEdge.java
similarity index 76%
rename from src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepEdge.java
rename to src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepEdge.java
index f61483a..7cd6d9f 100644
--- a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepEdge.java
+++ b/src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepEdge.java
@@ -3,6 +3,8 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.experimental.keepanno.ast;
+import java.util.Objects;
+
/**
* An edge in the keep graph.
*
@@ -43,6 +45,8 @@
private final KeepConsequences consequences;
private KeepEdge(KeepPreconditions preconditions, KeepConsequences consequences) {
+ assert preconditions != null;
+ assert consequences != null;
this.preconditions = preconditions;
this.consequences = consequences;
}
@@ -54,4 +58,22 @@
public KeepConsequences getConsequences() {
return consequences;
}
+
+ @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);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepEdgeException.java b/src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepEdgeException.java
similarity index 100%
rename from src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepEdgeException.java
rename to src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepEdgeException.java
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepExtendsPattern.java b/src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepExtendsPattern.java
similarity index 74%
rename from src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepExtendsPattern.java
rename to src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepExtendsPattern.java
index 17e543f..1f6ef9d 100644
--- a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepExtendsPattern.java
+++ b/src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepExtendsPattern.java
@@ -42,6 +42,16 @@
public boolean isAny() {
return true;
}
+
+ @Override
+ public boolean equals(Object obj) {
+ return this == obj;
+ }
+
+ @Override
+ public int hashCode() {
+ return System.identityHashCode(this);
+ }
}
private static class KeepExtendsClassPattern extends KeepExtendsPattern {
@@ -49,6 +59,7 @@
private final KeepQualifiedClassNamePattern pattern;
public KeepExtendsClassPattern(KeepQualifiedClassNamePattern pattern) {
+ assert pattern != null;
this.pattern = pattern;
}
@@ -56,6 +67,23 @@
public boolean isAny() {
return pattern.isAny();
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ KeepExtendsClassPattern that = (KeepExtendsClassPattern) o;
+ return pattern.equals(that.pattern);
+ }
+
+ @Override
+ public int hashCode() {
+ return pattern.hashCode();
+ }
}
public static Builder builder() {
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepFieldPattern.java b/src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepFieldPattern.java
similarity index 100%
rename from src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepFieldPattern.java
rename to src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepFieldPattern.java
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepItemPattern.java b/src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepItemPattern.java
similarity index 82%
rename from src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepItemPattern.java
rename to src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepItemPattern.java
index 3c6f947..443d63b 100644
--- a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepItemPattern.java
+++ b/src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepItemPattern.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.experimental.keepanno.ast;
+import java.util.Objects;
import java.util.function.Function;
import java.util.function.Supplier;
@@ -87,6 +88,16 @@
public <T> T match(Supplier<T> onAny, Function<KeepClassPattern, T> onItem) {
return onAny.get();
}
+
+ @Override
+ public boolean equals(Object obj) {
+ return this == obj;
+ }
+
+ @Override
+ public int hashCode() {
+ return System.identityHashCode(this);
+ }
}
public static class KeepClassPattern extends KeepItemPattern {
@@ -100,6 +111,9 @@
KeepQualifiedClassNamePattern qualifiedClassPattern,
KeepExtendsPattern extendsPattern,
KeepMembersPattern membersPattern) {
+ assert qualifiedClassPattern != null;
+ assert extendsPattern != null;
+ assert membersPattern != null;
this.qualifiedClassPattern = qualifiedClassPattern;
this.extendsPattern = extendsPattern;
this.membersPattern = membersPattern;
@@ -130,6 +144,25 @@
public KeepMembersPattern getMembersPattern() {
return membersPattern;
}
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ KeepClassPattern that = (KeepClassPattern) obj;
+ return qualifiedClassPattern.equals(that.qualifiedClassPattern)
+ && extendsPattern.equals(that.extendsPattern)
+ && membersPattern.equals(that.membersPattern);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(qualifiedClassPattern, extendsPattern, membersPattern);
+ }
}
public abstract boolean isAny();
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMemberPattern.java b/src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMemberPattern.java
similarity index 100%
rename from src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMemberPattern.java
rename to src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMemberPattern.java
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMembersPattern.java b/src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMembersPattern.java
similarity index 82%
rename from src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMembersPattern.java
rename to src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMembersPattern.java
index 6d3f93e..f8b3515 100644
--- a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMembersPattern.java
+++ b/src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMembersPattern.java
@@ -3,9 +3,10 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.experimental.keepanno.ast;
-import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.experimental.keepanno.utils.Unimplemented;
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
import java.util.function.Consumer;
public abstract class KeepMembersPattern {
@@ -89,6 +90,16 @@
public void forEach(Consumer<KeepFieldPattern> onField, Consumer<KeepMethodPattern> onMethod) {
throw new Unimplemented("Should this include all and none?");
}
+
+ @Override
+ public boolean equals(Object obj) {
+ return this == obj;
+ }
+
+ @Override
+ public int hashCode() {
+ return System.identityHashCode(this);
+ }
}
private static class KeepMembersNonePattern extends KeepMembersPattern {
@@ -116,6 +127,16 @@
public void forEach(Consumer<KeepFieldPattern> onField, Consumer<KeepMethodPattern> onMethod) {
throw new Unimplemented("Should this include all and none?");
}
+
+ @Override
+ public boolean equals(Object obj) {
+ return this == obj;
+ }
+
+ @Override
+ public int hashCode() {
+ return System.identityHashCode(this);
+ }
}
private static class KeepMembersSomePattern extends KeepMembersPattern {
@@ -146,6 +167,23 @@
fields.forEach(onField);
methods.forEach(onMethod);
}
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ KeepMembersSomePattern that = (KeepMembersSomePattern) obj;
+ return methods.equals(that.methods) && fields.equals(that.fields);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(methods, fields);
+ }
}
private KeepMembersPattern() {}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodAccessPattern.java b/src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodAccessPattern.java
similarity index 100%
rename from src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodAccessPattern.java
rename to src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodAccessPattern.java
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodNamePattern.java b/src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodNamePattern.java
similarity index 100%
rename from src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodNamePattern.java
rename to src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodNamePattern.java
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodParametersPattern.java b/src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodParametersPattern.java
similarity index 100%
rename from src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodParametersPattern.java
rename to src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodParametersPattern.java
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodPattern.java b/src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodPattern.java
similarity index 100%
rename from src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodPattern.java
rename to src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodPattern.java
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodReturnTypePattern.java b/src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodReturnTypePattern.java
similarity index 100%
rename from src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodReturnTypePattern.java
rename to src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodReturnTypePattern.java
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepOptions.java b/src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepOptions.java
similarity index 74%
rename from src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepOptions.java
rename to src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepOptions.java
index 9fb521c..573a482 100644
--- a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepOptions.java
+++ b/src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepOptions.java
@@ -3,15 +3,19 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.experimental.keepanno.ast;
-import com.android.tools.r8.utils.SetUtils;
-import com.google.common.collect.ImmutableSet;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
+import java.util.HashSet;
+import java.util.Objects;
import java.util.Set;
public final class KeepOptions {
+ public boolean isKeepAll() {
+ return allowIfSet ? options.isEmpty() : options.size() == KeepOption.values().length;
+ }
+
public enum KeepOption {
SHRINKING,
OPTIMIZING,
@@ -44,7 +48,7 @@
public static class Builder {
public final boolean allowIfSet;
- public Set<KeepOption> options = SetUtils.newIdentityHashSet();
+ public Set<KeepOption> options = new HashSet<>();
private Builder(boolean allowIfSet) {
this.allowIfSet = allowIfSet;
@@ -77,7 +81,7 @@
}
throw new KeepEdgeException("Invalid keep options that allow everything.");
}
- return new KeepOptions(allowIfSet, ImmutableSet.copyOf(options));
+ return new KeepOptions(allowIfSet, Collections.unmodifiableSet(options));
}
}
@@ -94,4 +98,23 @@
public boolean isAllowed(KeepOption option) {
return options.contains(option) == allowIfSet;
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ // This does not actually capture equivalence. We should normalize the builder the 'allow'
+ // variant always.
+ KeepOptions that = (KeepOptions) o;
+ return allowIfSet == that.allowIfSet && options.equals(that.options);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(allowIfSet, options);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepPackagePattern.java b/src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepPackagePattern.java
similarity index 100%
rename from src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepPackagePattern.java
rename to src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepPackagePattern.java
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepPreconditions.java b/src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepPreconditions.java
similarity index 75%
rename from src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepPreconditions.java
rename to src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepPreconditions.java
index f3083df..2a2c3b2 100644
--- a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepPreconditions.java
+++ b/src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepPreconditions.java
@@ -59,6 +59,16 @@
public void forEach(Consumer<KeepCondition> fn) {
// Empty.
}
+
+ @Override
+ public boolean equals(Object obj) {
+ return this == obj;
+ }
+
+ @Override
+ public int hashCode() {
+ return System.identityHashCode(this);
+ }
}
private static class KeepPreconditionsSome extends KeepPreconditions {
@@ -66,6 +76,8 @@
private final List<KeepCondition> preconditions;
private KeepPreconditionsSome(List<KeepCondition> preconditions) {
+ assert preconditions != null;
+ assert !preconditions.isEmpty();
this.preconditions = preconditions;
}
@@ -78,5 +90,22 @@
public void forEach(Consumer<KeepCondition> fn) {
preconditions.forEach(fn);
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ KeepPreconditionsSome that = (KeepPreconditionsSome) o;
+ return preconditions.equals(that.preconditions);
+ }
+
+ @Override
+ public int hashCode() {
+ return preconditions.hashCode();
+ }
}
}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepQualifiedClassNamePattern.java b/src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepQualifiedClassNamePattern.java
similarity index 86%
rename from src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepQualifiedClassNamePattern.java
rename to src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepQualifiedClassNamePattern.java
index 9485580..8aae535 100644
--- a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepQualifiedClassNamePattern.java
+++ b/src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepQualifiedClassNamePattern.java
@@ -74,6 +74,21 @@
return packagePattern.isAny() && namePattern.isAny();
}
+ public boolean isExact() {
+ return packagePattern.isExact() && namePattern.isExact();
+ }
+
+ public String getExactDescriptor() {
+ if (!isExact()) {
+ throw new KeepEdgeException("Attempt to obtain exact qualified type for inexact pattern");
+ }
+ return 'L'
+ + packagePattern.asExact().getExactPackageAsString().replace('.', '/')
+ + (packagePattern.isTop() ? "" : "/")
+ + namePattern.asExact().getExactNameAsString()
+ + ';';
+ }
+
public KeepPackagePattern getPackagePattern() {
return packagePattern;
}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepTarget.java b/src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepTarget.java
similarity index 75%
rename from src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepTarget.java
rename to src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepTarget.java
index a63a34a..059668a 100644
--- a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepTarget.java
+++ b/src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepTarget.java
@@ -3,6 +3,8 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.experimental.keepanno.ast;
+import java.util.Objects;
+
public class KeepTarget {
public static KeepTarget any() {
@@ -38,6 +40,8 @@
private final KeepOptions options;
private KeepTarget(KeepItemPattern item, KeepOptions options) {
+ assert item != null;
+ assert options != null;
this.item = item;
this.options = options;
}
@@ -53,4 +57,21 @@
public KeepOptions getOptions() {
return options;
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ KeepTarget that = (KeepTarget) o;
+ return item.equals(that.item) && options.equals(that.options);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(item, options);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepTypePattern.java b/src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepTypePattern.java
similarity index 100%
rename from src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepTypePattern.java
rename to src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepTypePattern.java
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepUnqualfiedClassNamePattern.java b/src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepUnqualfiedClassNamePattern.java
similarity index 100%
rename from src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepUnqualfiedClassNamePattern.java
rename to src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepUnqualfiedClassNamePattern.java
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/keeprules/KeepRuleExtractor.java b/src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/keeprules/KeepRuleExtractor.java
similarity index 96%
rename from src/main/java/com/android/tools/r8/experimental/keepanno/keeprules/KeepRuleExtractor.java
rename to src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/keeprules/KeepRuleExtractor.java
index 6c24ec1..f5d09fe 100644
--- a/src/main/java/com/android/tools/r8/experimental/keepanno/keeprules/KeepRuleExtractor.java
+++ b/src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/keeprules/KeepRuleExtractor.java
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.experimental.keepanno.keeprules;
-import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.experimental.keepanno.ast.KeepConsequences;
import com.android.tools.r8.experimental.keepanno.ast.KeepEdge;
import com.android.tools.r8.experimental.keepanno.ast.KeepFieldPattern;
@@ -22,11 +21,11 @@
import com.android.tools.r8.experimental.keepanno.ast.KeepTarget;
import com.android.tools.r8.experimental.keepanno.ast.KeepTypePattern;
import com.android.tools.r8.experimental.keepanno.ast.KeepUnqualfiedClassNamePattern;
-import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.StringUtils.BraceType;
+import com.android.tools.r8.experimental.keepanno.utils.Unimplemented;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
+import java.util.stream.Collectors;
public class KeepRuleExtractor {
@@ -151,7 +150,12 @@
StringBuilder builder, KeepMethodParametersPattern parametersPattern) {
return parametersPattern.match(
() -> builder.append("(***)"),
- list -> StringUtils.append(builder, list, ", ", BraceType.PARENS));
+ list -> {
+ return builder
+ .append('{')
+ .append(list.stream().map(Object::toString).collect(Collectors.joining(", ")))
+ .append('}');
+ });
}
private static StringBuilder printMethodName(
diff --git a/src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/processor/KeepEdgeProcessor.java b/src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/processor/KeepEdgeProcessor.java
new file mode 100644
index 0000000..a195fd4
--- /dev/null
+++ b/src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/processor/KeepEdgeProcessor.java
@@ -0,0 +1,247 @@
+// 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.processor;
+
+import static org.objectweb.asm.Opcodes.ACC_FINAL;
+import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
+import static org.objectweb.asm.Opcodes.ACC_SUPER;
+
+import com.android.tools.r8.experimental.keepanno.annotations.KeepConstants;
+import com.android.tools.r8.experimental.keepanno.asm.KeepEdgeReader;
+import com.android.tools.r8.experimental.keepanno.asm.KeepEdgeWriter;
+import com.android.tools.r8.experimental.keepanno.ast.KeepConsequences;
+import com.android.tools.r8.experimental.keepanno.ast.KeepEdge;
+import com.android.tools.r8.experimental.keepanno.ast.KeepEdge.Builder;
+import com.android.tools.r8.experimental.keepanno.ast.KeepItemPattern;
+import com.android.tools.r8.experimental.keepanno.ast.KeepQualifiedClassNamePattern;
+import com.android.tools.r8.experimental.keepanno.ast.KeepTarget;
+import com.android.tools.r8.experimental.keepanno.utils.Unimplemented;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Consumer;
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.Filer;
+import javax.annotation.processing.RoundEnvironment;
+import javax.annotation.processing.SupportedAnnotationTypes;
+import javax.annotation.processing.SupportedSourceVersion;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.AnnotationValue;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.SimpleAnnotationValueVisitor7;
+import javax.lang.model.util.SimpleTypeVisitor7;
+import javax.tools.Diagnostic.Kind;
+import javax.tools.JavaFileObject;
+import org.objectweb.asm.ClassWriter;
+
+@SupportedAnnotationTypes("com.android.tools.r8.experimental.keepanno.annotations.*")
+@SupportedSourceVersion(SourceVersion.RELEASE_7)
+public class KeepEdgeProcessor extends AbstractProcessor {
+
+ public static String getClassTypeNameForSynthesizedEdges(String classTypeName) {
+ return classTypeName + "$$KeepEdges";
+ }
+
+ @Override
+ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+ for (Element rootElement : roundEnv.getRootElements()) {
+ TypeElement typeElement = findEnclosingTypeElement(rootElement);
+ KeepEdge edge = processKeepEdge(typeElement, roundEnv);
+ if (edge != null) {
+ String edgeTargetClass =
+ getClassTypeNameForSynthesizedEdges(typeElement.getQualifiedName().toString());
+ byte[] writtenEdge = writeEdge(edge, edgeTargetClass);
+ Filer filer = processingEnv.getFiler();
+ try {
+ JavaFileObject classFile = filer.createClassFile(edgeTargetClass);
+ classFile.openOutputStream().write(writtenEdge);
+ } catch (IOException e) {
+ error(e.getMessage());
+ }
+ }
+ }
+ return true;
+ }
+
+ private static TypeElement findEnclosingTypeElement(Element element) {
+ while (true) {
+ if (element == null || element instanceof TypeElement) {
+ return (TypeElement) element;
+ }
+ element = element.getEnclosingElement();
+ }
+ }
+
+ private static byte[] writeEdge(KeepEdge edge, String classTypeName) {
+ String classBinaryName = KeepConstants.getBinaryNameFromClassTypeName(classTypeName);
+ ClassWriter classWriter = new ClassWriter(0);
+ classWriter.visit(
+ KeepEdgeReader.ASM_VERSION,
+ ACC_PUBLIC | ACC_FINAL | ACC_SUPER,
+ classBinaryName,
+ null,
+ "java/lang/Object",
+ null);
+ classWriter.visitSource("SynthesizedKeepEdge", null);
+ KeepEdgeWriter.writeEdge(edge, classWriter);
+ classWriter.visitEnd();
+ return classWriter.toByteArray();
+ }
+
+ private KeepEdge processKeepEdge(TypeElement keepEdge, RoundEnvironment roundEnv) {
+ AnnotationMirror mirror = getAnnotationMirror(keepEdge, KeepConstants.Edge.CLASS);
+ if (mirror == null) {
+ return null;
+ }
+ Builder edgeBuilder = KeepEdge.builder();
+ processPreconditions(edgeBuilder, mirror);
+ processConsequences(edgeBuilder, mirror);
+ return edgeBuilder.build();
+ }
+
+ private void processPreconditions(Builder edgeBuilder, AnnotationMirror mirror) {
+ AnnotationValue preconditions = getAnnotationValue(mirror, "preconditions");
+ if (preconditions == null) {
+ return;
+ }
+ throw new Unimplemented();
+ }
+
+ private void processConsequences(Builder edgeBuilder, AnnotationMirror mirror) {
+ AnnotationValue consequences = getAnnotationValue(mirror, "consequences");
+ if (consequences == null) {
+ return;
+ }
+ KeepConsequences.Builder consequencesBuilder = KeepConsequences.builder();
+ AnnotationListValueVisitor v =
+ new AnnotationListValueVisitor(
+ value -> {
+ KeepTarget.Builder targetBuilder = KeepTarget.builder();
+ processTarget(targetBuilder, getAnnotationMirror(value, KeepConstants.Target.CLASS));
+ consequencesBuilder.addTarget(targetBuilder.build());
+ });
+ consequences.accept(v, null);
+ edgeBuilder.setConsequences(consequencesBuilder.build());
+ }
+
+ private void processTarget(KeepTarget.Builder builder, AnnotationMirror mirror) {
+ KeepItemPattern.Builder itemBuilder = KeepItemPattern.builder();
+ AnnotationValue classConstantValue = getAnnotationValue(mirror, "classConstant");
+ if (classConstantValue != null) {
+ AnnotationClassValueVisitor v = new AnnotationClassValueVisitor();
+ classConstantValue.accept(v, null);
+ itemBuilder.setClassPattern(KeepQualifiedClassNamePattern.exact(v.type.toString()));
+ }
+ builder.setItem(itemBuilder.build());
+ }
+
+ public static class AnnotationListValueVisitor
+ extends SimpleAnnotationValueVisitor7<AnnotationListValueVisitor, Object> {
+
+ private final Consumer<AnnotationValue> fn;
+
+ public AnnotationListValueVisitor(Consumer<AnnotationValue> fn) {
+ this.fn = fn;
+ }
+
+ @Override
+ protected AnnotationListValueVisitor defaultAction(Object o, Object o2) {
+ throw new IllegalStateException();
+ }
+
+ @Override
+ public AnnotationListValueVisitor visitArray(List<? extends AnnotationValue> vals, Object o) {
+ vals.forEach(fn::accept);
+ return this;
+ }
+ }
+
+ public static class AnnotationMirrorValueVisitor
+ extends SimpleAnnotationValueVisitor7<AnnotationMirrorValueVisitor, Object> {
+
+ private AnnotationMirror mirror = null;
+
+ @Override
+ protected AnnotationMirrorValueVisitor defaultAction(Object o, Object o2) {
+ throw new IllegalStateException();
+ }
+
+ @Override
+ public AnnotationMirrorValueVisitor visitAnnotation(AnnotationMirror a, Object o) {
+ mirror = a;
+ return this;
+ }
+ }
+
+ public static class AnnotationClassValueVisitor
+ extends SimpleAnnotationValueVisitor7<AnnotationClassValueVisitor, Object> {
+
+ private DeclaredType type = null;
+
+ @Override
+ protected AnnotationClassValueVisitor defaultAction(Object o, Object o2) {
+ throw new IllegalStateException();
+ }
+
+ @Override
+ public AnnotationClassValueVisitor visitType(TypeMirror t, Object o) {
+ ClassTypeVisitor classTypeVisitor = new ClassTypeVisitor();
+ t.accept(classTypeVisitor, null);
+ type = classTypeVisitor.t;
+ return this;
+ }
+ }
+
+ public static class ClassTypeVisitor extends SimpleTypeVisitor7<ClassTypeVisitor, Object> {
+
+ DeclaredType t = null;
+
+ @Override
+ protected ClassTypeVisitor defaultAction(TypeMirror e, Object o) {
+ throw new IllegalStateException();
+ }
+
+ @Override
+ public ClassTypeVisitor visitDeclared(DeclaredType t, Object o) {
+ this.t = t;
+ return this;
+ }
+ }
+
+ private void error(String message) {
+ processingEnv.getMessager().printMessage(Kind.ERROR, message);
+ }
+
+ private static AnnotationMirror getAnnotationMirror(AnnotationValue value, Class<?> annoClass) {
+ AnnotationMirrorValueVisitor v = new AnnotationMirrorValueVisitor();
+ value.accept(v, null);
+ return v.mirror;
+ }
+
+ private static AnnotationMirror getAnnotationMirror(TypeElement typeElement, Class<?> clazz) {
+ String clazzName = clazz.getName();
+ for (AnnotationMirror m : typeElement.getAnnotationMirrors()) {
+ if (m.getAnnotationType().toString().equals(clazzName)) {
+ return m;
+ }
+ }
+ return null;
+ }
+
+ private static AnnotationValue getAnnotationValue(AnnotationMirror annotationMirror, String key) {
+ for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry :
+ annotationMirror.getElementValues().entrySet()) {
+ if (entry.getKey().getSimpleName().toString().equals(key)) {
+ return entry.getValue();
+ }
+ }
+ return null;
+ }
+}
diff --git a/src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/utils/Unimplemented.java b/src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/utils/Unimplemented.java
new file mode 100644
index 0000000..0112a09
--- /dev/null
+++ b/src/keepanno/main/java/com/android/tools/r8/experimental/keepanno/utils/Unimplemented.java
@@ -0,0 +1,12 @@
+// 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.utils;
+
+public class Unimplemented extends RuntimeException {
+ public Unimplemented() {}
+
+ public Unimplemented(String msg) {
+ super(msg);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/ZipUtils.java b/src/main/java/com/android/tools/r8/utils/ZipUtils.java
index fc9157d..43b1b02 100644
--- a/src/main/java/com/android/tools/r8/utils/ZipUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ZipUtils.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.ResourceException;
import com.android.tools.r8.androidapi.AndroidApiDataAccess;
import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.references.ClassReference;
import com.google.common.io.ByteStreams;
import com.google.common.io.Closer;
import java.io.BufferedOutputStream;
@@ -304,6 +305,10 @@
return DescriptorUtils.getClassBinaryName(clazz) + CLASS_EXTENSION;
}
+ public static String zipEntryNameForClass(ClassReference clazz) {
+ return clazz.getBinaryName() + CLASS_EXTENSION;
+ }
+
public static long getOffsetOfResourceInZip(File file, String entry) throws IOException {
// Look into the jar file to see find the offset.
ZipFile zipFile = new ZipFile(file);
diff --git a/src/test/java/com/android/tools/r8/JavaCompilerTool.java b/src/test/java/com/android/tools/r8/JavaCompilerTool.java
index b961367..ba23dd5 100644
--- a/src/test/java/com/android/tools/r8/JavaCompilerTool.java
+++ b/src/test/java/com/android/tools/r8/JavaCompilerTool.java
@@ -102,10 +102,10 @@
}
public JavaCompilerTool addAnnotationProcessors(String... processors) {
- return addAnnotationProcessor(Arrays.asList(processors));
+ return addAnnotationProcessors(Arrays.asList(processors));
}
- public JavaCompilerTool addAnnotationProcessor(Collection<String> processors) {
+ public JavaCompilerTool addAnnotationProcessors(Collection<String> processors) {
annotationProcessors.addAll(processors);
return this;
}
@@ -118,6 +118,11 @@
return this;
}
+ public JavaCompilerTool addDebugAgent() {
+ return addOptions(
+ "-J-debug", "-J-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:5005");
+ }
+
private Path getOrCreateOutputPath() throws IOException {
return output != null ? output : state.getNewTempFolder().resolve("out.jar");
}
diff --git a/src/test/java/com/android/tools/r8/experimental/keepanno/asm/KeepEdgeAsmTest.java b/src/test/java/com/android/tools/r8/experimental/keepanno/asm/KeepEdgeAsmTest.java
new file mode 100644
index 0000000..832d7bc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/experimental/keepanno/asm/KeepEdgeAsmTest.java
@@ -0,0 +1,84 @@
+// 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.asm;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.experimental.keepanno.ast.KeepEdge;
+import com.android.tools.r8.experimental.keepanno.testsource.KeepClassAndDefaultConstructorSource;
+import com.android.tools.r8.experimental.keepanno.testsource.KeepSourceEdges;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.transformers.ClassTransformer;
+import java.util.Collections;
+import java.util.Set;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.AnnotationVisitor;
+
+@RunWith(Parameterized.class)
+public class KeepEdgeAsmTest extends TestBase {
+
+ private static final Class<?> SOURCE = KeepClassAndDefaultConstructorSource.class;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withNoneRuntime().build();
+ }
+
+ public KeepEdgeAsmTest(TestParameters parameters) {
+ parameters.assertNoneRuntime();
+ }
+
+ @Test
+ public void testAsmReader() throws Exception {
+ Set<KeepEdge> expectedEdges = KeepSourceEdges.getExpectedEdges(SOURCE);
+ ClassReference clazz = Reference.classFromClass(SOURCE);
+ // Original bytes of the test class.
+ byte[] original = ToolHelper.getClassAsBytes(SOURCE);
+ // Strip out all the annotations to ensure they are actually added again.
+ byte[] stripped =
+ transformer(SOURCE)
+ .addClassTransformer(
+ new ClassTransformer() {
+ @Override
+ public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
+ // Ignore all input annotations.
+ return null;
+ }
+ })
+ .transform();
+ // Manually add in the expected edges again.
+ byte[] readded =
+ transformer(stripped, clazz)
+ .addClassTransformer(
+ new ClassTransformer() {
+
+ @Override
+ public void visitEnd() {
+ for (KeepEdge edge : expectedEdges) {
+ KeepEdgeWriter.writeEdge(edge, super::visitAnnotation);
+ }
+ super.visitEnd();
+ }
+ })
+ .transform();
+
+ // Read the edges from each version.
+ Set<KeepEdge> originalEdges = KeepEdgeReader.readKeepEdges(original);
+ Set<KeepEdge> strippedEdges = KeepEdgeReader.readKeepEdges(stripped);
+ Set<KeepEdge> 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(expectedEdges, originalEdges);
+ assertEquals(expectedEdges, readdedEdges);
+ }
+}
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/KeepEdgeAstTest.java
similarity index 90%
rename from src/test/java/com/android/tools/r8/experimental/keepanno/ast/KeepEdgeApiTest.java
rename to src/test/java/com/android/tools/r8/experimental/keepanno/ast/KeepEdgeAstTest.java
index d69d055..34a1f32 100644
--- a/src/test/java/com/android/tools/r8/experimental/keepanno/ast/KeepEdgeApiTest.java
+++ b/src/test/java/com/android/tools/r8/experimental/keepanno/ast/KeepEdgeAstTest.java
@@ -16,7 +16,7 @@
import org.junit.runners.Parameterized;
@RunWith(Parameterized.class)
-public class KeepEdgeApiTest extends TestBase {
+public class KeepEdgeAstTest extends TestBase {
private static String CLASS = "com.example.Foo";
@@ -25,7 +25,7 @@
return getTestParameters().withNoneRuntime().build();
}
- public KeepEdgeApiTest(TestParameters parameters) {
+ public KeepEdgeAstTest(TestParameters parameters) {
parameters.assertNoneRuntime();
}
@@ -102,10 +102,7 @@
KeepEdge.builder()
.setPreconditions(
KeepPreconditions.builder()
- .addCondition(
- KeepCondition.builder()
- .setItem(classItem(CLASS))
- .build())
+ .addCondition(KeepCondition.builder().setItem(classItem(CLASS)).build())
.build())
.setConsequences(
KeepConsequences.builder()
@@ -127,10 +124,7 @@
KeepEdge.builder()
.setPreconditions(
KeepPreconditions.builder()
- .addCondition(
- KeepCondition.builder()
- .setItem(classItem(CLASS))
- .build())
+ .addCondition(KeepCondition.builder().setItem(classItem(CLASS)).build())
.build())
.setConsequences(KeepConsequences.builder().addTarget(target(classItem(CLASS))).build())
.build();
@@ -144,10 +138,7 @@
KeepEdge.builder()
.setPreconditions(
KeepPreconditions.builder()
- .addCondition(
- KeepCondition.builder()
- .setItem(classItem(CLASS))
- .build())
+ .addCondition(KeepCondition.builder().setItem(classItem(CLASS)).build())
.build())
.setConsequences(
KeepConsequences.builder()
diff --git a/src/test/java/com/android/tools/r8/experimental/keepanno/processor/KeepEdgeProcessorTest.java b/src/test/java/com/android/tools/r8/experimental/keepanno/processor/KeepEdgeProcessorTest.java
new file mode 100644
index 0000000..9ce665e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/experimental/keepanno/processor/KeepEdgeProcessorTest.java
@@ -0,0 +1,99 @@
+// 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.processor;
+
+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 static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.JavaCompilerTool;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.experimental.keepanno.annotations.KeepConstants.Edge;
+import com.android.tools.r8.experimental.keepanno.asm.KeepEdgeReader;
+import com.android.tools.r8.experimental.keepanno.ast.KeepEdge;
+import com.android.tools.r8.experimental.keepanno.testsource.KeepClassAndDefaultConstructorSource;
+import com.android.tools.r8.experimental.keepanno.testsource.KeepSourceEdges;
+import com.android.tools.r8.utils.ZipUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Collections;
+import java.util.Set;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class KeepEdgeProcessorTest extends TestBase {
+
+ private static final Class<?> SOURCE = KeepClassAndDefaultConstructorSource.class;
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDefaultCfRuntime().build();
+ }
+
+ public KeepEdgeProcessorTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testClassfile() throws Exception {
+ Path out =
+ JavaCompilerTool.create(parameters.getRuntime().asCf(), temp)
+ .addAnnotationProcessors(typeName(KeepEdgeProcessor.class))
+ .addClasspathFiles(Paths.get(ToolHelper.BUILD_DIR, "classes", "java", "main"))
+ .addClassNames(Collections.singletonList(typeName(SOURCE)))
+ .addClasspathFiles(Paths.get(ToolHelper.BUILD_DIR, "classes", "java", "test"))
+ .addClasspathFiles(ToolHelper.DEPS)
+ .compile();
+
+ CodeInspector inspector = new CodeInspector(out);
+ checkSynthesizedKeepEdgeClass(inspector, out);
+ // The source is added as a classpath name but not part of the compilation unit output.
+ assertThat(inspector.clazz(SOURCE), isAbsent());
+ }
+
+ @Test
+ public void testJavaSource() throws Exception {
+ Path out =
+ JavaCompilerTool.create(parameters.getRuntime().asCf(), temp)
+ .addSourceFiles(ToolHelper.getSourceFileForTestClass(SOURCE))
+ .addAnnotationProcessors(typeName(KeepEdgeProcessor.class))
+ .addClasspathFiles(Paths.get(ToolHelper.BUILD_DIR, "classes", "java", "main"))
+ .addClasspathFiles(ToolHelper.DEPS)
+ .compile();
+
+ testForJvm()
+ .addProgramFiles(out)
+ .run(parameters.getRuntime(), SOURCE)
+ .assertSuccessWithOutputLines("A is alive!")
+ .inspect(
+ inspector -> {
+ assertThat(inspector.clazz(SOURCE), isPresent());
+ checkSynthesizedKeepEdgeClass(inspector, out);
+ });
+ }
+
+ private void checkSynthesizedKeepEdgeClass(CodeInspector inspector, Path data)
+ throws IOException {
+ String synthesizedEdgesClassName =
+ KeepEdgeProcessor.getClassTypeNameForSynthesizedEdges(SOURCE.getTypeName());
+ ClassSubject synthesizedEdgesClass = inspector.clazz(synthesizedEdgesClassName);
+ assertThat(synthesizedEdgesClass, isPresent());
+ assertThat(synthesizedEdgesClass.annotation(Edge.CLASS.getTypeName()), isPresent());
+ String entry = ZipUtils.zipEntryNameForClass(synthesizedEdgesClass.getFinalReference());
+ byte[] bytes = ZipUtils.readSingleEntry(data, entry);
+ Set<KeepEdge> keepEdges = KeepEdgeReader.readKeepEdges(bytes);
+ assertEquals(KeepSourceEdges.getExpectedEdges(SOURCE), keepEdges);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/experimental/keepanno/testsource/KeepClassAndDefaultConstructorSource.java b/src/test/java/com/android/tools/r8/experimental/keepanno/testsource/KeepClassAndDefaultConstructorSource.java
new file mode 100644
index 0000000..0550498
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/experimental/keepanno/testsource/KeepClassAndDefaultConstructorSource.java
@@ -0,0 +1,36 @@
+// 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.testsource;
+
+import com.android.tools.r8.experimental.keepanno.annotations.KeepEdge;
+import com.android.tools.r8.experimental.keepanno.annotations.KeepTarget;
+
+@KeepEdge(
+ consequences = {
+ // Keep the class to allow lookup of it.
+ @KeepTarget(classConstant = KeepClassAndDefaultConstructorSource.class)
+ // TODO(b/248408342): Add default constructor target.
+ // // Keep the default constructor.
+ // @KeepTarget(
+ // classConstant = KeepClassAndDefaultConstructorSource.class,
+ // methodName = "<init>"
+ // )
+ })
+public class KeepClassAndDefaultConstructorSource {
+
+ public static class A {
+ public A() {
+ System.out.println("A is alive!");
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+ Class<?> aClass =
+ Class.forName(
+ KeepClassAndDefaultConstructorSource.class.getPackage().getName()
+ + (args.length > 0 ? ".." : ".")
+ + "KeepClassAndDefaultConstructorSource$A");
+ aClass.getDeclaredConstructor().newInstance();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/experimental/keepanno/testsource/KeepSourceEdges.java b/src/test/java/com/android/tools/r8/experimental/keepanno/testsource/KeepSourceEdges.java
new file mode 100644
index 0000000..6c4f8fa
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/experimental/keepanno/testsource/KeepSourceEdges.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.testsource;
+
+import com.android.tools.r8.experimental.keepanno.ast.KeepConsequences;
+import com.android.tools.r8.experimental.keepanno.ast.KeepEdge;
+import com.android.tools.r8.experimental.keepanno.ast.KeepItemPattern;
+import com.android.tools.r8.experimental.keepanno.ast.KeepQualifiedClassNamePattern;
+import com.android.tools.r8.experimental.keepanno.ast.KeepTarget;
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * Utility to get the AST edges for the various annotation test sources.
+ *
+ * <p>This makes it easier to share the test sources among tests (e.g., processor and asm tests).
+ */
+public class KeepSourceEdges {
+
+ public static Set<KeepEdge> getExpectedEdges(Class<?> clazz) {
+ if (clazz.equals(KeepClassAndDefaultConstructorSource.class)) {
+ return getKeepClassAndDefaultConstructorSourceEdges();
+ }
+ throw new RuntimeException();
+ }
+
+ public static Set<KeepEdge> getKeepClassAndDefaultConstructorSourceEdges() {
+ Class<?> clazz = KeepClassAndDefaultConstructorSource.class;
+ KeepQualifiedClassNamePattern name = KeepQualifiedClassNamePattern.exact(clazz.getTypeName());
+ KeepItemPattern item = KeepItemPattern.builder().setClassPattern(name).build();
+ KeepTarget target = KeepTarget.builder().setItem(item).build();
+ KeepConsequences consequences = KeepConsequences.builder().addTarget(target).build();
+ KeepEdge edge = KeepEdge.builder().setConsequences(consequences).build();
+ return Collections.singleton(edge);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
index 63efc58..b0d3664 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -66,7 +66,7 @@
List<MethodTransformer> methodTransformers,
int flags) {
ClassReader reader = new ClassReader(bytes);
- ClassWriter writer = new ClassWriter(reader, flags);
+ ClassWriter writer = new ClassWriter(flags);
ClassVisitor subvisitor = new InnerMostClassTransformer(writer, methodTransformers);
for (int i = classTransformers.size() - 1; i >= 0; i--) {
classTransformers.get(i).setSubVisitor(subvisitor);