Merge commit '50f70c15b80b489373b7d6b9134062b546eb0b30' into dev-release
diff --git a/build.gradle b/build.gradle index eca19a7..f775822 100644 --- a/build.gradle +++ b/build.gradle
@@ -198,6 +198,11 @@ } output.resourcesDir = 'build/classes/kotlinR8TestResources' } + keepanno { + java { + srcDirs = ['src/keepanno/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/java/com/android/tools/r8/keepanno/annotations/KeepCondition.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepCondition.java new file mode 100644 index 0000000..9d0265e --- /dev/null +++ b/src/keepanno/java/com/android/tools/r8/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.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/java/com/android/tools/r8/keepanno/annotations/KeepConstants.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepConstants.java new file mode 100644 index 0000000..24e49d1 --- /dev/null +++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepConstants.java
@@ -0,0 +1,35 @@ +// 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.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"; + public static final String methodName = "methodName"; + } +}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepEdge.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepEdge.java new file mode 100644 index 0000000..19b1a4a --- /dev/null +++ b/src/keepanno/java/com/android/tools/r8/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.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/java/com/android/tools/r8/keepanno/annotations/KeepTarget.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepTarget.java new file mode 100644 index 0000000..e20f288 --- /dev/null +++ b/src/keepanno/java/com/android/tools/r8/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.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/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java new file mode 100644 index 0000000..3ba3b8b --- /dev/null +++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java
@@ -0,0 +1,181 @@ +// 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.keepanno.asm; + +import com.android.tools.r8.keepanno.annotations.KeepConstants.Edge; +import com.android.tools.r8.keepanno.annotations.KeepConstants.Target; +import com.android.tools.r8.keepanno.ast.KeepConsequences; +import com.android.tools.r8.keepanno.ast.KeepEdge; +import com.android.tools.r8.keepanno.ast.KeepEdgeException; +import com.android.tools.r8.keepanno.ast.KeepItemPattern; +import com.android.tools.r8.keepanno.ast.KeepItemPattern.Builder; +import com.android.tools.r8.keepanno.ast.KeepMembersPattern; +import com.android.tools.r8.keepanno.ast.KeepMethodNamePattern; +import com.android.tools.r8.keepanno.ast.KeepMethodPattern; +import com.android.tools.r8.keepanno.ast.KeepPreconditions; +import com.android.tools.r8.keepanno.ast.KeepQualifiedClassNamePattern; +import com.android.tools.r8.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 KeepQualifiedClassNamePattern classNamePattern = null; + private KeepMethodNamePattern methodName = 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) { + classNamePattern = KeepQualifiedClassNamePattern.exact(((Type) value).getClassName()); + return; + } + if (name.equals(Target.methodName) && value instanceof String) { + methodName = KeepMethodNamePattern.exact((String) value); + return; + } + super.visit(name, value); + } + + @Override + public void visitEnd() { + Builder itemBuilder = KeepItemPattern.builder(); + if (classNamePattern != null) { + itemBuilder.setClassPattern(classNamePattern); + } + if (methodName != null) { + itemBuilder.setMembersPattern( + KeepMembersPattern.builder() + .addMethodPattern(KeepMethodPattern.builder().setNamePattern(methodName).build()) + .build()); + } + KeepTarget target = KeepTarget.builder().setItem(itemBuilder.build()).build(); + parent.accept(target); + } + } +}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeWriter.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeWriter.java new file mode 100644 index 0000000..1641d8d --- /dev/null +++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeWriter.java
@@ -0,0 +1,112 @@ +// 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.keepanno.asm; + +import com.android.tools.r8.keepanno.annotations.KeepConstants; +import com.android.tools.r8.keepanno.annotations.KeepConstants.Edge; +import com.android.tools.r8.keepanno.annotations.KeepConstants.Target; +import com.android.tools.r8.keepanno.ast.KeepConsequences; +import com.android.tools.r8.keepanno.ast.KeepEdge; +import com.android.tools.r8.keepanno.ast.KeepMethodNamePattern; +import com.android.tools.r8.keepanno.ast.KeepPreconditions; +import com.android.tools.r8.keepanno.ast.KeepQualifiedClassNamePattern; +import com.android.tools.r8.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(); + } + if (!clazz.getExtendsPattern().isAny()) { + throw new Unimplemented(); + } + if (clazz.getMembersPattern().isNone()) { + // Default is "no methods". + } else if (clazz.getMembersPattern().isAll()) { + throw new Unimplemented(); + } else { + clazz + .getMembersPattern() + .forEach( + field -> { + throw new Unimplemented(); + }, + method -> { + KeepMethodNamePattern methodNamePattern = method.getNamePattern(); + methodNamePattern.match( + () -> { + throw new Unimplemented(); + }, + exactMethodName -> { + targetVisitor.visit(Target.methodName, exactMethodName); + return null; + }); + if (!method.getAccessPattern().isAny()) { + throw new Unimplemented(); + } + if (!method.getReturnTypePattern().isAny()) { + throw new Unimplemented(); + } + if (!method.getParametersPattern().isAny()) { + 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/java/com/android/tools/r8/keepanno/ast/KeepCondition.java similarity index 95% rename from src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepCondition.java rename to src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepCondition.java index acd327d..43b4176 100644 --- a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepCondition.java +++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepCondition.java
@@ -1,7 +1,7 @@ // 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; +package com.android.tools.r8.keepanno.ast; /** * A keep condition is the content of an item in the set of preconditions. @@ -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/java/com/android/tools/r8/keepanno/ast/KeepConsequences.java similarity index 64% rename from src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepConsequences.java rename to src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepConsequences.java index 6a227fd..1322746 100644 --- a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepConsequences.java +++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepConsequences.java
@@ -1,11 +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.ast; +package com.android.tools.r8.keepanno.ast; import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; +import java.util.stream.Collectors; /** * Set of consequences of a keep edge. @@ -29,6 +30,9 @@ } public KeepConsequences build() { + if (targets.isEmpty()) { + throw new KeepEdgeException("Invalid empty consequent set"); + } return new KeepConsequences(targets); } } @@ -40,6 +44,8 @@ private final List<KeepTarget> targets; private KeepConsequences(List<KeepTarget> targets) { + assert targets != null; + assert !targets.isEmpty(); this.targets = targets; } @@ -50,4 +56,26 @@ 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(); + } + + @Override + public String toString() { + return targets.stream().map(Object::toString).collect(Collectors.joining(", ")); + } }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdge.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdge.java new file mode 100644 index 0000000..4da44aa --- /dev/null +++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdge.java
@@ -0,0 +1,125 @@ +// 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.keepanno.ast; + +import java.util.Objects; + +/** + * 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. + * + * <p>Below is a BNF of the keep edge AST for reference. The non-terminals are written in ALL_CAPS, + * possibly-empty repeatable subexpressions denoted with SUB* and non-empty with SUB+ + * + * <p>In the Java AST, the non-terminals are prefixed with 'Keep' and in CamelCase. + * + * <p>TODO(b/248408342): Update the BNF and AST to be complete. + * + * <pre> + * EDGE ::= PRECONDITIONS -> CONSEQUENCES + * + * PRECONDITIONS ::= always | CONDITION+ + * CONDITION ::= ITEM_PATTERN + * + * CONSEQUENCES ::= TARGET+ + * TARGET ::= any | OPTIONS ITEM_PATTERN + * OPTIONS ::= keep-all | OPTION+ + * OPTION ::= shrinking | optimizing | obfuscating | access-modifying + * + * ITEM_PATTERN ::= any | CLASS_PATTERN + * CLASS_PATTERN ::= QUALIFIED_CLASS_NAME_PATTERN extends EXTENDS_PATTERN { MEMBERS_PATTERN } + * + * TYPE_PATTERN ::= any + * PACKAGE_PATTERN ::= any | exact package-name + * QUALIFIED_CLASS_NAME_PATTERN ::= any | PACKAGE_PATTERN | UNQUALIFIED_CLASS_NAME_PATTERN + * UNQUALIFIED_CLASS_NAME_PATTERN ::= any | exact simple-class-name + * EXTENDS_PATTERN ::= any | QUALIFIED_CLASS_NAME_PATTERN + * + * MEMBERS_PATTERN ::= none | all | METHOD_PATTERN* + * + * METHOD_PATTERN + * ::= METHOD_ACCESS_PATTERN + * METHOD_RETURN_TYPE_PATTERN + * METHOD_NAME_PATTERN + * METHOD_PARAMETERS_PATTERN + * + * METHOD_ACCESS_PATTERN ::= any + * METHOD_NAME_PATTERN ::= any | exact method-name + * METHOD_RETURN_TYPE_PATTERN ::= void | TYPE_PATTERN + * METHOD_PARAMETERS_PATTERN ::= any | none | TYPE_PATTERN+ + * </pre> + */ +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 preconditions; + private final KeepConsequences consequences; + + private KeepEdge(KeepPreconditions preconditions, KeepConsequences consequences) { + assert preconditions != null; + assert consequences != null; + this.preconditions = preconditions; + this.consequences = consequences; + } + + public KeepPreconditions getPreconditions() { + return preconditions; + } + + 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); + } + + @Override + public String toString() { + return "KeepEdge{" + "preconditions=" + preconditions + ", consequences=" + consequences + '}'; + } +}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepEdgeException.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdgeException.java similarity index 85% rename from src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepEdgeException.java rename to src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdgeException.java index 3345c8f..5abe947 100644 --- a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepEdgeException.java +++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdgeException.java
@@ -1,7 +1,7 @@ // 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; +package com.android.tools.r8.keepanno.ast; public class KeepEdgeException extends RuntimeException {
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepExtendsPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepExtendsPattern.java similarity index 67% rename from src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepExtendsPattern.java rename to src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepExtendsPattern.java index 17e543f..b6b0fd7 100644 --- a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepExtendsPattern.java +++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepExtendsPattern.java
@@ -1,7 +1,7 @@ // 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; +package com.android.tools.r8.keepanno.ast; /** Pattern for matching the "extends" or "implements" clause of a class. */ public abstract class KeepExtendsPattern { @@ -42,6 +42,21 @@ public boolean isAny() { return true; } + + @Override + public boolean equals(Object obj) { + return this == obj; + } + + @Override + public int hashCode() { + return System.identityHashCode(this); + } + + @Override + public String toString() { + return "*"; + } } private static class KeepExtendsClassPattern extends KeepExtendsPattern { @@ -49,6 +64,7 @@ private final KeepQualifiedClassNamePattern pattern; public KeepExtendsClassPattern(KeepQualifiedClassNamePattern pattern) { + assert pattern != null; this.pattern = pattern; } @@ -56,6 +72,28 @@ 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(); + } + + @Override + public String toString() { + return pattern.toString(); + } } public static Builder builder() {
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepFieldPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepFieldPattern.java similarity index 86% rename from src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepFieldPattern.java rename to src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepFieldPattern.java index 04e2697..8de46e1 100644 --- a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepFieldPattern.java +++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepFieldPattern.java
@@ -1,7 +1,7 @@ // 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; +package com.android.tools.r8.keepanno.ast; public class KeepFieldPattern extends KeepMemberPattern {
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepItemPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepItemPattern.java similarity index 75% rename from src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepItemPattern.java rename to src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepItemPattern.java index 3c6f947..e5b0987 100644 --- a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepItemPattern.java +++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepItemPattern.java
@@ -1,8 +1,9 @@ // 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; +package com.android.tools.r8.keepanno.ast; +import java.util.Objects; import java.util.function.Function; import java.util.function.Supplier; @@ -87,6 +88,21 @@ 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); + } + + @Override + public String toString() { + return "*"; + } } public static class KeepClassPattern extends KeepItemPattern { @@ -100,6 +116,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 +149,37 @@ 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); + } + + @Override + public String toString() { + return "KeepClassPattern{" + + "qualifiedClassPattern=" + + qualifiedClassPattern + + ", extendsPattern=" + + extendsPattern + + ", membersPattern=" + + membersPattern + + '}'; + } } public abstract boolean isAny();
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMemberPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMemberPattern.java similarity index 93% rename from src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMemberPattern.java rename to src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMemberPattern.java index 73bae7e..bd716e9 100644 --- a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMemberPattern.java +++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMemberPattern.java
@@ -1,7 +1,7 @@ // 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; +package com.android.tools.r8.keepanno.ast; public abstract class KeepMemberPattern {
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMembersPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMembersPattern.java similarity index 73% rename from src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMembersPattern.java rename to src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMembersPattern.java index 6d3f93e..be5af32 100644 --- a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMembersPattern.java +++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMembersPattern.java
@@ -1,12 +1,14 @@ // 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; +package com.android.tools.r8.keepanno.ast; -import com.android.tools.r8.errors.Unimplemented; +import com.android.tools.r8.keepanno.utils.Unimplemented; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.function.Consumer; +import java.util.stream.Collectors; public abstract class KeepMembersPattern { @@ -89,6 +91,21 @@ 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); + } + + @Override + public String toString() { + return "*"; + } } private static class KeepMembersNonePattern extends KeepMembersPattern { @@ -116,6 +133,21 @@ 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); + } + + @Override + public String toString() { + return "<none>"; + } } private static class KeepMembersSomePattern extends KeepMembersPattern { @@ -146,6 +178,33 @@ 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); + } + + @Override + public String toString() { + return "KeepMembersSomePattern{" + + "methods={" + + methods.stream().map(Object::toString).collect(Collectors.joining(", ")) + + "}, fields={" + + fields.stream().map(Object::toString).collect(Collectors.joining(", ")) + + "}}"; + } } private KeepMembersPattern() {}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodAccessPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodAccessPattern.java new file mode 100644 index 0000000..fd0fd3e --- /dev/null +++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodAccessPattern.java
@@ -0,0 +1,46 @@ +// 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.keepanno.ast; + +// TODO: finish this. +public abstract class KeepMethodAccessPattern { + + public static KeepMethodAccessPattern any() { + return Any.getInstance(); + } + + public abstract boolean isAny(); + + private static class Any extends KeepMethodAccessPattern { + + private static Any INSTANCE = null; + + private static Any getInstance() { + if (INSTANCE == null) { + INSTANCE = new Any(); + } + return INSTANCE; + } + + @Override + public boolean isAny() { + return true; + } + + @Override + public boolean equals(Object obj) { + return this == obj; + } + + @Override + public int hashCode() { + return System.identityHashCode(this); + } + + @Override + public String toString() { + return "*"; + } + } +}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodNamePattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodNamePattern.java similarity index 68% rename from src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodNamePattern.java rename to src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodNamePattern.java index ada6701..6dc373d 100644 --- a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodNamePattern.java +++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodNamePattern.java
@@ -1,7 +1,7 @@ // 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; +package com.android.tools.r8.keepanno.ast; import java.util.function.Function; import java.util.function.Supplier; @@ -47,12 +47,28 @@ public <T> T match(Supplier<T> onAny, Function<String, T> onExact) { return onAny.get(); } + + @Override + public boolean equals(Object obj) { + return this == obj; + } + + @Override + public int hashCode() { + return System.identityHashCode(this); + } + + @Override + public String toString() { + return "*"; + } } private static class KeepMethodNameExactPattern extends KeepMethodNamePattern { private final String name; public KeepMethodNameExactPattern(String name) { + assert name != null; this.name = name; } @@ -60,5 +76,27 @@ public <T> T match(Supplier<T> onAny, Function<String, T> onExact) { return onExact.apply(name); } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + KeepMethodNameExactPattern that = (KeepMethodNameExactPattern) o; + return name.equals(that.name); + } + + @Override + public int hashCode() { + return name.hashCode(); + } + + @Override + public String toString() { + return name; + } } }
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodParametersPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodParametersPattern.java similarity index 70% rename from src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodParametersPattern.java rename to src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodParametersPattern.java index 4f42202..83b4606 100644 --- a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodParametersPattern.java +++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodParametersPattern.java
@@ -1,7 +1,7 @@ // 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; +package com.android.tools.r8.keepanno.ast; import java.util.Collections; import java.util.List; @@ -22,6 +22,10 @@ public abstract <T> T match(Supplier<T> onAny, Function<List<KeepTypePattern>, T> onList); + public boolean isAny() { + return match(() -> true, params -> false); + } + private static class None extends KeepMethodParametersPattern { private static None INSTANCE = null; @@ -36,6 +40,21 @@ public <T> T match(Supplier<T> onAny, Function<List<KeepTypePattern>, T> onList) { return onList.apply(Collections.emptyList()); } + + @Override + public boolean equals(Object obj) { + return this == obj; + } + + @Override + public int hashCode() { + return System.identityHashCode(this); + } + + @Override + public String toString() { + return "()"; + } } private static class Any extends KeepMethodParametersPattern { @@ -52,5 +71,20 @@ public <T> T match(Supplier<T> onAny, Function<List<KeepTypePattern>, T> onList) { return onAny.get(); } + + @Override + public boolean equals(Object obj) { + return this == obj; + } + + @Override + public int hashCode() { + return System.identityHashCode(this); + } + + @Override + public String toString() { + return "(...)"; + } } }
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodPattern.java similarity index 71% rename from src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodPattern.java rename to src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodPattern.java index 5db7d26..b3ec418 100644 --- a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodPattern.java +++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodPattern.java
@@ -1,7 +1,9 @@ // 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; +package com.android.tools.r8.keepanno.ast; + +import java.util.Objects; public final class KeepMethodPattern extends KeepMemberPattern { @@ -62,6 +64,10 @@ KeepMethodNamePattern namePattern, KeepMethodReturnTypePattern returnTypePattern, KeepMethodParametersPattern parametersPattern) { + assert accessPattern != null; + assert namePattern != null; + assert returnTypePattern != null; + assert parametersPattern != null; this.accessPattern = accessPattern; this.namePattern = namePattern; this.returnTypePattern = returnTypePattern; @@ -87,4 +93,38 @@ public KeepMethodParametersPattern getParametersPattern() { return parametersPattern; } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + KeepMethodPattern that = (KeepMethodPattern) o; + return accessPattern.equals(that.accessPattern) + && namePattern.equals(that.namePattern) + && returnTypePattern.equals(that.returnTypePattern) + && parametersPattern.equals(that.parametersPattern); + } + + @Override + public int hashCode() { + return Objects.hash(accessPattern, namePattern, returnTypePattern, parametersPattern); + } + + @Override + public String toString() { + return "KeepMethodPattern{" + + "access=" + + accessPattern + + ", name=" + + namePattern + + ", returnType=" + + returnTypePattern + + ", parameters=" + + parametersPattern + + '}'; + } }
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodReturnTypePattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodReturnTypePattern.java similarity index 63% rename from src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodReturnTypePattern.java rename to src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodReturnTypePattern.java index 4e7b5b9..f711bc0 100644 --- a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodReturnTypePattern.java +++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodReturnTypePattern.java
@@ -1,7 +1,7 @@ // 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; +package com.android.tools.r8.keepanno.ast; import java.util.function.Function; import java.util.function.Supplier; @@ -23,6 +23,10 @@ public abstract <T> T match(Supplier<T> onVoid, Function<KeepTypePattern, T> onType); + public boolean isAny() { + return match(() -> false, KeepTypePattern::isAny); + } + private static class VoidType extends KeepMethodReturnTypePattern { private static VoidType INSTANCE = null; @@ -37,6 +41,21 @@ public <T> T match(Supplier<T> onVoid, Function<KeepTypePattern, T> onType) { return onVoid.get(); } + + @Override + public boolean equals(Object obj) { + return this == obj; + } + + @Override + public int hashCode() { + return System.identityHashCode(this); + } + + @Override + public String toString() { + return "void"; + } } private static class SomeType extends KeepMethodReturnTypePattern { @@ -44,6 +63,7 @@ private final KeepTypePattern typePattern; private SomeType(KeepTypePattern typePattern) { + assert typePattern != null; this.typePattern = typePattern; } @@ -51,5 +71,27 @@ public <T> T match(Supplier<T> onVoid, Function<KeepTypePattern, T> onType) { return onType.apply(typePattern); } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + SomeType someType = (SomeType) o; + return typePattern.equals(someType.typePattern); + } + + @Override + public int hashCode() { + return typePattern.hashCode(); + } + + @Override + public String toString() { + return typePattern.toString(); + } } }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepOptions.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepOptions.java new file mode 100644 index 0000000..21b7487 --- /dev/null +++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepOptions.java
@@ -0,0 +1,135 @@ +// 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.keepanno.ast; + +import com.google.common.collect.ImmutableSet; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +public final class KeepOptions { + + public boolean isKeepAll() { + return allowedOptions.isEmpty(); + } + + public enum KeepOption { + SHRINKING, + OPTIMIZING, + OBFUSCATING, + ACCESS_MODIFYING, + } + + public static KeepOptions keepAll() { + if (ALLOW_NONE_INSTANCE == null) { + ALLOW_NONE_INSTANCE = new KeepOptions(ImmutableSet.of()); + } + return ALLOW_NONE_INSTANCE; + } + + public static Builder allowBuilder() { + return new Builder(true); + } + + public static Builder disallowBuilder() { + return new Builder(false); + } + + public static KeepOptions allow(KeepOption... options) { + return allowBuilder().addAll(options).build(); + } + + public static KeepOptions disallow(KeepOption... options) { + return disallowBuilder().addAll(options).build(); + } + + public static class Builder { + public final boolean allowIfSet; + public Set<KeepOption> options = new HashSet<>(); + + private Builder(boolean allowIfSet) { + this.allowIfSet = allowIfSet; + } + + public Builder add(KeepOption option) { + options.add(option); + return this; + } + + public Builder addAll(KeepOption... options) { + return addAll(Arrays.asList(options)); + } + + public Builder addAll(Collection<KeepOption> options) { + this.options.addAll(options); + return this; + } + + public KeepOptions build() { + // Fast path check for the two variants of "keep all". + if (options.isEmpty()) { + if (allowIfSet) { + return keepAll(); + } + throw new KeepEdgeException("Invalid keep options that disallow nothing."); + } + if (options.size() == KeepOption.values().length) { + if (!allowIfSet) { + return keepAll(); + } + throw new KeepEdgeException("Invalid keep options that allow everything."); + } + // The normalized options is the "allow variant", if not of that form invert it on build. + if (allowIfSet) { + return new KeepOptions(ImmutableSet.copyOf(options)); + } + ImmutableSet.Builder<KeepOption> invertedOptions = ImmutableSet.builder(); + for (KeepOption option : KeepOption.values()) { + if (!options.contains(option)) { + invertedOptions.add(option); + } + } + return new KeepOptions(invertedOptions.build()); + } + } + + private static KeepOptions ALLOW_NONE_INSTANCE = null; + + private final ImmutableSet<KeepOption> allowedOptions; + + private KeepOptions(ImmutableSet<KeepOption> options) { + this.allowedOptions = options; + } + + public boolean isAllowed(KeepOption option) { + return allowedOptions.contains(option); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + KeepOptions that = (KeepOptions) o; + return allowedOptions.equals(that.allowedOptions); + } + + @Override + public int hashCode() { + return allowedOptions.hashCode(); + } + + @Override + public String toString() { + return "KeepOptions{" + + allowedOptions.stream().map(Objects::toString).collect(Collectors.joining(", ")) + + '}'; + } +}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepPackagePattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepPackagePattern.java similarity index 93% rename from src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepPackagePattern.java rename to src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepPackagePattern.java index 10db385..7d23490 100644 --- a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepPackagePattern.java +++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepPackagePattern.java
@@ -1,7 +1,7 @@ // 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; +package com.android.tools.r8.keepanno.ast; public abstract class KeepPackagePattern { @@ -88,6 +88,11 @@ public int hashCode() { return System.identityHashCode(this); } + + @Override + public String toString() { + return "*"; + } } private static final class KeepPackageTopPattern extends KeepPackageExactPattern { @@ -114,6 +119,11 @@ public boolean isTop() { return true; } + + @Override + public String toString() { + return ""; + } } public static class KeepPackageExactPattern extends KeepPackagePattern { @@ -166,6 +176,11 @@ public int hashCode() { return fullPackage.hashCode(); } + + @Override + public String toString() { + return fullPackage; + } } public abstract boolean isAny();
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepPreconditions.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepPreconditions.java similarity index 69% rename from src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepPreconditions.java rename to src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepPreconditions.java index f3083df..f672013 100644 --- a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepPreconditions.java +++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepPreconditions.java
@@ -1,7 +1,7 @@ // 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; +package com.android.tools.r8.keepanno.ast; import java.util.ArrayList; import java.util.List; @@ -59,6 +59,21 @@ public void forEach(Consumer<KeepCondition> fn) { // Empty. } + + @Override + public boolean equals(Object obj) { + return this == obj; + } + + @Override + public int hashCode() { + return System.identityHashCode(this); + } + + @Override + public String toString() { + return "true"; + } } private static class KeepPreconditionsSome extends KeepPreconditions { @@ -66,6 +81,8 @@ private final List<KeepCondition> preconditions; private KeepPreconditionsSome(List<KeepCondition> preconditions) { + assert preconditions != null; + assert !preconditions.isEmpty(); this.preconditions = preconditions; } @@ -78,5 +95,27 @@ 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(); + } + + @Override + public String toString() { + return preconditions.toString(); + } } }
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepQualifiedClassNamePattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepQualifiedClassNamePattern.java similarity index 82% rename from src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepQualifiedClassNamePattern.java rename to src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepQualifiedClassNamePattern.java index 9485580..97d01b9 100644 --- a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepQualifiedClassNamePattern.java +++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepQualifiedClassNamePattern.java
@@ -1,7 +1,7 @@ // 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; +package com.android.tools.r8.keepanno.ast; import java.util.Objects; @@ -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; } @@ -98,4 +113,9 @@ public int hashCode() { return Objects.hash(packagePattern.hashCode(), namePattern.hashCode()); } + + @Override + public String toString() { + return packagePattern + (packagePattern.isTop() ? "" : ".") + namePattern; + } }
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepTarget.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepTarget.java similarity index 67% rename from src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepTarget.java rename to src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepTarget.java index a63a34a..111022b 100644 --- a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepTarget.java +++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepTarget.java
@@ -1,7 +1,9 @@ // 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; +package com.android.tools.r8.keepanno.ast; + +import java.util.Objects; public class KeepTarget { @@ -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,26 @@ 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); + } + + @Override + public String toString() { + return "KeepTarget{" + "item=" + item + ", options=" + options + '}'; + } }
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepTypePattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepTypePattern.java similarity index 69% rename from src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepTypePattern.java rename to src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepTypePattern.java index eff9e9c..a790094 100644 --- a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepTypePattern.java +++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepTypePattern.java
@@ -1,7 +1,7 @@ // 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; +package com.android.tools.r8.keepanno.ast; public abstract class KeepTypePattern { @@ -23,6 +23,21 @@ public boolean isAny() { return true; } + + @Override + public boolean equals(Object obj) { + return this == obj; + } + + @Override + public int hashCode() { + return System.identityHashCode(this); + } + + @Override + public String toString() { + return "*"; + } } public abstract boolean isAny();
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepUnqualfiedClassNamePattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepUnqualfiedClassNamePattern.java similarity index 93% rename from src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepUnqualfiedClassNamePattern.java rename to src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepUnqualfiedClassNamePattern.java index ab38ed2..c9ac380 100644 --- a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepUnqualfiedClassNamePattern.java +++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepUnqualfiedClassNamePattern.java
@@ -1,7 +1,7 @@ // 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; +package com.android.tools.r8.keepanno.ast; public abstract class KeepUnqualfiedClassNamePattern { @@ -71,6 +71,11 @@ public int hashCode() { return System.identityHashCode(this); } + + @Override + public String toString() { + return "*"; + } } public static class KeepClassNameExactPattern extends KeepUnqualfiedClassNamePattern { @@ -117,6 +122,11 @@ public int hashCode() { return className.hashCode(); } + + @Override + public String toString() { + return className; + } } public abstract boolean isAny();
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/keeprules/KeepRuleExtractor.java b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepRuleExtractor.java similarity index 83% rename from src/main/java/com/android/tools/r8/experimental/keepanno/keeprules/KeepRuleExtractor.java rename to src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepRuleExtractor.java index 6c24ec1..d9b75ae 100644 --- a/src/main/java/com/android/tools/r8/experimental/keepanno/keeprules/KeepRuleExtractor.java +++ b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepRuleExtractor.java
@@ -1,32 +1,31 @@ // 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.keeprules; +package com.android.tools.r8.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; -import com.android.tools.r8.experimental.keepanno.ast.KeepItemPattern.KeepClassPattern; -import com.android.tools.r8.experimental.keepanno.ast.KeepMembersPattern; -import com.android.tools.r8.experimental.keepanno.ast.KeepMethodAccessPattern; -import com.android.tools.r8.experimental.keepanno.ast.KeepMethodNamePattern; -import com.android.tools.r8.experimental.keepanno.ast.KeepMethodParametersPattern; -import com.android.tools.r8.experimental.keepanno.ast.KeepMethodPattern; -import com.android.tools.r8.experimental.keepanno.ast.KeepMethodReturnTypePattern; -import com.android.tools.r8.experimental.keepanno.ast.KeepOptions; -import com.android.tools.r8.experimental.keepanno.ast.KeepOptions.KeepOption; -import com.android.tools.r8.experimental.keepanno.ast.KeepPackagePattern; -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 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.keepanno.ast.KeepConsequences; +import com.android.tools.r8.keepanno.ast.KeepEdge; +import com.android.tools.r8.keepanno.ast.KeepFieldPattern; +import com.android.tools.r8.keepanno.ast.KeepItemPattern.KeepClassPattern; +import com.android.tools.r8.keepanno.ast.KeepMembersPattern; +import com.android.tools.r8.keepanno.ast.KeepMethodAccessPattern; +import com.android.tools.r8.keepanno.ast.KeepMethodNamePattern; +import com.android.tools.r8.keepanno.ast.KeepMethodParametersPattern; +import com.android.tools.r8.keepanno.ast.KeepMethodPattern; +import com.android.tools.r8.keepanno.ast.KeepMethodReturnTypePattern; +import com.android.tools.r8.keepanno.ast.KeepOptions; +import com.android.tools.r8.keepanno.ast.KeepOptions.KeepOption; +import com.android.tools.r8.keepanno.ast.KeepPackagePattern; +import com.android.tools.r8.keepanno.ast.KeepPreconditions; +import com.android.tools.r8.keepanno.ast.KeepQualifiedClassNamePattern; +import com.android.tools.r8.keepanno.ast.KeepTarget; +import com.android.tools.r8.keepanno.ast.KeepTypePattern; +import com.android.tools.r8.keepanno.ast.KeepUnqualfiedClassNamePattern; +import com.android.tools.r8.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,11 @@ StringBuilder builder, KeepMethodParametersPattern parametersPattern) { return parametersPattern.match( () -> builder.append("(***)"), - list -> StringUtils.append(builder, list, ", ", BraceType.PARENS)); + list -> + builder + .append('(') + .append(list.stream().map(Object::toString).collect(Collectors.joining(", "))) + .append(')')); } private static StringBuilder printMethodName( @@ -220,7 +223,7 @@ return "optimization"; case OBFUSCATING: return "obfuscation"; - case ACCESS_MODIFING: + case ACCESS_MODIFYING: return "accessmodification"; default: throw new Unimplemented();
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/processor/KeepEdgeProcessor.java b/src/keepanno/java/com/android/tools/r8/keepanno/processor/KeepEdgeProcessor.java new file mode 100644 index 0000000..36bbaeb --- /dev/null +++ b/src/keepanno/java/com/android/tools/r8/keepanno/processor/KeepEdgeProcessor.java
@@ -0,0 +1,279 @@ +// 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.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.keepanno.annotations.KeepConstants; +import com.android.tools.r8.keepanno.annotations.KeepConstants.Edge; +import com.android.tools.r8.keepanno.annotations.KeepConstants.Target; +import com.android.tools.r8.keepanno.asm.KeepEdgeReader; +import com.android.tools.r8.keepanno.asm.KeepEdgeWriter; +import com.android.tools.r8.keepanno.ast.KeepConsequences; +import com.android.tools.r8.keepanno.ast.KeepEdge; +import com.android.tools.r8.keepanno.ast.KeepEdge.Builder; +import com.android.tools.r8.keepanno.ast.KeepItemPattern; +import com.android.tools.r8.keepanno.ast.KeepMembersPattern; +import com.android.tools.r8.keepanno.ast.KeepMethodNamePattern; +import com.android.tools.r8.keepanno.ast.KeepMethodPattern; +import com.android.tools.r8.keepanno.ast.KeepQualifiedClassNamePattern; +import com.android.tools.r8.keepanno.ast.KeepTarget; +import com.android.tools.r8.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.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 = getEnclosingTypeElement(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 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, Edge.preconditions); + if (preconditions == null) { + return; + } + throw new Unimplemented(); + } + + private void processConsequences(Builder edgeBuilder, AnnotationMirror mirror) { + AnnotationValue consequences = getAnnotationValue(mirror, Edge.consequences); + if (consequences == null) { + return; + } + KeepConsequences.Builder consequencesBuilder = KeepConsequences.builder(); + new AnnotationListValueVisitor( + value -> { + KeepTarget.Builder targetBuilder = KeepTarget.builder(); + processTarget(targetBuilder, AnnotationMirrorValueVisitor.getMirror(value)); + consequencesBuilder.addTarget(targetBuilder.build()); + }) + .onValue(consequences); + edgeBuilder.setConsequences(consequencesBuilder.build()); + } + + private void processTarget(KeepTarget.Builder builder, AnnotationMirror mirror) { + KeepItemPattern.Builder itemBuilder = KeepItemPattern.builder(); + AnnotationValue classConstantValue = getAnnotationValue(mirror, Target.classConstant); + if (classConstantValue != null) { + DeclaredType type = AnnotationClassValueVisitor.getType(classConstantValue); + itemBuilder.setClassPattern(KeepQualifiedClassNamePattern.exact(type.toString())); + } + AnnotationValue methodNameValue = getAnnotationValue(mirror, Target.methodName); + if (methodNameValue != null) { + String methodName = AnnotationStringValueVisitor.getString(methodNameValue); + itemBuilder.setMembersPattern( + KeepMembersPattern.builder() + .addMethodPattern( + KeepMethodPattern.builder() + .setNamePattern(KeepMethodNamePattern.exact(methodName)) + .build()) + .build()); + } + + builder.setItem(itemBuilder.build()); + } + + private void error(String message) { + processingEnv.getMessager().printMessage(Kind.ERROR, message); + } + + private static TypeElement getEnclosingTypeElement(Element element) { + while (true) { + if (element == null || element instanceof TypeElement) { + return (TypeElement) element; + } + element = element.getEnclosingElement(); + } + } + + 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; + } + + /// Annotation Visitors + + private abstract static class AnnotationValueVisitorBase<T> + extends SimpleAnnotationValueVisitor7<T, Object> { + @Override + protected T defaultAction(Object o1, Object o2) { + throw new IllegalStateException(); + } + + public T onValue(AnnotationValue value) { + return value.accept(this, null); + } + } + + private static class AnnotationListValueVisitor + extends AnnotationValueVisitorBase<AnnotationListValueVisitor> { + + private final Consumer<AnnotationValue> fn; + + public AnnotationListValueVisitor(Consumer<AnnotationValue> fn) { + this.fn = fn; + } + + @Override + public AnnotationListValueVisitor visitArray( + List<? extends AnnotationValue> values, Object ignore) { + values.forEach(fn); + return this; + } + } + + private static class AnnotationMirrorValueVisitor + extends AnnotationValueVisitorBase<AnnotationMirrorValueVisitor> { + + private AnnotationMirror mirror = null; + + public static AnnotationMirror getMirror(AnnotationValue value) { + return new AnnotationMirrorValueVisitor().onValue(value).mirror; + } + + @Override + public AnnotationMirrorValueVisitor visitAnnotation(AnnotationMirror mirror, Object o) { + this.mirror = mirror; + return this; + } + } + + private static class AnnotationStringValueVisitor + extends AnnotationValueVisitorBase<AnnotationStringValueVisitor> { + private String string; + + public static String getString(AnnotationValue value) { + return new AnnotationStringValueVisitor().onValue(value).string; + } + + @Override + public AnnotationStringValueVisitor visitString(String string, Object ignore) { + this.string = string; + return this; + } + } + + private static class AnnotationClassValueVisitor + extends AnnotationValueVisitorBase<AnnotationClassValueVisitor> { + private DeclaredType type = null; + + public static DeclaredType getType(AnnotationValue value) { + return new AnnotationClassValueVisitor().onValue(value).type; + } + + @Override + public AnnotationClassValueVisitor visitType(TypeMirror t, Object ignore) { + ClassTypeVisitor classTypeVisitor = new ClassTypeVisitor(); + t.accept(classTypeVisitor, null); + type = classTypeVisitor.type; + return this; + } + } + + private static class TypeVisitorBase<T> extends SimpleTypeVisitor7<T, Object> { + @Override + protected T defaultAction(TypeMirror typeMirror, Object ignore) { + throw new IllegalStateException(); + } + } + + private static class ClassTypeVisitor extends TypeVisitorBase<ClassTypeVisitor> { + private DeclaredType type = null; + + @Override + public ClassTypeVisitor visitDeclared(DeclaredType t, Object ignore) { + this.type = t; + return this; + } + } +}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/utils/Unimplemented.java b/src/keepanno/java/com/android/tools/r8/keepanno/utils/Unimplemented.java new file mode 100644 index 0000000..8293126 --- /dev/null +++ b/src/keepanno/java/com/android/tools/r8/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.keepanno.utils; + +public class Unimplemented extends RuntimeException { + public Unimplemented() {} + + public Unimplemented(String msg) { + super(msg); + } +}
diff --git a/src/library_desugar/java/java/nio/file/FileApiFlips.java b/src/library_desugar/java/java/nio/file/FileApiFlips.java index b68aeb2..9c8a46a 100644 --- a/src/library_desugar/java/java/nio/file/FileApiFlips.java +++ b/src/library_desugar/java/java/nio/file/FileApiFlips.java
@@ -101,32 +101,35 @@ throw exception("java.nio.file.attribute.FileTime", suffix); } + public static Object flipMaybeFileTime(Object val) { + if (val instanceof j$.nio.file.attribute.FileTime) { + j$.nio.file.attribute.FileTime fileTime; + try { + fileTime = (j$.nio.file.attribute.FileTime) val; + } catch (ClassCastException cce) { + throw exceptionFileTime(cce); + } + return FileAttributeConversions.convert(fileTime); + } + if (val instanceof java.nio.file.attribute.FileTime) { + java.nio.file.attribute.FileTime fileTime; + try { + fileTime = (java.nio.file.attribute.FileTime) val; + } catch (ClassCastException cce) { + throw exceptionFileTime(cce); + } + return FileAttributeConversions.convert(fileTime); + } + return val; + } + public static Map<String, Object> flipMapWithMaybeFileTimeValues(Map<String, Object> in) { if (in == null || in.isEmpty()) { return in; } HashMap<String, Object> newMap = new HashMap<>(); for (String key : in.keySet()) { - Object val = in.get(key); - if (val instanceof j$.nio.file.attribute.FileTime) { - j$.nio.file.attribute.FileTime fileTime; - try { - fileTime = (j$.nio.file.attribute.FileTime) val; - } catch (ClassCastException cce) { - throw exceptionFileTime(cce); - } - newMap.put(key, FileAttributeConversions.convert(fileTime)); - } else if (val instanceof java.nio.file.attribute.FileTime) { - java.nio.file.attribute.FileTime fileTime; - try { - fileTime = (java.nio.file.attribute.FileTime) val; - } catch (ClassCastException cce) { - throw exceptionFileTime(cce); - } - newMap.put(key, FileAttributeConversions.convert(fileTime)); - } else { - newMap.put(key, val); - } + newMap.put(key, flipMaybeFileTime(in.get(key))); } return newMap; }
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs_nio.json b/src/library_desugar/jdk11/desugar_jdk_libs_nio.json index ff05a80..c97bd79 100644 --- a/src/library_desugar/jdk11/desugar_jdk_libs_nio.json +++ b/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
@@ -7,6 +7,13 @@ "common_flags": [ { "api_level_below_or_equal": 10000, + "amend_library_method": [ + "public static java.nio.file.Path java.nio.file.Path#of(java.lang.String, java.lang.String[])", + "public static java.nio.file.Path java.nio.file.Path#of(java.net.URI)" + ] + }, + { + "api_level_below_or_equal": 10000, "api_level_greater_or_equal": 24, "rewrite_prefix": { "java.util.stream.DesugarDoubleStream": "j$.util.stream.DesugarDoubleStream", @@ -35,13 +42,15 @@ "long java.util.concurrent.TimeUnit#convert(java.time.Duration)": "java.util.concurrent.DesugarTimeUnit" }, "retarget_method_with_emulated_dispatch": { - "long java.io.InputStream#transferTo(java.io.OutputStream)": "java.io.DesugarInputStream" + "long java.io.InputStream#transferTo(java.io.OutputStream)": "java.io.DesugarInputStream", + "long java.io.ByteArrayInputStream#transferTo(java.io.OutputStream)": "long java.io.DesugarInputStream#transferTo(java.io.InputStream, java.io.OutputStream)" }, "amend_library_method": [ "public static java.util.concurrent.TimeUnit java.util.concurrent.TimeUnit#of(java.time.temporal.ChronoUnit)", "public java.time.temporal.ChronoUnit java.util.concurrent.TimeUnit#toChronoUnit()", "public long java.util.concurrent.TimeUnit#convert(java.time.Duration)", - "public long java.io.InputStream#transferTo(java.io.OutputStream)" + "public long java.io.InputStream#transferTo(java.io.OutputStream)", + "public long java.io.ByteArrayInputStream#transferTo(java.io.OutputStream)" ] }, { @@ -220,7 +229,8 @@ "java.util.Map java.nio.file.spi.FileSystemProvider#readAttributes(java.nio.file.Path, java.lang.String, java.nio.file.LinkOption[])" : [-1, "java.util.Map java.nio.file.FileApiFlips#flipMapWithMaybeFileTimeValues(java.util.Map)"], "java.lang.Iterable java.nio.file.FileSystem#getRootDirectories()": [-1, "java.lang.Iterable java.nio.file.PathApiFlips#flipIterablePath(java.lang.Iterable)"], "java.util.Iterator java.nio.file.Path#iterator()": [-1, "java.util.Iterator java.nio.file.PathApiFlips#flipIteratorPath(java.util.Iterator)"], - "java.nio.file.DirectoryStream java.nio.file.spi.FileSystemProvider#newDirectoryStream(java.nio.file.Path, java.nio.file.DirectoryStream$Filter)": [-1, "java.nio.file.DirectoryStream java.nio.file.PathApiFlips#flipDirectoryStreamPath(java.nio.file.DirectoryStream)", 1, "java.nio.file.DirectoryStream$Filter java.nio.file.PathApiFlips#flipDirectoryStreamFilterPath(java.nio.file.DirectoryStream$Filter)"] + "java.nio.file.DirectoryStream java.nio.file.spi.FileSystemProvider#newDirectoryStream(java.nio.file.Path, java.nio.file.DirectoryStream$Filter)": [-1, "java.nio.file.DirectoryStream java.nio.file.PathApiFlips#flipDirectoryStreamPath(java.nio.file.DirectoryStream)", 1, "java.nio.file.DirectoryStream$Filter java.nio.file.PathApiFlips#flipDirectoryStreamFilterPath(java.nio.file.DirectoryStream$Filter)"], + "void java.nio.file.spi.FileSystemProvider#setAttribute(java.nio.file.Path, java.lang.String, java.lang.Object, java.nio.file.LinkOption[])": [2, "java.lang.Object java.nio.file.FileApiFlips#flipMaybeFileTime(java.lang.Object)"] }, "wrapper_conversion": [ "java.nio.channels.CompletionHandler", @@ -403,6 +413,14 @@ ] }, { + "api_level_below_or_equal": 10000, + "api_level_greater_or_equal": 26, + "retarget_method": { + "java.nio.file.Path java.nio.file.Path#of(java.lang.String, java.lang.String[])": "java.nio.file.Path java.nio.file.Paths#get(java.lang.String, java.lang.String[])", + "java.nio.file.Path java.nio.file.Path#of(java.net.URI)": "java.nio.file.Path java.nio.file.Paths#get(java.net.URI)" + } + }, + { "api_level_below_or_equal": 33, "amend_library_method": [ "public java.lang.Object[] java.util.Collection#toArray(java.util.function.IntFunction)"
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java index 249de08..84334f5 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
@@ -131,7 +131,8 @@ String owner = namingLens.lookupInternalName(rewrittenMethod.holder); String name = namingLens.lookupName(rewrittenMethod).toString(); String desc = rewrittenMethod.proto.toDescriptorString(namingLens); - visitor.visitMethodInsn(rewrittenType.getCfOpcode(), owner, name, desc, itf); + visitor.visitMethodInsn( + rewrittenType.getCfOpcode(), owner, name, desc, rewrittenType.isInterface() || itf); } }
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 deleted file mode 100644 index f61483a..0000000 --- a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepEdge.java +++ /dev/null
@@ -1,57 +0,0 @@ -// 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 preconditions; - private final KeepConsequences consequences; - - private KeepEdge(KeepPreconditions preconditions, KeepConsequences consequences) { - this.preconditions = preconditions; - this.consequences = consequences; - } - - public KeepPreconditions getPreconditions() { - return preconditions; - } - - public KeepConsequences getConsequences() { - return consequences; - } -}
diff --git a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodAccessPattern.java b/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodAccessPattern.java deleted file mode 100644 index 7f83937..0000000 --- a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepMethodAccessPattern.java +++ /dev/null
@@ -1,16 +0,0 @@ -// 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; - -// TODO: finish this. -public class KeepMethodAccessPattern { - - public static KeepMethodAccessPattern any() { - return new KeepMethodAccessPattern(); - } - - public boolean isAny() { - return true; - } -}
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 deleted file mode 100644 index 9fb521c..0000000 --- a/src/main/java/com/android/tools/r8/experimental/keepanno/ast/KeepOptions.java +++ /dev/null
@@ -1,97 +0,0 @@ -// 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.utils.SetUtils; -import com.google.common.collect.ImmutableSet; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Set; - -public final class KeepOptions { - - public enum KeepOption { - SHRINKING, - OPTIMIZING, - OBFUSCATING, - ACCESS_MODIFING, - } - - public static KeepOptions keepAll() { - if (ALLOW_NONE_INSTANCE == null) { - ALLOW_NONE_INSTANCE = new KeepOptions(true, Collections.emptySet()); - } - return ALLOW_NONE_INSTANCE; - } - - public static Builder allowBuilder() { - return new Builder(true); - } - - public static Builder disallowBuilder() { - return new Builder(false); - } - - public static KeepOptions allow(KeepOption... options) { - return allowBuilder().addAll(options).build(); - } - - public static KeepOptions disallow(KeepOption... options) { - return disallowBuilder().addAll(options).build(); - } - - public static class Builder { - public final boolean allowIfSet; - public Set<KeepOption> options = SetUtils.newIdentityHashSet(); - - private Builder(boolean allowIfSet) { - this.allowIfSet = allowIfSet; - } - - public Builder add(KeepOption option) { - options.add(option); - return this; - } - - public Builder addAll(KeepOption... options) { - return addAll(Arrays.asList(options)); - } - - public Builder addAll(Collection<KeepOption> options) { - this.options.addAll(options); - return this; - } - - public KeepOptions build() { - if (options.isEmpty()) { - if (allowIfSet) { - return keepAll(); - } - throw new KeepEdgeException("Invalid keep options that disallow nothing."); - } - if (options.size() == KeepOption.values().length) { - if (!allowIfSet) { - return keepAll(); - } - throw new KeepEdgeException("Invalid keep options that allow everything."); - } - return new KeepOptions(allowIfSet, ImmutableSet.copyOf(options)); - } - } - - 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 isAllowed(KeepOption option) { - return options.contains(option) == allowIfSet; - } -}
diff --git a/src/main/java/com/android/tools/r8/graph/AccessFlags.java b/src/main/java/com/android/tools/r8/graph/AccessFlags.java index 10620fc6..ee86680 100644 --- a/src/main/java/com/android/tools/r8/graph/AccessFlags.java +++ b/src/main/java/com/android/tools/r8/graph/AccessFlags.java
@@ -109,25 +109,14 @@ } public boolean isMoreVisibleThan( - AccessFlags other, String packageNameThis, String packageNameOther) { + AccessFlags<?> other, String packageNameThis, String packageNameOther) { int visibilityOrdinal = getVisibilityOrdinal(); if (visibilityOrdinal > other.getVisibilityOrdinal()) { return true; } - if (visibilityOrdinal == other.getVisibilityOrdinal() + return visibilityOrdinal == other.getVisibilityOrdinal() && isVisibilityDependingOnPackage() - && !packageNameThis.equals(packageNameOther)) { - return true; - } - return false; - } - - public boolean isAtLeastAsVisibleAs(AccessFlags other) { - return getVisibilityOrdinal() >= other.getVisibilityOrdinal(); - } - - public boolean isSameVisibility(AccessFlags other) { - return getVisibilityOrdinal() == other.getVisibilityOrdinal(); + && !packageNameThis.equals(packageNameOther); } public int getVisibilityOrdinal() {
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java index 42f5d96..369e60c 100644 --- a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java +++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
@@ -536,7 +536,7 @@ assert potentialHolder.isInterface(); for (DexEncodedMethod virtualMethod : potentialHolder.virtualMethods()) { if (virtualMethod.getReference().match(method.getReference()) - && virtualMethod.accessFlags.isSameVisibility(method.accessFlags)) { + && virtualMethod.isSameVisibility(method)) { return true; } }
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java index d68d906..985e215 100644 --- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java +++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -569,6 +569,40 @@ return isStatic(); } + public boolean isAtLeastAsVisibleAsOtherInSameHierarchy( + DexEncodedMethod other, AppView<? extends AppInfoWithClassHierarchy> appView) { + assert getReference().getProto() == other.getReference().getProto(); + assert appView.isSubtype(getHolderType(), other.getHolderType()).isTrue() + || appView.isSubtype(other.getHolderType(), getHolderType()).isTrue(); + AccessFlags<MethodAccessFlags> accessFlags = getAccessFlags(); + AccessFlags<?> otherAccessFlags = other.getAccessFlags(); + if (accessFlags.getVisibilityOrdinal() < otherAccessFlags.getVisibilityOrdinal()) { + return false; + } else if (accessFlags.isPrivate()) { + return getHolderType() == other.getHolderType(); + } else if (accessFlags.isPublic() || accessFlags.isProtected()) { + return true; + } else { + assert accessFlags.isPackagePrivate(); + return getHolderType().getPackageName().equals(other.getHolderType().getPackageName()); + } + } + + public boolean isSameVisibility(DexEncodedMethod other) { + AccessFlags<MethodAccessFlags> accessFlags = getAccessFlags(); + if (accessFlags.getVisibilityOrdinal() != other.getAccessFlags().getVisibilityOrdinal()) { + return false; + } + if (accessFlags.isPublic()) { + return true; + } + if (accessFlags.isPrivate()) { + return getHolderType() == other.getHolderType(); + } + assert accessFlags.isVisibilityDependingOnPackage(); + return getHolderType().getPackageName().equals(other.getHolderType().getPackageName()); + } + /** * Returns true if this method is synthetic. */
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecificationParser.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecificationParser.java index 16c578b..8f7f2b6 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecificationParser.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanDesugaredLibrarySpecificationParser.java
@@ -302,9 +302,26 @@ if (jsonFlagSet.has(RETARGET_METHOD_KEY)) { for (Map.Entry<String, JsonElement> retarget : jsonFlagSet.get(RETARGET_METHOD_KEY).getAsJsonObject().entrySet()) { - builder.retargetMethod( - parseMethod(retarget.getKey()), - stringDescriptorToDexType(retarget.getValue().getAsString())); + String key = retarget.getKey(); + String value = retarget.getValue().getAsString(); + if (value.contains("#")) { + builder.retargetMethodToMethod(parseMethod(key), parseMethod(value)); + } else { + builder.retargetMethodToType(parseMethod(key), stringDescriptorToDexType(value)); + } + } + } + if (jsonFlagSet.has(RETARGET_METHOD_EMULATED_DISPATCH_KEY)) { + for (Map.Entry<String, JsonElement> retarget : + jsonFlagSet.get(RETARGET_METHOD_EMULATED_DISPATCH_KEY).getAsJsonObject().entrySet()) { + String key = retarget.getKey(); + String value = retarget.getValue().getAsString(); + if (value.contains("#")) { + builder.retargetMethodEmulatedDispatchToMethod(parseMethod(key), parseMethod(value)); + } else { + builder.retargetMethodEmulatedDispatchToType( + parseMethod(key), stringDescriptorToDexType(value)); + } } } if (jsonFlagSet.has(COVARIANT_RETARGET_METHOD_KEY)) { @@ -315,14 +332,6 @@ stringDescriptorToDexType(retarget.getValue().getAsString())); } } - if (jsonFlagSet.has(RETARGET_METHOD_EMULATED_DISPATCH_KEY)) { - for (Map.Entry<String, JsonElement> retarget : - jsonFlagSet.get(RETARGET_METHOD_EMULATED_DISPATCH_KEY).getAsJsonObject().entrySet()) { - builder.retargetMethodEmulatedDispatch( - parseMethod(retarget.getKey()), - stringDescriptorToDexType(retarget.getValue().getAsString())); - } - } if (jsonFlagSet.has(BACKPORT_KEY)) { for (Map.Entry<String, JsonElement> backport : jsonFlagSet.get(BACKPORT_KEY).getAsJsonObject().entrySet()) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanRewritingFlags.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanRewritingFlags.java index 8c4316f..314d657 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanRewritingFlags.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanRewritingFlags.java
@@ -32,8 +32,10 @@ private final Map<DexType, DexType> emulatedInterfaces; private final Map<DexField, DexField> retargetStaticField; private final Map<DexMethod, DexType> covariantRetarget; - private final Map<DexMethod, DexType> retargetMethod; - private final Map<DexMethod, DexType> retargetMethodEmulatedDispatch; + private final Map<DexMethod, DexType> retargetMethodToType; + private final Map<DexMethod, DexType> retargetMethodEmulatedDispatchToType; + private final Map<DexMethod, DexMethod> retargetMethodToMethod; + private final Map<DexMethod, DexMethod> retargetMethodEmulatedDispatchToMethod; private final Map<DexMethod, DexMethod[]> apiGenericTypesConversion; private final Map<DexType, DexType> legacyBackport; private final Map<DexType, DexType> customConversions; @@ -52,8 +54,10 @@ Map<DexType, DexType> emulateLibraryInterface, Map<DexField, DexField> retargetStaticField, Map<DexMethod, DexType> covariantRetarget, - Map<DexMethod, DexType> retargetMethod, - Map<DexMethod, DexType> retargetMethodEmulatedDispatch, + Map<DexMethod, DexType> retargetMethodToType, + Map<DexMethod, DexType> retargetMethodEmulatedDispatchToType, + Map<DexMethod, DexMethod> retargetMethodToMethod, + Map<DexMethod, DexMethod> retargetMethodEmulatedDispatchToMethod, Map<DexMethod, DexMethod[]> apiGenericTypesConversion, Map<DexType, DexType> legacyBackport, Map<DexType, DexType> customConversion, @@ -70,8 +74,10 @@ this.emulatedInterfaces = emulateLibraryInterface; this.retargetStaticField = retargetStaticField; this.covariantRetarget = covariantRetarget; - this.retargetMethod = retargetMethod; - this.retargetMethodEmulatedDispatch = retargetMethodEmulatedDispatch; + this.retargetMethodToType = retargetMethodToType; + this.retargetMethodEmulatedDispatchToType = retargetMethodEmulatedDispatchToType; + this.retargetMethodToMethod = retargetMethodToMethod; + this.retargetMethodEmulatedDispatchToMethod = retargetMethodEmulatedDispatchToMethod; this.apiGenericTypesConversion = apiGenericTypesConversion; this.legacyBackport = legacyBackport; this.customConversions = customConversion; @@ -97,6 +103,8 @@ ImmutableMap.of(), ImmutableMap.of(), ImmutableMap.of(), + ImmutableMap.of(), + ImmutableMap.of(), ImmutableSet.of(), ImmutableSet.of(), ImmutableMap.of(), @@ -120,8 +128,10 @@ emulatedInterfaces, retargetStaticField, covariantRetarget, - retargetMethod, - retargetMethodEmulatedDispatch, + retargetMethodToType, + retargetMethodEmulatedDispatchToType, + retargetMethodToMethod, + retargetMethodEmulatedDispatchToMethod, apiGenericTypesConversion, legacyBackport, customConversions, @@ -161,12 +171,20 @@ return covariantRetarget; } - public Map<DexMethod, DexType> getRetargetMethod() { - return retargetMethod; + public Map<DexMethod, DexType> getRetargetMethodToType() { + return retargetMethodToType; } - public Map<DexMethod, DexType> getRetargetMethodEmulatedDispatch() { - return retargetMethodEmulatedDispatch; + public Map<DexMethod, DexType> getRetargetMethodEmulatedDispatchToType() { + return retargetMethodEmulatedDispatchToType; + } + + public Map<DexMethod, DexMethod> getRetargetMethodToMethod() { + return retargetMethodToMethod; + } + + public Map<DexMethod, DexMethod> getRetargetMethodEmulatedDispatchToMethod() { + return retargetMethodEmulatedDispatchToMethod; } public Set<DexMethod> getNeverOutlineApi() { @@ -211,8 +229,8 @@ && maintainPrefix.isEmpty() && emulatedInterfaces.isEmpty() && covariantRetarget.isEmpty() - && retargetMethod.isEmpty() - && retargetMethodEmulatedDispatch.isEmpty() + && retargetMethodToType.isEmpty() + && retargetMethodEmulatedDispatchToType.isEmpty() && retargetStaticField.isEmpty(); } @@ -228,8 +246,10 @@ private final Map<DexType, DexType> emulatedInterfaces; private final Map<DexField, DexField> retargetStaticField; private final Map<DexMethod, DexType> covariantRetarget; - private final Map<DexMethod, DexType> retargetMethod; - private final Map<DexMethod, DexType> retargetMethodEmulatedDispatch; + private final Map<DexMethod, DexType> retargetMethodToType; + private final Map<DexMethod, DexType> retargetMethodEmulatedDispatchToType; + private final Map<DexMethod, DexMethod> retargetMethodToMethod; + private final Map<DexMethod, DexMethod> retargetMethodEmulatedDispatchToMethod; private final Map<DexMethod, DexMethod[]> apiGenericTypesConversion; private final Map<DexType, DexType> legacyBackport; private final Map<DexType, DexType> customConversions; @@ -256,6 +276,8 @@ new IdentityHashMap<>(), new IdentityHashMap<>(), new IdentityHashMap<>(), + new IdentityHashMap<>(), + new IdentityHashMap<>(), Sets.newIdentityHashSet(), Sets.newIdentityHashSet(), new IdentityHashMap<>(), @@ -274,8 +296,10 @@ Map<DexType, DexType> emulateLibraryInterface, Map<DexField, DexField> retargetStaticField, Map<DexMethod, DexType> covariantRetarget, - Map<DexMethod, DexType> retargetMethod, - Map<DexMethod, DexType> retargetMethodEmulatedDispatch, + Map<DexMethod, DexType> retargetMethodToType, + Map<DexMethod, DexType> retargetMethodEmulatedDispatchToType, + Map<DexMethod, DexMethod> retargetMethodToMethod, + Map<DexMethod, DexMethod> retargetMethodEmulatedDispatchToMethod, Map<DexMethod, DexMethod[]> apiConversionCollection, Map<DexType, DexType> backportCoreLibraryMember, Map<DexType, DexType> customConversions, @@ -294,8 +318,12 @@ this.emulatedInterfaces = new IdentityHashMap<>(emulateLibraryInterface); this.retargetStaticField = new IdentityHashMap<>(retargetStaticField); this.covariantRetarget = new IdentityHashMap<>(covariantRetarget); - this.retargetMethod = new IdentityHashMap<>(retargetMethod); - this.retargetMethodEmulatedDispatch = new IdentityHashMap<>(retargetMethodEmulatedDispatch); + this.retargetMethodToType = new IdentityHashMap<>(retargetMethodToType); + this.retargetMethodEmulatedDispatchToType = + new IdentityHashMap<>(retargetMethodEmulatedDispatchToType); + this.retargetMethodToMethod = new IdentityHashMap<>(retargetMethodToMethod); + this.retargetMethodEmulatedDispatchToMethod = + new IdentityHashMap<>(retargetMethodEmulatedDispatchToMethod); this.apiGenericTypesConversion = new IdentityHashMap<>(apiConversionCollection); this.legacyBackport = new IdentityHashMap<>(backportCoreLibraryMember); this.customConversions = new IdentityHashMap<>(customConversions); @@ -384,15 +412,42 @@ return this; } - public Builder retargetMethod(DexMethod key, DexType rewrittenType) { + public Builder retargetMethodToType(DexMethod key, DexType rewrittenType) { put( - retargetMethod, + retargetMethodToType, key, rewrittenType, HumanDesugaredLibrarySpecificationParser.RETARGET_METHOD_KEY); return this; } + public Builder retargetMethodEmulatedDispatchToType(DexMethod key, DexType rewrittenType) { + put( + retargetMethodEmulatedDispatchToType, + key, + rewrittenType, + HumanDesugaredLibrarySpecificationParser.RETARGET_METHOD_EMULATED_DISPATCH_KEY); + return this; + } + + public Builder retargetMethodToMethod(DexMethod key, DexMethod retarget) { + put( + retargetMethodToMethod, + key, + retarget, + HumanDesugaredLibrarySpecificationParser.RETARGET_METHOD_KEY); + return this; + } + + public Builder retargetMethodEmulatedDispatchToMethod(DexMethod key, DexMethod retarget) { + put( + retargetMethodEmulatedDispatchToMethod, + key, + retarget, + HumanDesugaredLibrarySpecificationParser.RETARGET_METHOD_EMULATED_DISPATCH_KEY); + return this; + } + public Builder covariantRetargetMethod(DexMethod key, DexType rewrittenType) { put( covariantRetarget, @@ -411,15 +466,6 @@ return this; } - public Builder retargetMethodEmulatedDispatch(DexMethod key, DexType rewrittenType) { - put( - retargetMethodEmulatedDispatch, - key, - rewrittenType, - HumanDesugaredLibrarySpecificationParser.RETARGET_METHOD_EMULATED_DISPATCH_KEY); - return this; - } - public void addApiGenericTypesConversion(DexMethod method, int index, DexMethod conversion) { DexMethod[] types = apiGenericTypesConversion.computeIfAbsent( @@ -473,8 +519,10 @@ ImmutableMap.copyOf(emulatedInterfaces), ImmutableMap.copyOf(retargetStaticField), ImmutableMap.copyOf(covariantRetarget), - ImmutableMap.copyOf(retargetMethod), - ImmutableMap.copyOf(retargetMethodEmulatedDispatch), + ImmutableMap.copyOf(retargetMethodToType), + ImmutableMap.copyOf(retargetMethodEmulatedDispatchToType), + ImmutableMap.copyOf(retargetMethodToMethod), + ImmutableMap.copyOf(retargetMethodEmulatedDispatchToMethod), ImmutableMap.copyOf(apiGenericTypesConversion), ImmutableMap.copyOf(legacyBackport), ImmutableMap.copyOf(customConversions),
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/MultiAPILevelHumanDesugaredLibrarySpecificationFlagDeduplicator.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/MultiAPILevelHumanDesugaredLibrarySpecificationFlagDeduplicator.java index 6283419..ad9735c 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/MultiAPILevelHumanDesugaredLibrarySpecificationFlagDeduplicator.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/MultiAPILevelHumanDesugaredLibrarySpecificationFlagDeduplicator.java
@@ -93,15 +93,15 @@ commonBuilder::putEmulatedInterface, builder::putEmulatedInterface); deduplicateFlags( - flags.getRetargetMethod(), - otherFlags.getRetargetMethod(), - commonBuilder::retargetMethod, - builder::retargetMethod); + flags.getRetargetMethodToType(), + otherFlags.getRetargetMethodToType(), + commonBuilder::retargetMethodToType, + builder::retargetMethodToType); deduplicateFlags( - flags.getRetargetMethodEmulatedDispatch(), - otherFlags.getRetargetMethodEmulatedDispatch(), - commonBuilder::retargetMethodEmulatedDispatch, - builder::retargetMethodEmulatedDispatch); + flags.getRetargetMethodEmulatedDispatchToType(), + otherFlags.getRetargetMethodEmulatedDispatchToType(), + commonBuilder::retargetMethodEmulatedDispatchToType, + builder::retargetMethodEmulatedDispatchToType); deduplicateFlags( flags.getLegacyBackport(), otherFlags.getLegacyBackport(),
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/MultiAPILevelHumanDesugaredLibrarySpecificationJsonExporter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/MultiAPILevelHumanDesugaredLibrarySpecificationJsonExporter.java index 736632a..2096b33 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/MultiAPILevelHumanDesugaredLibrarySpecificationJsonExporter.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/MultiAPILevelHumanDesugaredLibrarySpecificationJsonExporter.java
@@ -124,13 +124,22 @@ if (!flags.getRetargetStaticField().isEmpty()) { toJson.put(RETARGET_STATIC_FIELD_KEY, mapToString(flags.getRetargetStaticField())); } - if (!flags.getRetargetMethod().isEmpty()) { - toJson.put(RETARGET_METHOD_KEY, mapToString(flags.getRetargetMethod())); + if (!flags.getRetargetMethodToType().isEmpty()) { + toJson.put(RETARGET_METHOD_KEY, mapToString(flags.getRetargetMethodToType())); } - if (!flags.getRetargetMethodEmulatedDispatch().isEmpty()) { + if (!flags.getRetargetMethodEmulatedDispatchToMethod().isEmpty()) { + toJson.put( + RETARGET_METHOD_KEY, mapToString(flags.getRetargetMethodEmulatedDispatchToMethod())); + } + if (!flags.getRetargetMethodEmulatedDispatchToType().isEmpty()) { toJson.put( RETARGET_METHOD_EMULATED_DISPATCH_KEY, - mapToString(flags.getRetargetMethodEmulatedDispatch())); + mapToString(flags.getRetargetMethodEmulatedDispatchToType())); + } + if (!flags.getRetargetMethodEmulatedDispatchToMethod().isEmpty()) { + toJson.put( + RETARGET_METHOD_EMULATED_DISPATCH_KEY, + mapToString(flags.getRetargetMethodEmulatedDispatchToMethod())); } if (!flags.getDontRetarget().isEmpty()) { toJson.put(DONT_RETARGET_KEY, setToString(flags.getDontRetarget()));
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineEmulatedInterfaceConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineEmulatedInterfaceConverter.java index 5f385c5..00c7da1 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineEmulatedInterfaceConverter.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineEmulatedInterfaceConverter.java
@@ -95,7 +95,7 @@ LinkedHashMap<DexType, DerivedMethod> extraDispatchCases = new LinkedHashMap<>(); // Retarget core lib emulated dispatch handled as part of emulated interface dispatch. Map<DexMethod, DexType> retargetCoreLibMember = - rewritingFlags.getRetargetMethodEmulatedDispatch(); + rewritingFlags.getRetargetMethodEmulatedDispatchToType(); for (DexMethod retarget : retargetCoreLibMember.keySet()) { if (retarget.match(method)) { DexClass inClass = appInfo.definitionFor(retarget.getHolderType());
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachinePrefixConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachinePrefixConverter.java index a1a8d29..7a669c2 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachinePrefixConverter.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachinePrefixConverter.java
@@ -50,11 +50,14 @@ public void convertPrefixFlags( HumanRewritingFlags rewritingFlags, BiConsumer<String, Set<DexString>> warnConsumer) { rewriteClasses(); - rewriteValues(rewritingFlags.getRetargetMethod()); - rewriteValues(rewritingFlags.getRetargetMethodEmulatedDispatch()); + rewriteValues(rewritingFlags.getRetargetMethodToType()); + rewriteValues(rewritingFlags.getRetargetMethodEmulatedDispatchToType()); rewriteValues(rewritingFlags.getCustomConversions()); + rewriteMethodValues(rewritingFlags.getRetargetMethodToMethod()); + rewriteMethodValues(rewritingFlags.getRetargetMethodEmulatedDispatchToMethod()); rewriteEmulatedInterface(rewritingFlags.getEmulatedInterfaces()); - rewriteRetargetKeys(rewritingFlags.getRetargetMethodEmulatedDispatch()); + rewriteRetargetKeys(rewritingFlags.getRetargetMethodEmulatedDispatchToType()); + rewriteRetargetKeys(rewritingFlags.getRetargetMethodEmulatedDispatchToMethod()); rewriteApiConversions(rewritingFlags.getApiGenericConversion()); warnIfUnusedPrefix(warnConsumer); } @@ -79,7 +82,7 @@ convertedPrefix + interfaceType.substring(firstPackage + 1))); } - private void rewriteRetargetKeys(Map<DexMethod, DexType> retarget) { + private void rewriteRetargetKeys(Map<DexMethod, ?> retarget) { for (DexMethod dexMethod : retarget.keySet()) { DexType type = convertJavaNameToDesugaredLibrary(dexMethod.holder); builder.rewriteDerivedTypeOnly(dexMethod.holder, type); @@ -108,6 +111,12 @@ } } + private void rewriteMethodValues(Map<?, DexMethod> flags) { + for (DexMethod method : flags.values()) { + registerType(method.getHolderType()); + } + } + private void rewriteClasses() { appInfo.app().forEachLibraryType(this::registerClassType); if (libraryCompilation) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineRetargetConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineRetargetConverter.java index 6078e9d..004c35f 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineRetargetConverter.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineRetargetConverter.java
@@ -24,6 +24,7 @@ import java.util.Map; import java.util.Set; import java.util.function.BiConsumer; +import java.util.function.Consumer; public class HumanToMachineRetargetConverter { @@ -45,13 +46,22 @@ .getCovariantRetarget() .forEach((method, type) -> convertCovariantRetarget(builder, method, type)); rewritingFlags - .getRetargetMethod() - .forEach((method, type) -> convertRetargetMethod(builder, method, type)); + .getRetargetMethodToType() + .forEach((method, type) -> convertRetargetMethodToType(builder, method, type)); rewritingFlags - .getRetargetMethodEmulatedDispatch() + .getRetargetMethodEmulatedDispatchToType() .forEach( (method, type) -> - convertRetargetMethodEmulatedDispatch(builder, rewritingFlags, method, type)); + convertRetargetMethodEmulatedDispatchToType(builder, rewritingFlags, method, type)); + rewritingFlags + .getRetargetMethodToMethod() + .forEach((method, retarget) -> convertRetargetMethodToMethod(builder, method, retarget)); + rewritingFlags + .getRetargetMethodEmulatedDispatchToMethod() + .forEach( + (method, retarget) -> + convertRetargetMethodEmulatedDispatchToMethod( + builder, rewritingFlags, method, retarget)); warnConsumer.accept("Cannot retarget missing references: ", missingReferences); } @@ -66,37 +76,6 @@ builder.putStaticFieldRetarget(field, rewrittenField); } - private void convertRetargetMethodEmulatedDispatch( - MachineRewritingFlags.Builder builder, - HumanRewritingFlags rewritingFlags, - DexMethod method, - DexType type) { - DexClass holder = appInfo.definitionFor(method.holder); - DexEncodedMethod foundMethod = holder.lookupMethod(method); - if (foundMethod == null) { - missingReferences.add(method); - return; - } - if (foundMethod.isStatic()) { - appInfo - .app() - .options - .reporter - .error("Cannot generate emulated dispatch for static method " + foundMethod); - return; - } - if (!seemsToNeedEmulatedDispatch(holder, foundMethod)) { - appInfo - .app() - .options - .reporter - .warning( - "Generating (seemingly unnecessary) emulated dispatch for final method " - + foundMethod); - } - convertEmulatedVirtualRetarget(builder, rewritingFlags, foundMethod, type); - } - private void convertCovariantRetarget( MachineRewritingFlags.Builder builder, DexMethod method, DexType type) { DexClass holder = appInfo.definitionFor(method.holder); @@ -133,7 +112,9 @@ } private void convertRetargetMethod( - MachineRewritingFlags.Builder builder, DexMethod method, DexType type) { + DexMethod method, + Consumer<DexEncodedMethod> staticRetarget, + Consumer<DexEncodedMethod> nonEmulatedVirtualRetarget) { DexClass holder = appInfo.definitionFor(method.holder); DexEncodedMethod foundMethod = holder.lookupMethod(method); if (foundMethod == null) { @@ -141,7 +122,7 @@ return; } if (foundMethod.isStatic()) { - convertStaticRetarget(builder, foundMethod, type); + staticRetarget.accept(foundMethod); return; } if (seemsToNeedEmulatedDispatch(holder, foundMethod)) { @@ -154,7 +135,72 @@ + foundMethod + " which could lead to invalid runtime execution in overrides."); } - convertNonEmulatedVirtualRetarget(builder, foundMethod, type); + nonEmulatedVirtualRetarget.accept(foundMethod); + } + + private void convertRetargetMethodToType( + MachineRewritingFlags.Builder builder, DexMethod method, DexType type) { + convertRetargetMethod( + method, + foundMethod -> convertStaticRetarget(builder, foundMethod, type), + foundMethod -> convertNonEmulatedVirtualRetarget(builder, foundMethod, type)); + } + + private void convertRetargetMethodToMethod( + MachineRewritingFlags.Builder builder, DexMethod method, DexMethod retarget) { + convertRetargetMethod( + method, + foundMethod -> builder.putStaticRetarget(method, retarget), + foundMethod -> builder.putNonEmulatedVirtualRetarget(method, retarget)); + } + + private void convertRetargetMethodEmulatedDispatch( + DexMethod method, Consumer<DexEncodedMethod> emulatedRetarget) { + DexClass holder = appInfo.definitionFor(method.holder); + DexEncodedMethod foundMethod = holder.lookupMethod(method); + if (foundMethod == null) { + missingReferences.add(method); + return; + } + if (foundMethod.isStatic()) { + appInfo + .app() + .options + .reporter + .error("Cannot generate emulated dispatch for static method " + foundMethod); + return; + } + if (!seemsToNeedEmulatedDispatch(holder, foundMethod)) { + appInfo + .app() + .options + .reporter + .warning( + "Generating (seemingly unnecessary) emulated dispatch for final method " + + foundMethod); + } + emulatedRetarget.accept(foundMethod); + } + + private void convertRetargetMethodEmulatedDispatchToType( + MachineRewritingFlags.Builder builder, + HumanRewritingFlags rewritingFlags, + DexMethod method, + DexType type) { + convertRetargetMethodEmulatedDispatch( + method, + foundMethod -> convertEmulatedVirtualRetarget(builder, rewritingFlags, foundMethod, type)); + } + + private void convertRetargetMethodEmulatedDispatchToMethod( + MachineRewritingFlags.Builder builder, + HumanRewritingFlags rewritingFlags, + DexMethod method, + DexMethod retarget) { + convertRetargetMethodEmulatedDispatch( + method, + foundMethod -> + convertEmulatedVirtualRetarget(builder, rewritingFlags, foundMethod, retarget)); } private boolean seemsToNeedEmulatedDispatch(DexClass holder, DexEncodedMethod method) { @@ -166,10 +212,7 @@ MachineRewritingFlags.Builder builder, HumanRewritingFlags rewritingFlags, DexEncodedMethod src, - DexType type) { - DexProto newProto = appInfo.dexItemFactory().prependHolderToProto(src.getReference()); - DexMethod forwardingDexMethod = - appInfo.dexItemFactory().createMethod(type, newProto, src.getName()); + DexMethod forwardingDexMethod) { if (isEmulatedInterfaceDispatch(src, appInfo, rewritingFlags)) { // Handled by emulated interface dispatch. builder.putEmulatedVirtualRetargetThroughEmulatedInterface( @@ -190,6 +233,17 @@ interfaceMethod, dispatchMethod, forwardingMethod, dispatchCases)); } + private void convertEmulatedVirtualRetarget( + MachineRewritingFlags.Builder builder, + HumanRewritingFlags rewritingFlags, + DexEncodedMethod src, + DexType type) { + DexProto newProto = appInfo.dexItemFactory().prependHolderToProto(src.getReference()); + DexMethod forwardingDexMethod = + appInfo.dexItemFactory().createMethod(type, newProto, src.getName()); + convertEmulatedVirtualRetarget(builder, rewritingFlags, src, forwardingDexMethod); + } + private boolean isEmulatedInterfaceDispatch( DexEncodedMethod method, AppInfoWithClassHierarchy appInfo,
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/LegacyToHumanSpecificationConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/LegacyToHumanSpecificationConverter.java index b251add..1f7fe89 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/LegacyToHumanSpecificationConverter.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/LegacyToHumanSpecificationConverter.java
@@ -161,7 +161,7 @@ itemFactory.createMethod( itemFactory.createType(itemFactory.arraysDescriptor), proto, name); DexType target = itemFactory.createType("Ljava/util/DesugarArrays;"); - builder.retargetMethod(source, target); + builder.retargetMethodToType(source, target); builder.amendLibraryMethod( source, @@ -178,7 +178,7 @@ source = itemFactory.createMethod(itemFactory.createType("Ljava/util/TimeZone;"), proto, name); target = itemFactory.createType("Ljava/util/DesugarTimeZone;"); - builder.retargetMethod(source, target); + builder.retargetMethodToType(source, target); } // Required by // https://github.com/google/desugar_jdk_libs/commit/485071cd09a3691549d065ba9e323d07edccf085. @@ -272,9 +272,9 @@ if (definition.isStatic() || definition.isFinal() || dexClassAndMethod.getHolder().isFinal()) { - builder.retargetMethod(dexClassAndMethod.getReference(), rewrittenType); + builder.retargetMethodToType(dexClassAndMethod.getReference(), rewrittenType); } else { - builder.retargetMethodEmulatedDispatch( + builder.retargetMethodEmulatedDispatchToType( dexClassAndMethod.getReference(), rewrittenType); } }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java index 6099074..3d9b8d2 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -2220,8 +2220,7 @@ InstructionListIterator it = block.listIterator(code); while (it.hasNext()) { Instruction instruction = it.next(); - if (instruction.getLocalInfo() != null - || !allowNewFilledArrayConstruction(instruction)) { + if (instruction.getLocalInfo() != null || !allowNewFilledArrayConstruction(instruction)) { continue; } NewArrayEmpty newArray = instruction.asNewArrayEmpty();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java index 8a023db..08f3912 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
@@ -395,8 +395,7 @@ .isPossiblyFalse() || !newResolutionResult .getResolvedMethod() - .getAccessFlags() - .isAtLeastAsVisibleAs(resolutionResult.getResolvedMethod().getAccessFlags()) + .isAtLeastAsVisibleAsOtherInSameHierarchy(resolutionResult.getResolvedMethod(), appView) // isOverriding expects both arguments to be not private. || (!newResolutionResult.getResolvedMethod().isPrivateMethod() && !isOverriding(
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLens.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLens.java index 0cc861f..0dbea46 100644 --- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLens.java +++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingIdentityLens.java
@@ -86,7 +86,13 @@ } private DexMethod getReboundMethodReference(DexMethod method) { - return nonReboundMethodReferenceToDefinitionMap.getOrDefault(method, method); + DexMethod rebound = nonReboundMethodReferenceToDefinitionMap.get(method); + while (rebound != null) { + method = rebound; + rebound = nonReboundMethodReferenceToDefinitionMap.get(method); + } + assert method != rebound; + return method; } @Override
diff --git a/src/main/java/com/android/tools/r8/optimize/RedundantBridgeRemover.java b/src/main/java/com/android/tools/r8/optimize/RedundantBridgeRemover.java index d0066e0..5cbb1b6 100644 --- a/src/main/java/com/android/tools/r8/optimize/RedundantBridgeRemover.java +++ b/src/main/java/com/android/tools/r8/optimize/RedundantBridgeRemover.java
@@ -10,12 +10,17 @@ import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexProgramClass; +import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.MethodResolutionResult; +import com.android.tools.r8.graph.MethodResolutionResult.FailedResolutionResult; +import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult; import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.graph.PrunedItems; import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo; import com.android.tools.r8.optimize.InvokeSingleTargetExtractor.InvokeKind; import com.android.tools.r8.optimize.redundantbridgeremoval.RedundantBridgeRemovalLens; import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.shaking.KeepMethodInfo; import com.android.tools.r8.utils.collections.ProgramMethodSet; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -31,12 +36,7 @@ } private DexClassAndMethod getTargetForRedundantBridge(ProgramMethod method) { - // Clean-up the predicate check. - if (appView.appInfo().isPinned(method.getReference())) { - return null; - } DexEncodedMethod definition = method.getDefinition(); - // TODO(b/197490164): Remove if method is abstract. BridgeInfo bridgeInfo = definition.getOptimizationInfo().getBridgeInfo(); boolean isBridge = definition.isBridge() || bridgeInfo != null; if (!isBridge || definition.isAbstract()) { @@ -59,18 +59,10 @@ if (targetMethod == null) { return null; } - if (method.getAccessFlags().isPublic()) { - if (!targetMethod.getAccessFlags().isPublic()) { - return null; - } - } else { - if (targetMethod.getAccessFlags().isProtected() - && !targetMethod.getHolderType().isSamePackage(method.getHolderType())) { - return null; - } - if (targetMethod.getAccessFlags().isPrivate()) { - return null; - } + if (!targetMethod + .getDefinition() + .isAtLeastAsVisibleAsOtherInSameHierarchy(method.getDefinition(), appView)) { + return null; } if (definition.isStatic() && method.getHolder().hasClassInitializer() @@ -148,6 +140,16 @@ ProgramMethodSet bridgesToRemoveForClass = ProgramMethodSet.create(); clazz.forEachProgramMethod( method -> { + KeepMethodInfo keepInfo = appView.getKeepInfo(method); + if (!keepInfo.isShrinkingAllowed(appView.options()) + || !keepInfo.isOptimizationAllowed(appView.options())) { + return; + } + if (isRedundantAbstractBridge(method)) { + // Record that the redundant bridge should be removed. + bridgesToRemoveForClass.add(method); + return; + } DexClassAndMethod target = getTargetForRedundantBridge(method); if (target != null) { // Record that the redundant bridge should be removed. @@ -175,6 +177,55 @@ return bridgesToRemove; } + private boolean isRedundantAbstractBridge(ProgramMethod method) { + if (!method.getAccessFlags().isAbstract() || method.getDefinition().getCode() != null) { + return false; + } + DexProgramClass holder = method.getHolder(); + if (holder.getSuperType() == null) { + assert holder.getType() == appView.dexItemFactory().objectType; + return false; + } + MethodResolutionResult superTypeResolution = + appView.appInfo().resolveMethodOn(holder.getSuperType(), method.getReference(), false); + if (superTypeResolution.isMultiMethodResolutionResult()) { + return false; + } + // Check if there is a definition in the super type hieararchy that is also abstract and has the + // same visibility. + if (superTypeResolution.isSingleResolution()) { + DexClassAndMethod resolutionPair = + superTypeResolution.asSingleResolution().getResolutionPair(); + return resolutionPair.getDefinition().isAbstract() + && resolutionPair + .getDefinition() + .isAtLeastAsVisibleAsOtherInSameHierarchy(method.getDefinition(), appView) + && (!resolutionPair.getHolder().isInterface() || holder.getInterfaces().isEmpty()); + } + // Only check for interfaces if resolving the method on super type causes NoSuchMethodError. + FailedResolutionResult failedResolutionResult = superTypeResolution.asFailedResolution(); + if (failedResolutionResult == null + || !failedResolutionResult.isNoSuchMethodErrorResult(holder, appView.appInfo()) + || holder.getInterfaces().isEmpty()) { + return false; + } + for (DexType iface : holder.getInterfaces()) { + SingleResolutionResult<?> singleIfaceResult = + appView + .appInfo() + .resolveMethodOn(iface, method.getReference(), true) + .asSingleResolution(); + if (singleIfaceResult == null + || !singleIfaceResult.getResolvedMethod().isAbstract() + || !singleIfaceResult + .getResolvedMethod() + .isAtLeastAsVisibleAsOtherInSameHierarchy(method.getDefinition(), appView)) { + return false; + } + } + return true; + } + private void pruneApp( Map<DexProgramClass, ProgramMethodSet> bridgesToRemove, ExecutorService executorService) throws ExecutionException {
diff --git a/src/main/java/com/android/tools/r8/optimize/redundantbridgeremoval/RedundantBridgeRemovalLens.java b/src/main/java/com/android/tools/r8/optimize/redundantbridgeremoval/RedundantBridgeRemovalLens.java index a680b36..042cc6a 100644 --- a/src/main/java/com/android/tools/r8/optimize/redundantbridgeremoval/RedundantBridgeRemovalLens.java +++ b/src/main/java/com/android/tools/r8/optimize/redundantbridgeremoval/RedundantBridgeRemovalLens.java
@@ -13,6 +13,7 @@ import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens; import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription; +import com.android.tools.r8.ir.code.Invoke; import com.google.common.collect.Sets; import java.util.IdentityHashMap; import java.util.Map; @@ -80,14 +81,18 @@ do { newReference = methodMap.get(newReference); } while (methodMap.containsKey(newReference)); - if (previous.getType().isSuper() && interfaces.contains(newReference.getHolderType())) { + boolean holderTypeIsInterface = interfaces.contains(newReference.getHolderType()); + if (previous.getType().isSuper() && holderTypeIsInterface) { return previous; } return MethodLookupResult.builder(this) .setReference(newReference) .setReboundReference(newReference) .setPrototypeChanges(previous.getPrototypeChanges()) - .setType(previous.getType()) + .setType( + holderTypeIsInterface && previous.getType().isVirtual() + ? Invoke.Type.INTERFACE + : previous.getType()) .build(); } return previous;
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java index 9af4f06..e653a7a 100644 --- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java +++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1548,7 +1548,8 @@ private boolean enableInterfaceMerging = System.getProperty("com.android.tools.r8.enableHorizontalInterfaceMerging") != null; private boolean enableInterfaceMergingInInitial = false; - private boolean enableSameFilePolicy = false; + private boolean enableSameFilePolicy = + System.getProperty("com.android.tools.r8.enableSameFilePolicy") != null; private boolean enableSyntheticMerging = true; private boolean ignoreRuntimeTypeChecksForTesting = false; private boolean restrictToSynthetics = false;
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/examplesJava11/path/PathExample.java b/src/test/examplesJava11/path/PathExample.java new file mode 100644 index 0000000..8b17da2 --- /dev/null +++ b/src/test/examplesJava11/path/PathExample.java
@@ -0,0 +1,16 @@ +// 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 path; + +import java.nio.file.Path; + +public class PathExample { + + public static void main(String[] args) { + Path thePath = Path.of("foo", "bar"); + System.out.println(thePath); + System.out.println(Path.of(thePath.toUri()).getFileName()); + } +}
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/bridgeremoval/BridgeWithInvokeSuperOnInterfaceTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/BridgeWithInvokeSuperOnInterfaceTest.java new file mode 100644 index 0000000..cc24115 --- /dev/null +++ b/src/test/java/com/android/tools/r8/bridgeremoval/BridgeWithInvokeSuperOnInterfaceTest.java
@@ -0,0 +1,107 @@ +// 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.bridgeremoval; + +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.MatcherAssert.assertThat; + +import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoVerticalClassMerging; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.graph.MethodAccessFlags; +import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate; +import com.android.tools.r8.utils.codeinspector.ClassSubject; +import com.android.tools.r8.utils.codeinspector.MethodSubject; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class BridgeWithInvokeSuperOnInterfaceTest extends TestBase { + + private static final String[] EXPECTED = new String[] {"I::foo"}; + + @Parameter() public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + @Test + public void testRuntime() throws Exception { + testForRuntime(parameters) + .addProgramClasses(Main.class, I.class) + .addProgramClassFileData(getJWithBridgeAccessFlag()) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines(EXPECTED); + } + + @Test + public void testR8() throws Exception { + testForR8(parameters.getBackend()) + .addProgramClasses(Main.class, I.class) + .addProgramClassFileData(getJWithBridgeAccessFlag()) + .setMinApi(parameters.getApiLevel()) + .addKeepMainRule(Main.class) + .enableNoVerticalClassMergingAnnotations() + .enableInliningAnnotations() + .addDontObfuscate() + .compile() + .inspect( + inspector -> { + // Check that we are removing the bridge if we support default methods. + if (parameters.canUseDefaultAndStaticInterfaceMethods()) { + ClassSubject J = inspector.clazz(J.class); + assertThat(J, isPresent()); + MethodSubject fooMethod = J.uniqueMethodWithOriginalName("foo"); + assertThat(fooMethod, not(isPresent())); + } + }) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines(EXPECTED); + } + + private byte[] getJWithBridgeAccessFlag() throws Exception { + return transformer(J.class) + .setAccessFlags(MethodPredicate.onName("foo"), MethodAccessFlags::setBridge) + .transform(); + } + + @NoVerticalClassMerging + public interface I { + + @NeverInline + default void foo() { + System.out.println("I::foo"); + } + } + + @NoVerticalClassMerging + public interface J extends I { + + @Override + default void foo() { + I.super.foo(); + } + } + + public static class Main implements J { + + public static void main(String[] args) { + new Main().callSuper(); + } + + @NeverInline + public void callSuper() { + J.super.foo(); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/BridgeWithInvokeSuperTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/BridgeWithInvokeSuperTest.java new file mode 100644 index 0000000..b965fea --- /dev/null +++ b/src/test/java/com/android/tools/r8/bridgeremoval/BridgeWithInvokeSuperTest.java
@@ -0,0 +1,101 @@ +// 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.bridgeremoval; + +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.MatcherAssert.assertThat; + +import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NoVerticalClassMerging; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.graph.MethodAccessFlags; +import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate; +import com.android.tools.r8.utils.codeinspector.ClassSubject; +import com.android.tools.r8.utils.codeinspector.MethodSubject; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class BridgeWithInvokeSuperTest extends TestBase { + + private static final String[] EXPECTED = new String[] {"I::foo"}; + + @Parameter() public TestParameters parameters; + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimesAndApiLevels().build(); + } + + @Test + public void testRuntime() throws Exception { + testForRuntime(parameters) + .addProgramClasses(Main.class, I.class) + .addProgramClassFileData(getAWithBridgeAccessFlag()) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines(EXPECTED); + } + + @Test + public void testR8() throws Exception { + testForR8(parameters.getBackend()) + .addProgramClasses(Main.class, I.class) + .addProgramClassFileData(getAWithBridgeAccessFlag()) + .setMinApi(parameters.getApiLevel()) + .addKeepMainRule(Main.class) + .enableNoVerticalClassMergingAnnotations() + .enableInliningAnnotations() + .compile() + .inspect( + inspector -> { + // Check that we are removing the bridge if we support default methods. + if (parameters.canUseDefaultAndStaticInterfaceMethods()) { + ClassSubject A = inspector.clazz(A.class); + assertThat(A, isPresent()); + MethodSubject fooMethod = A.uniqueMethodWithOriginalName("foo"); + assertThat(fooMethod, not(isPresent())); + } + }) + .run(parameters.getRuntime(), Main.class) + .assertSuccessWithOutputLines(EXPECTED); + } + + private byte[] getAWithBridgeAccessFlag() throws Exception { + return transformer(A.class) + .setAccessFlags(MethodPredicate.onName("foo"), MethodAccessFlags::setBridge) + .transform(); + } + + @NoVerticalClassMerging + public interface I { + + @NeverInline + default void foo() { + System.out.println("I::foo"); + } + } + + public static class A implements I { + + @Override + @NeverInline + public void foo() { + I.super.foo(); + } + } + + public static class Main { + + public static void main(String[] args) { + new A().foo(); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesTest.java index cef3785..e6015ad 100644 --- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesTest.java +++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesTest.java
@@ -25,9 +25,11 @@ import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.FileTime; import java.nio.file.attribute.PosixFileAttributeView; import java.nio.file.attribute.PosixFileAttributes; import java.nio.file.attribute.PosixFilePermission; +import java.time.Instant; import java.util.HashSet; import java.util.Iterator; import java.util.List; @@ -157,6 +159,7 @@ readWriteThroughFilesAPI(path); readThroughFileChannelAPI(path); attributeAccess(path); + Files.setAttribute(path, "basic:lastModifiedTime", FileTime.from(Instant.EPOCH)); fspMethodsWithGeneric(path); pathGeneric(); }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/InputStreamTransferToTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/InputStreamTransferToTest.java index 70d445d..130cce2 100644 --- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/InputStreamTransferToTest.java +++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/InputStreamTransferToTest.java
@@ -13,6 +13,7 @@ import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase; import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification; import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification; +import com.android.tools.r8.utils.AndroidApiLevel; import com.android.tools.r8.utils.StringUtils; import com.google.common.collect.ImmutableList; import java.nio.file.Path; @@ -67,4 +68,23 @@ .run(parameters.getRuntime(), MAIN_CLASS) .assertSuccessWithOutput(EXPECTED_OUTPUT); } + + /** + * See b/248200357, in T an override or transferTo was introduced in android.jar changing + * resolution. + */ + @Test + public void testWithAndroidJarFromT() throws Exception { + // The method is not present on JDK8 so if we don't desugar that won't work. + Assume.assumeFalse( + parameters.isCfRuntime(CfVm.JDK8) + && !libraryDesugaringSpecification.hasNioFileDesugaring(parameters) + && compilationSpecification.isCfToCf()); + testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification) + .addProgramFiles(INPUT_JAR) + .addKeepMainRule(MAIN_CLASS) + .overrideLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T)) + .run(parameters.getRuntime(), MAIN_CLASS) + .assertSuccessWithOutput(EXPECTED_OUTPUT); + } }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/PathOfTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/PathOfTest.java new file mode 100644 index 0000000..33fa6ab --- /dev/null +++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/PathOfTest.java
@@ -0,0 +1,61 @@ +// 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.desugar.desugaredlibrary.jdk11; + +import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.DEFAULT_SPECIFICATIONS; +import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11_PATH; + +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.ToolHelper; +import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase; +import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification; +import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification; +import com.android.tools.r8.utils.StringUtils; +import com.google.common.collect.ImmutableList; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class PathOfTest extends DesugaredLibraryTestBase { + + private final TestParameters parameters; + private final LibraryDesugaringSpecification libraryDesugaringSpecification; + private final CompilationSpecification compilationSpecification; + + private static final Path INPUT_JAR = Paths.get(ToolHelper.EXAMPLES_JAVA11_JAR_DIR + "path.jar"); + private static final String EXPECTED_OUTPUT = StringUtils.lines("foo/bar", "bar"); + private static final String MAIN_CLASS = "path.PathExample"; + + @Parameters(name = "{0}, spec: {1}, {2}") + public static List<Object[]> data() { + return buildParameters( + getTestParameters().withDexRuntimes().withAllApiLevels().build(), + ImmutableList.of(JDK11_PATH), + DEFAULT_SPECIFICATIONS); + } + + public PathOfTest( + TestParameters parameters, + LibraryDesugaringSpecification libraryDesugaringSpecification, + CompilationSpecification compilationSpecification) { + this.parameters = parameters; + this.libraryDesugaringSpecification = libraryDesugaringSpecification; + this.compilationSpecification = compilationSpecification; + } + + @Test + public void test() throws Exception { + testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification) + .addProgramFiles(INPUT_JAR) + .addKeepMainRule(MAIN_CLASS) + .run(parameters.getRuntime(), MAIN_CLASS) + .assertSuccessWithOutput(EXPECTED_OUTPUT); + } +}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/specification/ConvertExportReadTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/specification/ConvertExportReadTest.java index 2a07cfb..a66e6b4 100644 --- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/specification/ConvertExportReadTest.java +++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/specification/ConvertExportReadTest.java
@@ -293,7 +293,8 @@ assertEquals( humanRewritingFlags1.getEmulatedInterfaces(), humanRewritingFlags2.getEmulatedInterfaces()); assertEquals( - humanRewritingFlags1.getRetargetMethod(), humanRewritingFlags2.getRetargetMethod()); + humanRewritingFlags1.getRetargetMethodToType(), + humanRewritingFlags2.getRetargetMethodToType()); assertEquals(humanRewritingFlags1.getDontRetarget(), humanRewritingFlags2.getDontRetarget()); assertEquals(
diff --git a/src/test/java/com/android/tools/r8/keepanno/asm/KeepEdgeAsmTest.java b/src/test/java/com/android/tools/r8/keepanno/asm/KeepEdgeAsmTest.java new file mode 100644 index 0000000..b9ebfa2 --- /dev/null +++ b/src/test/java/com/android/tools/r8/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.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.keepanno.ast.KeepEdge; +import com.android.tools.r8.keepanno.testsource.KeepClassAndDefaultConstructorSource; +import com.android.tools.r8.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/keepanno/ast/KeepEdgeAstTest.java similarity index 87% rename from src/test/java/com/android/tools/r8/experimental/keepanno/ast/KeepEdgeApiTest.java rename to src/test/java/com/android/tools/r8/keepanno/ast/KeepEdgeAstTest.java index d69d055..bcdb8ed 100644 --- a/src/test/java/com/android/tools/r8/experimental/keepanno/ast/KeepEdgeApiTest.java +++ b/src/test/java/com/android/tools/r8/keepanno/ast/KeepEdgeAstTest.java
@@ -1,22 +1,22 @@ // 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; +package com.android.tools.r8.keepanno.ast; 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.experimental.keepanno.ast.KeepOptions.KeepOption; -import com.android.tools.r8.experimental.keepanno.keeprules.KeepRuleExtractor; +import com.android.tools.r8.keepanno.ast.KeepOptions.KeepOption; +import com.android.tools.r8.keepanno.keeprules.KeepRuleExtractor; import com.android.tools.r8.utils.StringUtils; import org.junit.Test; import org.junit.runner.RunWith; 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/keepanno/processor/KeepEdgeProcessorTest.java b/src/test/java/com/android/tools/r8/keepanno/processor/KeepEdgeProcessorTest.java new file mode 100644 index 0000000..eb7b616 --- /dev/null +++ b/src/test/java/com/android/tools/r8/keepanno/processor/KeepEdgeProcessorTest.java
@@ -0,0 +1,101 @@ +// 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.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.keepanno.annotations.KeepConstants.Edge; +import com.android.tools.r8.keepanno.asm.KeepEdgeReader; +import com.android.tools.r8.keepanno.ast.KeepEdge; +import com.android.tools.r8.keepanno.testsource.KeepClassAndDefaultConstructorSource; +import com.android.tools.r8.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 Path KEEP_ANNO_PATH = + Paths.get(ToolHelper.BUILD_DIR, "classes", "java", "keepanno"); + 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(KEEP_ANNO_PATH) + .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(KEEP_ANNO_PATH) + .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/keepanno/testsource/KeepClassAndDefaultConstructorSource.java b/src/test/java/com/android/tools/r8/keepanno/testsource/KeepClassAndDefaultConstructorSource.java new file mode 100644 index 0000000..ca83a46 --- /dev/null +++ b/src/test/java/com/android/tools/r8/keepanno/testsource/KeepClassAndDefaultConstructorSource.java
@@ -0,0 +1,32 @@ +// 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.keepanno.testsource; + +import com.android.tools.r8.keepanno.annotations.KeepEdge; +import com.android.tools.r8.keepanno.annotations.KeepTarget; + +@KeepEdge( + consequences = { + // Keep the class to allow lookup of it. + @KeepTarget(classConstant = KeepClassAndDefaultConstructorSource.class), + // 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/keepanno/testsource/KeepSourceEdges.java b/src/test/java/com/android/tools/r8/keepanno/testsource/KeepSourceEdges.java new file mode 100644 index 0000000..187a019 --- /dev/null +++ b/src/test/java/com/android/tools/r8/keepanno/testsource/KeepSourceEdges.java
@@ -0,0 +1,54 @@ +// 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.keepanno.testsource; + +import com.android.tools.r8.keepanno.ast.KeepConsequences; +import com.android.tools.r8.keepanno.ast.KeepEdge; +import com.android.tools.r8.keepanno.ast.KeepItemPattern; +import com.android.tools.r8.keepanno.ast.KeepMembersPattern; +import com.android.tools.r8.keepanno.ast.KeepMethodNamePattern; +import com.android.tools.r8.keepanno.ast.KeepMethodPattern; +import com.android.tools.r8.keepanno.ast.KeepQualifiedClassNamePattern; +import com.android.tools.r8.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; + // Build the class target. + KeepQualifiedClassNamePattern name = KeepQualifiedClassNamePattern.exact(clazz.getTypeName()); + KeepItemPattern classItem = KeepItemPattern.builder().setClassPattern(name).build(); + KeepTarget classTarget = KeepTarget.builder().setItem(classItem).build(); + // Build the constructor target. + KeepMethodPattern constructorMethod = + KeepMethodPattern.builder().setNamePattern(KeepMethodNamePattern.exact("<init>")).build(); + KeepMembersPattern constructorMembers = + KeepMembersPattern.builder().addMethodPattern(constructorMethod).build(); + KeepItemPattern constructorItem = + KeepItemPattern.builder() + .setClassPattern(name) + .setMembersPattern(constructorMembers) + .build(); + KeepTarget constructorTarget = KeepTarget.builder().setItem(constructorItem).build(); + // The consequet set is the class an its constructor. + KeepConsequences consequences = + KeepConsequences.builder().addTarget(classTarget).addTarget(constructorTarget).build(); + KeepEdge edge = KeepEdge.builder().setConsequences(consequences).build(); + return Collections.singleton(edge); + } +}
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingRemoveInterfaceBridgeTest.java b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingRemoveInterfaceBridgeTest.java index d569899..6d96680 100644 --- a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingRemoveInterfaceBridgeTest.java +++ b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingRemoveInterfaceBridgeTest.java
@@ -5,9 +5,8 @@ package com.android.tools.r8.memberrebinding; import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; -import static com.android.tools.r8.utils.codeinspector.Matchers.isSynthetic; import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import com.android.tools.r8.NeverInline; import com.android.tools.r8.NoHorizontalClassMerging; @@ -16,7 +15,6 @@ import com.android.tools.r8.TestParametersCollection; import com.android.tools.r8.utils.DescriptorUtils; import com.android.tools.r8.utils.codeinspector.ClassSubject; -import com.android.tools.r8.utils.codeinspector.FoundMethodSubject; import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector; import org.junit.Test; import org.junit.runner.RunWith; @@ -58,10 +56,7 @@ inspector -> { ClassSubject clazz = inspector.clazz(J.class); assertThat(clazz, isPresent()); - // TODO(b/197490164): We should remove the bridge inserted here. - assertEquals(1, clazz.allMethods().size()); - FoundMethodSubject foundMethodSubject = clazz.allMethods().get(0); - assertThat(foundMethodSubject, isSynthetic()); + assertTrue(clazz.allMethods().isEmpty()); }); }
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingRemoveInterfaceBridgeWithSubTypeTest.java b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingRemoveInterfaceBridgeWithSubTypeTest.java index 4551dbe..dd2f0c1 100644 --- a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingRemoveInterfaceBridgeWithSubTypeTest.java +++ b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingRemoveInterfaceBridgeWithSubTypeTest.java
@@ -5,9 +5,8 @@ package com.android.tools.r8.memberrebinding; import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; -import static com.android.tools.r8.utils.codeinspector.Matchers.isSynthetic; import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import com.android.tools.r8.NeverInline; import com.android.tools.r8.NoHorizontalClassMerging; @@ -18,7 +17,6 @@ import com.android.tools.r8.TestParametersCollection; import com.android.tools.r8.utils.DescriptorUtils; import com.android.tools.r8.utils.codeinspector.ClassSubject; -import com.android.tools.r8.utils.codeinspector.FoundMethodSubject; import com.android.tools.r8.utils.codeinspector.HorizontallyMergedClassesInspector; import org.junit.Test; import org.junit.runner.RunWith; @@ -63,10 +61,7 @@ inspector -> { ClassSubject clazz = inspector.clazz(A.class); assertThat(clazz, isPresent()); - // TODO(b/197490164): We should remove the bridge inserted here. - assertEquals(1, clazz.virtualMethods().size()); - FoundMethodSubject foundMethodSubject = clazz.virtualMethods().get(0); - assertThat(foundMethodSubject, isSynthetic()); + assertTrue(clazz.virtualMethods().isEmpty()); }); }
diff --git a/src/test/java/com/android/tools/r8/shaking/b136195382/AbstractBridgeInheritTest.java b/src/test/java/com/android/tools/r8/shaking/b136195382/AbstractBridgeInheritTest.java index f1fc012..58faf90 100644 --- a/src/test/java/com/android/tools/r8/shaking/b136195382/AbstractBridgeInheritTest.java +++ b/src/test/java/com/android/tools/r8/shaking/b136195382/AbstractBridgeInheritTest.java
@@ -4,7 +4,6 @@ package com.android.tools.r8.shaking.b136195382; -import com.android.tools.r8.CompilationFailedException; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; @@ -13,8 +12,6 @@ import com.android.tools.r8.shaking.b136195382.package2.Main; import com.android.tools.r8.shaking.b136195382.package2.SubFactory; import com.android.tools.r8.shaking.b136195382.package2.SubService; -import java.io.IOException; -import java.util.concurrent.ExecutionException; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -35,8 +32,7 @@ } @Test - public void testRemovingBridge() - throws ExecutionException, CompilationFailedException, IOException { + public void testRemovingBridge() throws Exception { testForR8(parameters.getBackend()) .addProgramClasses( Service.class, Factory.class, SubService.class, SubFactory.class, Main.class) @@ -44,7 +40,6 @@ .enableInliningAnnotations() .setMinApi(parameters.getApiLevel()) .run(parameters.getRuntime(), Main.class) - .assertSuccessWithOutputLines("Hello World!") - .inspector(); + .assertSuccessWithOutputLines("Hello World!"); } }