Merge commit 'e3cdcb7d' into dev-release
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
index 24e49d1..5b6de6e 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepConstants.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepConstants.java
@@ -31,5 +31,6 @@
public static final String DESCRIPTOR = getDescriptor(CLASS);
public static final String classConstant = "classConstant";
public static final String methodName = "methodName";
+ public static final String fieldName = "fieldName";
}
}
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
index e20f288..f87e32c 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepTarget.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepTarget.java
@@ -16,4 +16,6 @@
String classTypeName() default "";
String methodName() default "";
+
+ String fieldName() 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
index 029ec75..cc6e4ee 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java
@@ -8,6 +8,8 @@
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.KeepFieldNamePattern;
+import com.android.tools.r8.keepanno.ast.KeepFieldPattern;
import com.android.tools.r8.keepanno.ast.KeepItemPattern;
import com.android.tools.r8.keepanno.ast.KeepItemPattern.Builder;
import com.android.tools.r8.keepanno.ast.KeepMethodNamePattern;
@@ -143,6 +145,7 @@
private final Parent<KeepTarget> parent;
private KeepQualifiedClassNamePattern classNamePattern = null;
private KeepMethodNamePattern methodName = null;
+ private KeepFieldNamePattern fieldName = null;
public KeepTargetVisitor(Parent<KeepTarget> parent) {
this.parent = parent;
@@ -158,6 +161,10 @@
methodName = KeepMethodNamePattern.exact((String) value);
return;
}
+ if (name.equals(Target.fieldName) && value instanceof String) {
+ fieldName = KeepFieldNamePattern.exact((String) value);
+ return;
+ }
super.visit(name, value);
}
@@ -167,10 +174,16 @@
if (classNamePattern != null) {
itemBuilder.setClassPattern(classNamePattern);
}
+ if (methodName != null && fieldName != null) {
+ throw new KeepEdgeException("Cannot define both a field and a method pattern.");
+ }
if (methodName != null) {
itemBuilder.setMemberPattern(
KeepMethodPattern.builder().setNamePattern(methodName).build());
}
+ if (fieldName != null) {
+ itemBuilder.setMemberPattern(KeepFieldPattern.builder().setNamePattern(fieldName).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
index 8307265..0a0ccc8 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeWriter.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeWriter.java
@@ -7,6 +7,9 @@
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.KeepFieldNamePattern.KeepFieldNameExactPattern;
+import com.android.tools.r8.keepanno.ast.KeepFieldPattern;
import com.android.tools.r8.keepanno.ast.KeepItemPattern;
import com.android.tools.r8.keepanno.ast.KeepMemberPattern;
import com.android.tools.r8.keepanno.ast.KeepMethodNamePattern.KeepMethodNameExactPattern;
@@ -84,7 +87,31 @@
if (memberPattern.isAll()) {
throw new Unimplemented();
}
- KeepMethodPattern method = memberPattern.asMethod();
+ if (memberPattern.isMethod()) {
+ writeMethod(memberPattern.asMethod(), targetVisitor);
+ } else if (memberPattern.isField()) {
+ writeField(memberPattern.asField(), targetVisitor);
+ } else {
+ throw new KeepEdgeException("Unexpected member pattern: " + memberPattern);
+ }
+ }
+
+ private void writeField(KeepFieldPattern field, AnnotationVisitor targetVisitor) {
+ KeepFieldNameExactPattern exactFieldName = field.getNamePattern().asExact();
+ if (exactFieldName != null) {
+ targetVisitor.visit(Target.fieldName, exactFieldName.getName());
+ } else {
+ throw new Unimplemented();
+ }
+ if (!field.getAccessPattern().isAny()) {
+ throw new Unimplemented();
+ }
+ if (!field.getTypePattern().isAny()) {
+ throw new Unimplemented();
+ }
+ }
+
+ private void writeMethod(KeepMethodPattern method, AnnotationVisitor targetVisitor) {
KeepMethodNameExactPattern exactMethodName = method.getNamePattern().asExact();
if (exactMethodName != null) {
targetVisitor.visit(Target.methodName, exactMethodName.getName());
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdge.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdge.java
index 89be76b..b2da25a 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdge.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepEdge.java
@@ -39,7 +39,12 @@
* UNQUALIFIED_CLASS_NAME_PATTERN ::= any | exact simple-class-name
* EXTENDS_PATTERN ::= any | QUALIFIED_CLASS_NAME_PATTERN
*
- * MEMBER_PATTERN ::= none | all | METHOD_PATTERN
+ * MEMBER_PATTERN ::= none | all | FIELD_PATTERN | METHOD_PATTERN
+ *
+ * FIELD_PATTERN
+ * ::= FIELD_ACCESS_PATTERN
+ * FIELD_TYPE_PATTERN
+ * FIELD_NAME_PATTERN;
*
* METHOD_PATTERN
* ::= METHOD_ACCESS_PATTERN
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepFieldAccessPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepFieldAccessPattern.java
new file mode 100644
index 0000000..056430b
--- /dev/null
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepFieldAccessPattern.java
@@ -0,0 +1,43 @@
+// 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 KeepFieldAccessPattern {
+
+ public static KeepFieldAccessPattern any() {
+ return Any.getInstance();
+ }
+
+ public abstract boolean isAny();
+
+ private static class Any extends KeepFieldAccessPattern {
+
+ private static final Any INSTANCE = new Any();
+
+ private static Any getInstance() {
+ 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/keepanno/java/com/android/tools/r8/keepanno/ast/KeepFieldNamePattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepFieldNamePattern.java
new file mode 100644
index 0000000..3067966
--- /dev/null
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepFieldNamePattern.java
@@ -0,0 +1,97 @@
+// 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;
+
+public abstract class KeepFieldNamePattern {
+
+ public static KeepFieldNamePattern any() {
+ return Any.getInstance();
+ }
+
+ public static KeepFieldNamePattern exact(String methodName) {
+ return new KeepFieldNameExactPattern(methodName);
+ }
+
+ private KeepFieldNamePattern() {}
+
+ public boolean isAny() {
+ return false;
+ }
+
+ public final boolean isExact() {
+ return asExact() != null;
+ }
+
+ public KeepFieldNameExactPattern asExact() {
+ return null;
+ }
+
+ private static class Any extends KeepFieldNamePattern {
+ private static final Any INSTANCE = new Any();
+
+ public static Any getInstance() {
+ 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 "*";
+ }
+ }
+
+ public static class KeepFieldNameExactPattern extends KeepFieldNamePattern {
+ private final String name;
+
+ public KeepFieldNameExactPattern(String name) {
+ assert name != null;
+ this.name = name;
+ }
+
+ @Override
+ public KeepFieldNameExactPattern asExact() {
+ return this;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ KeepFieldNameExactPattern that = (KeepFieldNameExactPattern) o;
+ return name.equals(that.name);
+ }
+
+ @Override
+ public int hashCode() {
+ return name.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
+ }
+}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepFieldPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepFieldPattern.java
new file mode 100644
index 0000000..7137033
--- /dev/null
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepFieldPattern.java
@@ -0,0 +1,116 @@
+// 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;
+
+public final class KeepFieldPattern extends KeepMemberPattern {
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+
+ private KeepFieldAccessPattern accessPattern = KeepFieldAccessPattern.any();
+ private KeepFieldNamePattern namePattern = null;
+ private KeepFieldTypePattern typePattern = KeepFieldTypePattern.any();
+
+ private Builder() {}
+
+ public Builder self() {
+ return this;
+ }
+
+ public Builder setAccessPattern(KeepFieldAccessPattern accessPattern) {
+ this.accessPattern = accessPattern;
+ return self();
+ }
+
+ public Builder setNamePattern(KeepFieldNamePattern namePattern) {
+ this.namePattern = namePattern;
+ return self();
+ }
+
+ public Builder setTypePattern(KeepFieldTypePattern typePattern) {
+ this.typePattern = typePattern;
+ return self();
+ }
+
+ public KeepFieldPattern build() {
+ if (namePattern == null) {
+ throw new KeepEdgeException("Field pattern must declare a name pattern");
+ }
+ return new KeepFieldPattern(accessPattern, namePattern, typePattern);
+ }
+ }
+
+ private final KeepFieldAccessPattern accessPattern;
+ private final KeepFieldNamePattern namePattern;
+ private final KeepFieldTypePattern typePattern;
+
+ private KeepFieldPattern(
+ KeepFieldAccessPattern accessPattern,
+ KeepFieldNamePattern namePattern,
+ KeepFieldTypePattern typePattern) {
+ assert accessPattern != null;
+ assert namePattern != null;
+ assert typePattern != null;
+ this.accessPattern = accessPattern;
+ this.namePattern = namePattern;
+ this.typePattern = typePattern;
+ }
+
+ @Override
+ public KeepFieldPattern asField() {
+ return this;
+ }
+
+ public boolean isAnyField() {
+ return accessPattern.isAny() && namePattern.isAny() && typePattern.isAny();
+ }
+
+ public KeepFieldAccessPattern getAccessPattern() {
+ return accessPattern;
+ }
+
+ public KeepFieldNamePattern getNamePattern() {
+ return namePattern;
+ }
+
+ public KeepFieldTypePattern getTypePattern() {
+ return typePattern;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ KeepFieldPattern that = (KeepFieldPattern) o;
+ return accessPattern.equals(that.accessPattern)
+ && namePattern.equals(that.namePattern)
+ && typePattern.equals(that.typePattern);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(accessPattern, namePattern, typePattern);
+ }
+
+ @Override
+ public String toString() {
+ return "KeepFieldPattern{"
+ + "access="
+ + accessPattern
+ + ", name="
+ + namePattern
+ + ", type="
+ + typePattern
+ + '}';
+ }
+}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepFieldTypePattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepFieldTypePattern.java
new file mode 100644
index 0000000..beaaa55
--- /dev/null
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepFieldTypePattern.java
@@ -0,0 +1,62 @@
+// 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;
+
+public abstract class KeepFieldTypePattern {
+
+ public static KeepFieldTypePattern any() {
+ return SomeType.ANY_TYPE_INSTANCE;
+ }
+
+ public boolean isAny() {
+ return isType() && asType().isAny();
+ }
+
+ public boolean isType() {
+ return asType() != null;
+ }
+
+ public KeepTypePattern asType() {
+ return null;
+ }
+
+ private static class SomeType extends KeepFieldTypePattern {
+
+ private static final SomeType ANY_TYPE_INSTANCE = new SomeType(KeepTypePattern.any());
+
+ private final KeepTypePattern typePattern;
+
+ private SomeType(KeepTypePattern typePattern) {
+ assert typePattern != null;
+ this.typePattern = typePattern;
+ }
+
+ @Override
+ public KeepTypePattern asType() {
+ return 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/KeepMemberPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMemberPattern.java
index 25a57ab..b0f205e 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMemberPattern.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMemberPattern.java
@@ -88,4 +88,12 @@
public KeepMethodPattern asMethod() {
return null;
}
+
+ public final boolean isField() {
+ return asField() != null;
+ }
+
+ public KeepFieldPattern asField() {
+ return null;
+ }
}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepRuleExtractor.java b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepRuleExtractor.java
index 7c8c032..6781a22 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepRuleExtractor.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/KeepRuleExtractor.java
@@ -5,6 +5,9 @@
import com.android.tools.r8.keepanno.ast.KeepConsequences;
import com.android.tools.r8.keepanno.ast.KeepEdge;
+import com.android.tools.r8.keepanno.ast.KeepFieldAccessPattern;
+import com.android.tools.r8.keepanno.ast.KeepFieldNamePattern;
+import com.android.tools.r8.keepanno.ast.KeepFieldPattern;
import com.android.tools.r8.keepanno.ast.KeepItemPattern;
import com.android.tools.r8.keepanno.ast.KeepMemberPattern;
import com.android.tools.r8.keepanno.ast.KeepMethodAccessPattern;
@@ -114,9 +117,25 @@
printMethod(builder.append(' '), member.asMethod());
return builder.append(" }");
}
+ if (member.isField()) {
+ builder.append(" {");
+ printField(builder.append(' '), member.asField());
+ return builder.append(" }");
+ }
throw new Unimplemented();
}
+ private static StringBuilder printField(StringBuilder builder, KeepFieldPattern fieldPattern) {
+ if (fieldPattern.isAnyField()) {
+ return builder.append("<fields>;");
+ }
+ printAccess(builder, " ", fieldPattern.getAccessPattern());
+ printType(builder, fieldPattern.getTypePattern().asType());
+ builder.append(' ');
+ printFieldName(builder, fieldPattern.getNamePattern());
+ return builder.append(';');
+ }
+
private static StringBuilder printMethod(StringBuilder builder, KeepMethodPattern methodPattern) {
if (methodPattern.isAnyMethod()) {
return builder.append("<methods>;");
@@ -143,6 +162,13 @@
.append(')');
}
+ private static StringBuilder printFieldName(
+ StringBuilder builder, KeepFieldNamePattern namePattern) {
+ return namePattern.isAny()
+ ? builder.append("*")
+ : builder.append(namePattern.asExact().getName());
+ }
+
private static StringBuilder printMethodName(
StringBuilder builder, KeepMethodNamePattern namePattern) {
return namePattern.isAny()
@@ -175,6 +201,16 @@
throw new Unimplemented();
}
+ private static StringBuilder printAccess(
+ StringBuilder builder, String indent, KeepFieldAccessPattern accessPattern) {
+ if (accessPattern.isAny()) {
+ // No text will match any access pattern.
+ // Don't print the indent in this case.
+ return builder;
+ }
+ throw new Unimplemented();
+ }
+
private static StringBuilder printClassName(
StringBuilder builder, KeepQualifiedClassNamePattern classNamePattern) {
if (classNamePattern.isAny()) {
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
index c86857d..18d3932 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/processor/KeepEdgeProcessor.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/processor/KeepEdgeProcessor.java
@@ -16,6 +16,8 @@
import com.android.tools.r8.keepanno.ast.KeepEdge;
import com.android.tools.r8.keepanno.ast.KeepEdge.Builder;
import com.android.tools.r8.keepanno.ast.KeepEdgeException;
+import com.android.tools.r8.keepanno.ast.KeepFieldNamePattern;
+import com.android.tools.r8.keepanno.ast.KeepFieldPattern;
import com.android.tools.r8.keepanno.ast.KeepItemPattern;
import com.android.tools.r8.keepanno.ast.KeepMethodNamePattern;
import com.android.tools.r8.keepanno.ast.KeepMethodPattern;
@@ -148,14 +150,21 @@
itemBuilder.setClassPattern(KeepQualifiedClassNamePattern.exact(typeName));
}
AnnotationValue methodNameValue = getAnnotationValue(mirror, Target.methodName);
+ AnnotationValue fieldNameValue = getAnnotationValue(mirror, Target.fieldName);
+ if (methodNameValue != null && fieldNameValue != null) {
+ throw new KeepEdgeException("Cannot define both a method and a field name pattern");
+ }
if (methodNameValue != null) {
String methodName = AnnotationStringValueVisitor.getString(methodNameValue);
itemBuilder.setMemberPattern(
KeepMethodPattern.builder()
.setNamePattern(KeepMethodNamePattern.exact(methodName))
.build());
+ } else if (fieldNameValue != null) {
+ String fieldName = AnnotationStringValueVisitor.getString(fieldNameValue);
+ itemBuilder.setMemberPattern(
+ KeepFieldPattern.builder().setNamePattern(KeepFieldNamePattern.exact(fieldName)).build());
}
-
builder.setItem(itemBuilder.build());
}
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs_nio.json b/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
index 3295333..dcd5329 100644
--- a/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
+++ b/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
@@ -616,6 +616,7 @@
"-keepattributes EnclosingMethod",
"-keepattributes InnerClasses",
"-dontwarn sun.misc.Unsafe",
- "-dontwarn wrapper.**"
+ "-dontwarn wrapper.**",
+ "-dontwarn sun.nio.fs.UnixFileSystemProvider"
]
}
diff --git a/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java b/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java
index 4649132..4864e0b 100644
--- a/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java
+++ b/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java
@@ -233,7 +233,8 @@
// We cannot reliably create a stub that will have the same throwing
// behavior for all VMs. We only create stubs for exceptions to allow them being present in
// catch handlers. See b/b/258270051 for more information.
- if (!isThrowable(libraryClass)) {
+ if (!isThrowable(libraryClass)
+ || appView.options().apiModelingOptions().stubNonThrowableClasses) {
return;
}
if (appView
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index 63d7d65..43e7099 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -397,6 +397,23 @@
return true;
}
+ private static class PrunePreambleMethodVisitor extends MethodVisitor {
+
+ private boolean inPreamble = true;
+
+ public PrunePreambleMethodVisitor(MethodVisitor methodVisitor) {
+ super(InternalOptions.ASM_VERSION, methodVisitor);
+ }
+
+ @Override
+ public void visitLineNumber(int line, Label start) {
+ if (!inPreamble || line != 0) {
+ inPreamble = false;
+ super.visitLineNumber(line, start);
+ }
+ }
+ }
+
@Override
public void writeCf(
ProgramMethod method,
@@ -422,12 +439,20 @@
|| (appView.enableWholeProgramOptimizations()
&& classFileVersion.isEqualTo(CfVersion.V1_6)
&& !options.shouldKeepStackMapTable());
+ PrunePreambleMethodVisitor prunePreambleVisitor = new PrunePreambleMethodVisitor(visitor);
for (CfInstruction instruction : instructions) {
if (discardFrames && instruction instanceof CfFrame) {
continue;
}
instruction.write(
- appView, method, dexItemFactory, graphLens, initClassLens, namingLens, rewriter, visitor);
+ appView,
+ method,
+ dexItemFactory,
+ graphLens,
+ initClassLens,
+ namingLens,
+ rewriter,
+ prunePreambleVisitor);
}
visitor.visitEnd();
visitor.visitMaxs(maxStack, maxLocals);
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/equivalence/BasicBlockBehavioralSubsumption.java b/src/main/java/com/android/tools/r8/ir/analysis/equivalence/BasicBlockBehavioralSubsumption.java
index e901e23..d274600 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/equivalence/BasicBlockBehavioralSubsumption.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/equivalence/BasicBlockBehavioralSubsumption.java
@@ -4,12 +4,10 @@
package com.android.tools.r8.ir.analysis.equivalence;
-import static com.google.common.base.Predicates.not;
-import static com.google.common.base.Predicates.or;
-
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.code.Assume;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.ConstClass;
import com.android.tools.r8.ir.code.ConstNumber;
@@ -23,8 +21,10 @@
import com.android.tools.r8.ir.code.Return;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.utils.SetUtils;
+import com.android.tools.r8.utils.WorkList;
import java.util.List;
import java.util.Set;
+import java.util.function.Predicate;
/**
* Analysis that can be used to determine if the behavior of one basic block is subsumed by the
@@ -45,19 +45,59 @@
this.context = code.context();
}
- public boolean isSubsumedBy(BasicBlock block, BasicBlock other) {
- return isSubsumedBy(block.iterator(), other.iterator(), null);
+ public boolean isSubsumedBy(Value conditionValue, BasicBlock block, BasicBlock other) {
+ return isSubsumedBy(conditionValue, block.iterator(), other.iterator(), null);
+ }
+
+ private boolean dependsOnConditionValue(Value conditionValue, Instruction instruction) {
+ if (instruction.isAssume()) {
+ // Assume instructions are just virtual alias instructions so they are not materializing uses.
+ return false;
+ }
+ WorkList<Assume> assumptionUses = null;
+ for (Value value : instruction.inValues()) {
+ Assume assumption = value.isPhi() ? null : value.getDefinition().asAssume();
+ if (assumption != null) {
+ if (assumptionUses == null) {
+ assumptionUses = WorkList.newIdentityWorkList();
+ }
+ assumptionUses.addIfNotSeen(assumption);
+ }
+ }
+ if (assumptionUses != null) {
+ while (assumptionUses.hasNext()) {
+ Assume next = assumptionUses.next();
+ for (Value assumptionArgument : next.inValues()) {
+ if (assumptionArgument == conditionValue) {
+ return true;
+ }
+ Assume indirectAssumption =
+ assumptionArgument.isPhi() ? null : assumptionArgument.getDefinition().asAssume();
+ if (indirectAssumption != null) {
+ assumptionUses.addIfNotSeen(indirectAssumption);
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ private Instruction skipNonDependentInstructionsUntil(
+ InstructionIterator iterator, Value conditionValue, Predicate<Instruction> predicate) {
+ return iterator.nextUntil(
+ predicate.or(i -> i.isJumpInstruction() || dependsOnConditionValue(conditionValue, i)));
}
private boolean isSubsumedBy(
- InstructionIterator iterator, InstructionIterator otherIterator, Set<BasicBlock> visited) {
+ Value conditionValue,
+ InstructionIterator iterator,
+ InstructionIterator otherIterator,
+ Set<BasicBlock> visited) {
// Skip over block-local instructions (i.e., instructions that define values that are not used
// outside the block itself) that do not have side effects.
Instruction instruction =
- iterator.nextUntil(
- or(
- not(this::isBlockLocalInstructionWithoutSideEffects),
- Instruction::isJumpInstruction));
+ skipNonDependentInstructionsUntil(
+ iterator, conditionValue, this::isNonLocalDefinitionOrSideEffecting);
// If the instruction defines a value with non-local usages, then we would need a dominator
// analysis to verify that all these non-local usages can in fact be replaced by a value
@@ -68,8 +108,8 @@
// Skip over non-throwing instructions in the other block.
Instruction otherInstruction =
- otherIterator.nextUntil(
- or(this::instructionMayHaveSideEffects, Instruction::isJumpInstruction));
+ skipNonDependentInstructionsUntil(
+ otherIterator, conditionValue, this::instructionMayHaveSideEffects);
assert otherInstruction != null;
if (instruction.isGoto()) {
@@ -101,7 +141,7 @@
visited = SetUtils.newIdentityHashSet(instruction.getBlock());
}
if (visited.add(targetBlock)) {
- return isSubsumedBy(targetBlock.iterator(), otherIterator, visited);
+ return isSubsumedBy(conditionValue, targetBlock.iterator(), otherIterator, visited);
}
// Guard against cycles in the control flow graph.
return false;
@@ -125,8 +165,8 @@
otherIterator = targetBlock.iterator();
otherInstruction =
- otherIterator.nextUntil(
- or(this::instructionMayHaveSideEffects, Instruction::isJumpInstruction));
+ skipNonDependentInstructionsUntil(
+ otherIterator, conditionValue, this::instructionMayHaveSideEffects);
// If following the first goto instruction leads to another goto instruction, then we need to
// keep track of the set of visited blocks to guard against cycles in the control flow graph.
@@ -150,7 +190,7 @@
|| otherSingleTarget.getDefinition().getOptimizationInfo().mayHaveSideEffects()) {
return false;
}
- return isSubsumedBy(iterator, otherIterator, visited);
+ return isSubsumedBy(conditionValue, iterator, otherIterator, visited);
}
return false;
}
@@ -172,8 +212,8 @@
return false;
}
- private boolean isBlockLocalInstructionWithoutSideEffects(Instruction instruction) {
- return definesBlockLocalValue(instruction) && !instructionMayHaveSideEffects(instruction);
+ private boolean isNonLocalDefinitionOrSideEffecting(Instruction instruction) {
+ return !definesBlockLocalValue(instruction) || instructionMayHaveSideEffects(instruction);
}
private boolean definesBlockLocalValue(Instruction instruction) {
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 1d4e435..f0ca38f 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
@@ -1135,7 +1135,8 @@
// behavior of the default block, or if the switch case is unreachable.
if (switchCaseAnalyzer.switchCaseIsUnreachable(theSwitch, switchAbstractValue, i)) {
eliminator.markSwitchCaseForRemoval(i);
- } else if (behavioralSubsumption.isSubsumedBy(targetBlock, defaultTarget)) {
+ } else if (behavioralSubsumption.isSubsumedBy(
+ theSwitch.value(), targetBlock, defaultTarget)) {
eliminator.markSwitchCaseForRemoval(i);
hasSwitchCaseToDefaultRewrite = true;
}
@@ -2642,7 +2643,8 @@
// Unable to determine which branch will be taken. Check if the true target can safely be
// rewritten to the false target.
- if (behavioralSubsumption.isSubsumedBy(theIf.getTrueTarget(), theIf.fallthroughBlock())) {
+ if (behavioralSubsumption.isSubsumedBy(
+ theIf.inValues().get(0), theIf.getTrueTarget(), theIf.fallthroughBlock())) {
simplifyIfWithKnownCondition(code, block, theIf, theIf.fallthroughBlock());
simplified = true;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendOptimizer.java
index 407717c..5f0bea9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendOptimizer.java
@@ -608,7 +608,7 @@
}
@Override
- protected List<Void> getFinalStateForRoots(Collection<StringBuilderNode> roots) {
+ protected List<Void> getFinalStateForRoots(Collection<? extends StringBuilderNode> roots) {
return null;
}
diff --git a/src/main/java/com/android/tools/r8/optimize/proto/ProtoNormalizer.java b/src/main/java/com/android/tools/r8/optimize/proto/ProtoNormalizer.java
index a3f2d38..3c032b3 100644
--- a/src/main/java/com/android/tools/r8/optimize/proto/ProtoNormalizer.java
+++ b/src/main/java/com/android/tools/r8/optimize/proto/ProtoNormalizer.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexMethodSignature;
@@ -15,31 +16,35 @@
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexTypeList;
import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
-import com.android.tools.r8.graph.ObjectAllocationInfoCollection;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.shaking.KeepMethodInfo;
+import com.android.tools.r8.utils.DepthFirstSearchWorkListBase.StatefulDepthFirstSearchWorkList;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.IterableUtils;
import com.android.tools.r8.utils.MapUtils;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.TraversalContinuation;
import com.android.tools.r8.utils.WorkList;
import com.android.tools.r8.utils.collections.BidirectionalOneToOneHashMap;
import com.android.tools.r8.utils.collections.DexMethodSignatureSet;
import com.android.tools.r8.utils.collections.MutableBidirectionalOneToOneMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
+import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
+import java.util.function.Function;
public class ProtoNormalizer {
@@ -60,47 +65,93 @@
private void run(ExecutorService executorService) throws ExecutionException {
GlobalReservationState globalReservationState = computeGlobalReservationState(executorService);
- // TODO(b/173398086): This uses a single LocalReservationState for the entire program. This
- // should process the strongly connected program components in parallel, each with their own
- // LocalReservationState.
- LocalReservationState localReservationState = new LocalReservationState();
+ // To ensure we do not add collisions of method signatures when creating the new method
+ // signatures we keep a map of methods in the type hierarchy, similar to what we do for
+ // minification.
ProtoNormalizerGraphLens.Builder lensBuilder = ProtoNormalizerGraphLens.builder(appView);
- for (DexProgramClass clazz : appView.appInfo().classesWithDeterministicOrder()) {
- Map<DexMethodSignature, DexMethodSignature> newInstanceInitializerSignatures =
- computeNewInstanceInitializerSignatures(
- clazz, localReservationState, globalReservationState);
- clazz
- .getMethodCollection()
- .replaceMethods(
- method -> {
- DexMethodSignature methodSignature = method.getSignature();
- DexMethodSignature newMethodSignature =
- method.isInstanceInitializer()
- ? newInstanceInitializerSignatures.get(methodSignature)
- : localReservationState.getAndReserveNewMethodSignature(
- methodSignature, dexItemFactory, globalReservationState);
- if (methodSignature.equals(newMethodSignature)) {
- return method;
- }
- DexMethod newMethodReference = newMethodSignature.withHolder(clazz, dexItemFactory);
- RewrittenPrototypeDescription prototypeChanges =
- lensBuilder.recordNewMethodSignature(method, newMethodReference);
- // TODO(b/195112263): Assert that the method does not have any optimization info.
- // If/when enabling proto normalization after the final round of tree shaking, this
- // should simply clear the optimization info, or replace it by a
- // ThrowingMethodOptimizationInfo since we should never use the optimization info
- // after this point.
- return method.toTypeSubstitutedMethod(
- newMethodReference,
- builder -> {
- if (!prototypeChanges.isEmpty()) {
- builder
- .apply(prototypeChanges.createParameterAnnotationsRemover(method))
- .setGenericSignature(MethodTypeSignature.noSignature());
- }
- });
- });
- }
+ new StatefulDepthFirstSearchWorkList<DexClass, LocalReservationState, Void>() {
+
+ @Override
+ @SuppressWarnings("ReturnValueIgnored")
+ protected TraversalContinuation<Void, LocalReservationState> process(
+ DFSNodeWithState<DexClass, LocalReservationState> node,
+ Function<DexClass, DFSNodeWithState<DexClass, LocalReservationState>> childNodeConsumer) {
+ DexClass clazz = node.getNode();
+ node.setState(new LocalReservationState());
+ if (clazz.getSuperType() != null) {
+ appView
+ .contextIndependentDefinitionForWithResolutionResult(clazz.getSuperType())
+ .forEachClassResolutionResult(childNodeConsumer::apply);
+ }
+ return TraversalContinuation.doContinue();
+ }
+
+ @Override
+ protected TraversalContinuation<Void, LocalReservationState> joiner(
+ DFSNodeWithState<DexClass, LocalReservationState> node,
+ List<DFSNodeWithState<DexClass, LocalReservationState>> childStates) {
+ DexClass clazz = node.getNode();
+ assert childStates.size() >= 1
+ || clazz.getType() == dexItemFactory.objectType
+ || clazz.hasMissingSuperType(appView.appInfo());
+ // We can have multiple child states if there are multiple definitions of a class.
+ assert childStates.size() <= 1 || options.loadAllClassDefinitions;
+ LocalReservationState localReservationState = node.getState();
+ if (!childStates.isEmpty()) {
+ localReservationState.linkParent(childStates.get(0).getState());
+ }
+ Map<DexMethodSignature, DexMethodSignature> newInstanceInitializerSignatures =
+ clazz.isProgramClass()
+ ? computeNewInstanceInitializerSignatures(
+ clazz.asProgramClass(), localReservationState, globalReservationState)
+ : null;
+ clazz
+ .getMethodCollection()
+ .replaceMethods(
+ method -> {
+ DexMethodSignature methodSignature = method.getSignature();
+ if (!clazz.isProgramClass()) {
+ // We cannot change the signature of the method. Record it to ensure we do not
+ // change a program signature in a sub class to override this.
+ localReservationState.recordNoSignatureChange(methodSignature, dexItemFactory);
+ return method;
+ } else {
+ assert newInstanceInitializerSignatures != null;
+ DexMethodSignature newMethodSignature =
+ method.isInstanceInitializer()
+ ? newInstanceInitializerSignatures.get(methodSignature)
+ : localReservationState.getAndReserveNewMethodSignature(
+ methodSignature, dexItemFactory, globalReservationState);
+ if (methodSignature.equals(newMethodSignature)) {
+ // This method could not be optimized, record it as identity mapping to ensure
+ // we keep it going forward.
+ localReservationState.recordNoSignatureChange(
+ methodSignature, dexItemFactory);
+ return method;
+ }
+ DexMethod newMethodReference =
+ newMethodSignature.withHolder(clazz.asProgramClass(), dexItemFactory);
+ RewrittenPrototypeDescription prototypeChanges =
+ lensBuilder.recordNewMethodSignature(method, newMethodReference);
+ // TODO(b/195112263): Assert that the method does not have optimization info.
+ // If/when enabling proto normalization after the final round of tree shaking,
+ // this should simply clear the optimization info, or replace it by a
+ // ThrowingMethodOptimizationInfo since we should never use the optimization
+ // info after this point.
+ return method.toTypeSubstitutedMethod(
+ newMethodReference,
+ builder -> {
+ if (!prototypeChanges.isEmpty()) {
+ builder
+ .apply(prototypeChanges.createParameterAnnotationsRemover(method))
+ .setGenericSignature(MethodTypeSignature.noSignature());
+ }
+ });
+ }
+ });
+ return TraversalContinuation.doContinue(localReservationState);
+ }
+ }.run(appView.appInfo().classesWithDeterministicOrder());
if (!lensBuilder.isEmpty()) {
appView.rewriteWithLens(lensBuilder.build());
@@ -331,15 +382,13 @@
if (appInfo.isBootstrapMethod(method)) {
return true;
}
- ObjectAllocationInfoCollection objectAllocationInfoCollection =
- appInfo.getObjectAllocationInfoCollection();
- if (method.getHolder().isInterface()
+ // As long as we do not consider interface method and overrides as optimizable we can change
+ // method signatures in a top-down traversal.
+ return method.getHolder().isInterface()
&& method.getDefinition().isAbstract()
- && objectAllocationInfoCollection.isImmediateInterfaceOfInstantiatedLambda(
- method.getHolder())) {
- return true;
- }
- return false;
+ && appInfo
+ .getObjectAllocationInfoCollection()
+ .isImmediateInterfaceOfInstantiatedLambda(method.getHolder());
}
static class GlobalReservationState {
@@ -389,8 +438,10 @@
static class LocalReservationState {
- MutableBidirectionalOneToOneMap<DexMethodSignature, DexMethodSignature> newMethodSignatures =
- new BidirectionalOneToOneHashMap<>();
+ private final List<LocalReservationState> parents = new ArrayList<>(2);
+
+ private final MutableBidirectionalOneToOneMap<DexMethodSignature, DexMethodSignature>
+ newMethodSignatures = new BidirectionalOneToOneHashMap<>();
DexMethodSignature getNewMethodSignature(
DexMethodSignature methodSignature,
@@ -414,33 +465,68 @@
GlobalReservationState globalReservationState,
boolean reserve) {
if (globalReservationState.isUnoptimizable(methodSignature)) {
- assert !newMethodSignatures.containsKey(methodSignature);
+ assert getReserved(methodSignature) == null
+ || methodSignature.equals(getReserved(methodSignature));
return methodSignature;
}
- DexMethodSignature reservedSignature = newMethodSignatures.get(methodSignature);
+ DexMethodSignature reservedSignature = getReserved(methodSignature);
if (reservedSignature != null) {
- assert reservedSignature
- .getParameters()
- .equals(globalReservationState.getReservedParameters(methodSignature));
return reservedSignature;
}
DexTypeList reservedParameters =
globalReservationState.getReservedParameters(methodSignature);
DexMethodSignature newMethodSignature =
methodSignature.withParameters(reservedParameters, dexItemFactory);
- if (newMethodSignatures.containsValue(newMethodSignature)) {
+ if (isDestinationTaken(newMethodSignature)) {
int index = 1;
String newMethodBaseName = methodSignature.getName().toString();
do {
DexString newMethodName = dexItemFactory.createString(newMethodBaseName + "$" + index);
newMethodSignature = newMethodSignature.withName(newMethodName);
index++;
- } while (newMethodSignatures.containsValue(newMethodSignature));
+ } while (isDestinationTaken(newMethodSignature));
}
if (reserve) {
newMethodSignatures.put(methodSignature, newMethodSignature);
}
return newMethodSignature;
}
+
+ private void linkParent(LocalReservationState parent) {
+ this.parents.add(parent);
+ }
+
+ private DexMethodSignature getReserved(DexMethodSignature signature) {
+ WorkList<LocalReservationState> workList = WorkList.newIdentityWorkList(this);
+ while (workList.hasNext()) {
+ LocalReservationState localReservationState = workList.next();
+ DexMethodSignature reservedSignature =
+ localReservationState.newMethodSignatures.get(signature);
+ if (reservedSignature != null) {
+ return reservedSignature;
+ }
+ workList.addIfNotSeen(localReservationState.parents);
+ }
+ return null;
+ }
+
+ private boolean isDestinationTaken(DexMethodSignature signature) {
+ WorkList<LocalReservationState> workList = WorkList.newIdentityWorkList(this);
+ while (workList.hasNext()) {
+ LocalReservationState localReservationState = workList.next();
+ if (localReservationState.newMethodSignatures.containsValue(signature)) {
+ return true;
+ }
+ workList.addIfNotSeen(localReservationState.parents);
+ }
+ return false;
+ }
+
+ public void recordNoSignatureChange(
+ DexMethodSignature methodSignature, DexItemFactory factory) {
+ if (!methodSignature.getName().equals(factory.constructorMethodName)) {
+ newMethodSignatures.put(methodSignature, methodSignature);
+ }
+ }
}
}
diff --git a/src/main/java/com/android/tools/r8/utils/DepthFirstSearchWorkListBase.java b/src/main/java/com/android/tools/r8/utils/DepthFirstSearchWorkListBase.java
index 37a55bb..a4d1c8f 100644
--- a/src/main/java/com/android/tools/r8/utils/DepthFirstSearchWorkListBase.java
+++ b/src/main/java/com/android/tools/r8/utils/DepthFirstSearchWorkListBase.java
@@ -115,7 +115,7 @@
/** The joining of state during backtracking of the algorithm. */
abstract TraversalContinuation<TB, TC> internalOnJoin(T node);
- protected abstract List<TC> getFinalStateForRoots(Collection<N> roots);
+ protected abstract List<TC> getFinalStateForRoots(Collection<? extends N> roots);
final T internalEnqueueNode(N value) {
T dfsNode = nodeToNodeWithStateMap.computeIfAbsent(value, this::createDfsNode);
@@ -138,7 +138,7 @@
return run(Arrays.asList(roots));
}
- public final TraversalContinuation<TB, List<TC>> run(Collection<N> roots) {
+ public final TraversalContinuation<TB, List<TC>> run(Collection<? extends N> roots) {
roots.forEach(this::internalEnqueueNode);
while (!workList.isEmpty()) {
T node = workList.removeLast();
@@ -199,7 +199,7 @@
}
@Override
- protected List<TC> getFinalStateForRoots(Collection<N> roots) {
+ protected List<TC> getFinalStateForRoots(Collection<? extends N> roots) {
return null;
}
}
@@ -264,7 +264,7 @@
}
@Override
- public List<S> getFinalStateForRoots(Collection<N> roots) {
+ public List<S> getFinalStateForRoots(Collection<? extends N> roots) {
return ListUtils.map(roots, root -> getNodeStateForNode(root).state);
}
}
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 98d5a27..09641c3 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1805,6 +1805,9 @@
System.getProperty("com.android.tools.r8.disableApiModeling") == null;
public boolean reportUnknownApiReferences =
System.getProperty("com.android.tools.r8.reportUnknownApiReferences") != null;
+ // TODO(b/259076765): Remove when resolved.
+ public boolean stubNonThrowableClasses =
+ System.getProperty("com.android.tools.r8.stubNonThrowableClasses") != null;
// TODO(b/232823652): Enable when we can compute the offset correctly.
public boolean useMemoryMappedByteBuffer = false;
diff --git a/src/main/java/com/android/tools/r8/utils/SetUtils.java b/src/main/java/com/android/tools/r8/utils/SetUtils.java
index 6be9cc3..834a3df 100644
--- a/src/main/java/com/android/tools/r8/utils/SetUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/SetUtils.java
@@ -16,6 +16,9 @@
public class SetUtils {
public static <T> boolean containsAnyOf(Set<T> set, Iterable<T> elements) {
+ if (set.isEmpty()) {
+ return false;
+ }
for (T element : elements) {
if (set.contains(element)) {
return true;
diff --git a/src/test/java/com/android/tools/r8/cf/CompanionClassPreamblePositionTest.java b/src/test/java/com/android/tools/r8/cf/CompanionClassPreamblePositionTest.java
new file mode 100644
index 0000000..49d7ef6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/CompanionClassPreamblePositionTest.java
@@ -0,0 +1,116 @@
+// 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.cf;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.transformers.ClassFileTransformer.LineTranslation;
+import com.android.tools.r8.transformers.MethodTransformer.MethodContext;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import it.unimi.dsi.fastutil.ints.Int2IntMap;
+import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
+import java.nio.file.Path;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class CompanionClassPreamblePositionTest extends TestBase {
+
+ static final String EXPECTED = StringUtils.lines("no line", "has line");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters()
+ .withDefaultCfRuntime()
+ .withApiLevel(AndroidApiLevel.B)
+ .enableApiLevelsForCf()
+ .build();
+ }
+
+ public CompanionClassPreamblePositionTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testZeroInInput() throws Exception {
+ testForJvm()
+ .addProgramClasses(TestClass.class, A.class)
+ .addProgramClassFileData(getTransformedI(true))
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED)
+ .inspect(this::checkZeroLineIsPresent);
+ }
+
+ @Test
+ public void testNoAddedZeroD8() throws Exception {
+ Path out =
+ testForD8(parameters.getBackend())
+ .addProgramClasses(TestClass.class, A.class)
+ .addProgramClassFileData(getTransformedI(false))
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .writeToZip();
+ testForJvm()
+ .addProgramFiles(out)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED)
+ .inspect(this::checkZeroLineNotPresent);
+ }
+
+ private void checkZeroLineIsPresent(CodeInspector inspector) throws Exception {
+ // ASM skips zero lines so use javap to check the presence of a zero line.
+ ClassSubject itf = inspector.clazz(I.class);
+ assertThat(itf.javap(true), containsString("line 0: 0"));
+ }
+
+ private void checkZeroLineNotPresent(CodeInspector inspector) throws Exception {
+ ClassSubject companion = inspector.companionClassFor(I.class);
+ assertThat(companion.javap(true), not(containsString("line 0: 0")));
+ }
+
+ private byte[] getTransformedI(boolean includeZero) throws Exception {
+ return transformer(I.class)
+ .setPredictiveLineNumbering(
+ new LineTranslation() {
+ Int2IntMap map = new Int2IntOpenHashMap();
+
+ @Override
+ public int translate(MethodContext context, int line) {
+ if (context.getReference().getMethodName().equals("foo")) {
+ int newLine = map.computeIfAbsent(line, ignore -> map.size());
+ return newLine == 0 ? (includeZero ? 0 : -1) : newLine;
+ }
+ return line;
+ }
+ })
+ .transform();
+ }
+
+ interface I {
+ default void foo() {
+ System.out.println("no line"); // Transform removes/replaces this line entry.
+ System.out.println("has line");
+ }
+ }
+
+ static class A implements I {}
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ new A().foo();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepEdgeAnnotationsTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepEdgeAnnotationsTest.java
new file mode 100644
index 0000000..fcdab96
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/KeepEdgeAnnotationsTest.java
@@ -0,0 +1,218 @@
+// 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;
+
+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 static org.junit.Assume.assumeTrue;
+
+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.asm.KeepEdgeWriter;
+import com.android.tools.r8.keepanno.ast.KeepEdge;
+import com.android.tools.r8.keepanno.keeprules.KeepRuleExtractor;
+import com.android.tools.r8.keepanno.processor.KeepEdgeProcessor;
+import com.android.tools.r8.keepanno.testsource.KeepClassAndDefaultConstructorSource;
+import com.android.tools.r8.keepanno.testsource.KeepFieldSource;
+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 com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.ZipUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.AnnotationVisitor;
+
+@RunWith(Parameterized.class)
+public class KeepEdgeAnnotationsTest extends TestBase {
+
+ private static class ParamWrapper {
+ private final Class<?> clazz;
+ private final TestParameters params;
+
+ public ParamWrapper(Class<?> clazz, TestParameters params) {
+ this.clazz = clazz;
+ this.params = params;
+ }
+
+ @Override
+ public String toString() {
+ return clazz.getSimpleName() + ", " + params.toString();
+ }
+ }
+
+ private static final Path KEEP_ANNO_PATH =
+ Paths.get(ToolHelper.BUILD_DIR, "classes", "java", "keepanno");
+
+ private static List<Class<?>> getTestClasses() {
+ return ImmutableList.of(KeepClassAndDefaultConstructorSource.class, KeepFieldSource.class);
+ }
+
+ private final TestParameters parameters;
+ private final Class<?> source;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static List<ParamWrapper> data() {
+ TestParametersCollection params =
+ getTestParameters().withDefaultRuntimes().withApiLevel(AndroidApiLevel.B).build();
+ return getTestClasses().stream()
+ .flatMap(c -> params.stream().map(p -> new ParamWrapper(c, p)))
+ .collect(Collectors.toList());
+ }
+
+ public KeepEdgeAnnotationsTest(ParamWrapper wrapper) {
+ this.parameters = wrapper.params;
+ this.source = wrapper.clazz;
+ }
+
+ private String getExpected() {
+ return KeepSourceEdges.getExpected(source);
+ }
+
+ @Test
+ public void testProcessorClassfiles() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ 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());
+
+ testForJvm()
+ .addProgramClassesAndInnerClasses(source)
+ .addProgramFiles(out)
+ .run(parameters.getRuntime(), source)
+ .assertSuccessWithOutput(getExpected());
+ }
+
+ @Test
+ public void testProcessorJavaSource() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ 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)
+ .assertSuccessWithOutput(getExpected())
+ .inspect(
+ inspector -> {
+ assertThat(inspector.clazz(source), isPresent());
+ checkSynthesizedKeepEdgeClass(inspector, out);
+ });
+ }
+
+ @Test
+ public void testAsmReader() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ 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);
+ }
+
+ @Test
+ public void testExtractAndRun() throws Exception {
+ List<String> rules = getKeepRulesForClass(source);
+ testForR8(parameters.getBackend())
+ .addClasspathFiles(KEEP_ANNO_PATH)
+ .addProgramClassesAndInnerClasses(source)
+ .addKeepRules(rules)
+ .addKeepMainRule(source)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), source)
+ .assertSuccessWithOutput(getExpected());
+ }
+
+ private List<String> getKeepRulesForClass(Class<?> clazz) throws IOException {
+ Set<KeepEdge> keepEdges = KeepEdgeReader.readKeepEdges(ToolHelper.getClassAsBytes(clazz));
+ List<String> rules = new ArrayList<>();
+ KeepRuleExtractor extractor = new KeepRuleExtractor(rules::add);
+ keepEdges.forEach(extractor::extract);
+ return rules;
+ }
+
+ 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/asm/KeepEdgeAsmTest.java b/src/test/java/com/android/tools/r8/keepanno/asm/KeepEdgeAsmTest.java
deleted file mode 100644
index b9ebfa2..0000000
--- a/src/test/java/com/android/tools/r8/keepanno/asm/KeepEdgeAsmTest.java
+++ /dev/null
@@ -1,84 +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.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/keepanno/keeprules/KeepRuleExtractorTest.java b/src/test/java/com/android/tools/r8/keepanno/keeprules/KeepRuleExtractorTest.java
deleted file mode 100644
index 94001ec..0000000
--- a/src/test/java/com/android/tools/r8/keepanno/keeprules/KeepRuleExtractorTest.java
+++ /dev/null
@@ -1,63 +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.keepanno.keeprules;
-
-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.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 java.io.IOException;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-
-@RunWith(Parameterized.class)
-public class KeepRuleExtractorTest extends TestBase {
-
- private static final Class<?> SOURCE = KeepClassAndDefaultConstructorSource.class;
- private static final String EXPECTED = KeepSourceEdges.getExpected(SOURCE);
- private static final Path KEEP_ANNO_PATH =
- Paths.get(ToolHelper.BUILD_DIR, "classes", "java", "keepanno");
-
- private final TestParameters parameters;
-
- @Parameterized.Parameters(name = "{0}")
- public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimes().withAllApiLevels().build();
- }
-
- public KeepRuleExtractorTest(TestParameters parameters) {
- this.parameters = parameters;
- }
-
- @Test
- public void test() throws Exception {
- List<String> rules = getKeepRulesForClass(SOURCE);
- testForR8(parameters.getBackend())
- .addClasspathFiles(KEEP_ANNO_PATH)
- .addProgramClassesAndInnerClasses(SOURCE)
- .addKeepRules(rules)
- .addKeepMainRule(SOURCE)
- .setMinApi(parameters.getApiLevel())
- .run(parameters.getRuntime(), SOURCE)
- .assertSuccessWithOutput(EXPECTED);
- }
-
- private List<String> getKeepRulesForClass(Class<?> clazz) throws IOException {
- Set<KeepEdge> keepEdges = KeepEdgeReader.readKeepEdges(ToolHelper.getClassAsBytes(clazz));
- List<String> rules = new ArrayList<>();
- KeepRuleExtractor extractor = new KeepRuleExtractor(rules::add);
- keepEdges.forEach(extractor::extract);
- return rules;
- }
-}
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
deleted file mode 100644
index 83f60a3..0000000
--- a/src/test/java/com/android/tools/r8/keepanno/processor/KeepEdgeProcessorTest.java
+++ /dev/null
@@ -1,108 +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.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 static final String EXPECTED = KeepSourceEdges.getExpected(SOURCE);
-
- 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());
-
- testForJvm()
- .addProgramClasses(SOURCE, KeepClassAndDefaultConstructorSource.A.class)
- .addProgramFiles(out)
- .run(parameters.getRuntime(), SOURCE)
- .assertSuccessWithOutput(EXPECTED);
- }
-
- @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)
- .assertSuccessWithOutput(EXPECTED)
- .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/KeepFieldSource.java b/src/test/java/com/android/tools/r8/keepanno/testsource/KeepFieldSource.java
new file mode 100644
index 0000000..6f577e4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/testsource/KeepFieldSource.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.testsource;
+
+import com.android.tools.r8.keepanno.annotations.KeepEdge;
+import com.android.tools.r8.keepanno.annotations.KeepTarget;
+import java.lang.reflect.Field;
+
+@KeepEdge(
+ consequences = {
+ // Keep the reflectively accessed field.
+ @KeepTarget(classConstant = KeepFieldSource.A.class, fieldName = "f")
+ })
+public class KeepFieldSource {
+
+ public static class A {
+
+ public int f;
+
+ public A(int x) {
+ f = x;
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+ int x = 42 + args.length;
+ Object o = System.nanoTime() > 0 ? new A(x) : null;
+ Field f = o.getClass().getDeclaredField("f");
+ int y = f.getInt(o);
+ if (x == y) {
+ System.out.println("The values match!");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/testsource/KeepSourceEdges.java b/src/test/java/com/android/tools/r8/keepanno/testsource/KeepSourceEdges.java
index 5637edf..7af9aee 100644
--- a/src/test/java/com/android/tools/r8/keepanno/testsource/KeepSourceEdges.java
+++ b/src/test/java/com/android/tools/r8/keepanno/testsource/KeepSourceEdges.java
@@ -5,6 +5,8 @@
import com.android.tools.r8.keepanno.ast.KeepConsequences;
import com.android.tools.r8.keepanno.ast.KeepEdge;
+import com.android.tools.r8.keepanno.ast.KeepFieldNamePattern;
+import com.android.tools.r8.keepanno.ast.KeepFieldPattern;
import com.android.tools.r8.keepanno.ast.KeepItemPattern;
import com.android.tools.r8.keepanno.ast.KeepMethodNamePattern;
import com.android.tools.r8.keepanno.ast.KeepMethodPattern;
@@ -25,6 +27,9 @@
if (clazz.equals(KeepClassAndDefaultConstructorSource.class)) {
return getKeepClassAndDefaultConstructorSourceEdges();
}
+ if (clazz.equals(KeepFieldSource.class)) {
+ return getKeepFieldSourceEdges();
+ }
throw new RuntimeException();
}
@@ -32,6 +37,9 @@
if (clazz.equals(KeepClassAndDefaultConstructorSource.class)) {
return getKeepClassAndDefaultConstructorSourceExpected();
}
+ if (clazz.equals(KeepFieldSource.class)) {
+ return getKeepFieldSourceExpected();
+ }
throw new RuntimeException();
}
@@ -57,4 +65,21 @@
KeepEdge edge = KeepEdge.builder().setConsequences(consequences).build();
return Collections.singleton(edge);
}
+
+ public static String getKeepFieldSourceExpected() {
+ return StringUtils.lines("The values match!");
+ }
+
+ public static Set<KeepEdge> getKeepFieldSourceEdges() {
+ Class<?> clazz = KeepFieldSource.A.class;
+ KeepQualifiedClassNamePattern name = KeepQualifiedClassNamePattern.exact(clazz.getTypeName());
+ KeepFieldPattern fieldPattern =
+ KeepFieldPattern.builder().setNamePattern(KeepFieldNamePattern.exact("f")).build();
+ KeepItemPattern fieldItem =
+ KeepItemPattern.builder().setClassPattern(name).setMemberPattern(fieldPattern).build();
+ KeepTarget fieldTarget = KeepTarget.builder().setItem(fieldItem).build();
+ KeepConsequences consequences = KeepConsequences.builder().addTarget(fieldTarget).build();
+ KeepEdge edge = KeepEdge.builder().setConsequences(consequences).build();
+ return Collections.singleton(edge);
+ }
}
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingAmbiguousDispatchTest.java b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingAmbiguousDispatchTest.java
new file mode 100644
index 0000000..2b41739
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingAmbiguousDispatchTest.java
@@ -0,0 +1,164 @@
+// 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.memberrebinding;
+
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.DiagnosticsMatcher;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.util.List;
+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 MemberRebindingAmbiguousDispatchTest extends TestBase {
+
+ @Parameter() public TestParameters parameters;
+
+ @Parameter(1)
+ public boolean abstractMethodOnSuperClass;
+
+ @Parameter(2)
+ public boolean interfaceAsSymbolicReference;
+
+ @Parameters(name = "{0}, abstractMethodOnSuperClass: {1}, interfaceAsSymbolicReference {2}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withAllRuntimesAndApiLevels().build(),
+ BooleanUtils.values(),
+ BooleanUtils.values());
+ }
+
+ private void setupInput(TestBuilder<?, ?> testBuilder) {
+ testBuilder
+ .addProgramClasses(Main.class, SuperInterface.class)
+ .applyIf(
+ abstractMethodOnSuperClass,
+ b -> b.addProgramClassFileData(getSuperClassWithFooAsAbstract()),
+ b -> b.addProgramClasses(SuperClass.class))
+ .applyIf(
+ interfaceAsSymbolicReference,
+ b -> b.addProgramClassFileData(getProgramClassWithInvokeToInterface()),
+ b -> b.addProgramClasses(ProgramClass.class));
+ }
+
+ @Test
+ public void testRuntime() throws Exception {
+ testForRuntime(parameters)
+ .apply(this::setupInput)
+ .run(parameters.getRuntime(), Main.class)
+ .apply(this::checkOutput);
+ }
+
+ private boolean desugaringWithoutSupport() {
+ return parameters.isDexRuntime()
+ && interfaceAsSymbolicReference
+ && !parameters.canUseDefaultAndStaticInterfaceMethods();
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ assumeFalse(desugaringWithoutSupport());
+ testForR8(parameters.getBackend())
+ .apply(this::setupInput)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Main.class)
+ .run(parameters.getRuntime(), Main.class)
+ .apply(this::checkOutput);
+ }
+
+ @Test
+ public void testR8AssertionError() {
+ assumeTrue(desugaringWithoutSupport());
+ // TODO(b/259227990): We should not fail compilation.
+ assertThrows(
+ CompilationFailedException.class,
+ () ->
+ testForR8(parameters.getBackend())
+ .apply(this::setupInput)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Main.class)
+ .compileWithExpectedDiagnostics(
+ diagnostics ->
+ diagnostics.assertErrorThatMatches(
+ DiagnosticsMatcher.diagnosticException(AssertionError.class))));
+ }
+
+ private void checkOutput(TestRunResult<?> result) {
+ if (parameters.isDexRuntime()
+ && parameters.getDexRuntimeVersion().isDalvik()
+ && interfaceAsSymbolicReference) {
+ result.assertFailureWithErrorThatThrows(VerifyError.class);
+ } else if (parameters.isDexRuntime()
+ && parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V7_0_0)
+ && interfaceAsSymbolicReference
+ && !parameters.canUseDefaultAndStaticInterfaceMethods()) {
+ result.assertFailureWithErrorThatThrows(ClassNotFoundException.class);
+ } else if (abstractMethodOnSuperClass || interfaceAsSymbolicReference) {
+ result.assertFailureWithErrorThatThrows(AbstractMethodError.class);
+ } else {
+ result.assertSuccessWithOutputLines("SuperClass::foo");
+ }
+ }
+
+ private byte[] getSuperClassWithFooAsAbstract() throws Exception {
+ return transformer(SuperClassAbstract.class)
+ .setClassDescriptor(descriptor(SuperClass.class))
+ .transform();
+ }
+
+ private byte[] getProgramClassWithInvokeToInterface() throws Exception {
+ return transformer(ProgramClass.class)
+ .transformMethodInsnInMethod(
+ "foo",
+ (opcode, owner, name, descriptor, isInterface, visitor) ->
+ visitor.visitMethodInsn(
+ opcode, binaryName(SuperInterface.class), name, descriptor, true))
+ .transform();
+ }
+
+ public abstract static class SuperClassAbstract {
+
+ public abstract void foo();
+ }
+
+ public abstract static class SuperClass {
+
+ public void foo() {
+ System.out.println("SuperClass::foo");
+ }
+ }
+
+ public interface SuperInterface {
+
+ void foo();
+ }
+
+ public static class ProgramClass extends SuperClass implements SuperInterface {
+
+ @Override
+ public void foo() {
+ super.foo();
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ new ProgramClass().foo();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingAmbiguousDispatchToLibraryTest.java b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingAmbiguousDispatchToLibraryTest.java
new file mode 100644
index 0000000..81ac099
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingAmbiguousDispatchToLibraryTest.java
@@ -0,0 +1,152 @@
+// 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.memberrebinding;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.util.List;
+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 MemberRebindingAmbiguousDispatchToLibraryTest extends TestBase {
+
+ @Parameter() public TestParameters parameters;
+
+ @Parameter(1)
+ public boolean abstractMethodOnSuperClass;
+
+ @Parameter(2)
+ public boolean interfaceAsSymbolicReference;
+
+ @Parameters(name = "{0}, abstractMethodOnSuperClass: {1}, interfaceAsSymbolicReference {2}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withDexRuntimes().withAllApiLevelsAlsoForCf().build(),
+ BooleanUtils.values(),
+ BooleanUtils.values());
+ }
+
+ private void setupInput(TestBuilder<?, ?> testBuilder) throws Exception {
+ testBuilder
+ .addProgramClasses(Main.class)
+ .addProgramClassFileData(getProgramClass())
+ .addDefaultRuntimeLibrary(parameters)
+ .addLibraryClasses(SuperInterface.class)
+ .addLibraryClassFileData(getSuperClass());
+ }
+
+ @Test
+ public void testReference() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForJvm()
+ .apply(this::setupInput)
+ .run(parameters.getRuntime(), Main.class)
+ .apply(this::checkOutput);
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ testForD8(parameters.getBackend())
+ .apply(this::setupInput)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .addRunClasspathClasses(SuperInterface.class)
+ .addRunClasspathClassFileData(getSuperClass())
+ .run(parameters.getRuntime(), Main.class)
+ .apply(this::checkOutput);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .apply(this::setupInput)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Main.class)
+ .compile()
+ .addRunClasspathClasses(SuperInterface.class)
+ .addRunClasspathClassFileData(getSuperClass())
+ .run(parameters.getRuntime(), Main.class)
+ .apply(this::checkOutput);
+ }
+
+ private void checkOutput(TestRunResult<?> result) {
+ if (parameters.isDexRuntime() && interfaceAsSymbolicReference) {
+ if (parameters.getDexRuntimeVersion().isDalvik()) {
+ result.assertFailureWithErrorThatThrows(VerifyError.class);
+ } else if (parameters.getDexRuntimeVersion().isOlderThan(Version.V7_0_0)) {
+ result.assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class);
+ } else {
+ // TODO(b/259227990): If the SuperClass.foo() is not abstract we produce a working program.
+ result.assertFailureWithErrorThatThrows(AbstractMethodError.class);
+ }
+ } else if (abstractMethodOnSuperClass || interfaceAsSymbolicReference) {
+ result.assertFailureWithErrorThatThrows(AbstractMethodError.class);
+ } else {
+ result.assertSuccessWithOutputLines("SuperClass::foo");
+ }
+ }
+
+ private byte[] getSuperClass() throws Exception {
+ return abstractMethodOnSuperClass
+ ? transformer(SuperClassAbstract.class)
+ .setClassDescriptor(descriptor(SuperClass.class))
+ .transform()
+ : transformer(SuperClass.class).transform();
+ }
+
+ private byte[] getProgramClass() throws Exception {
+ return interfaceAsSymbolicReference
+ ? transformer(ProgramClass.class)
+ .transformMethodInsnInMethod(
+ "foo",
+ (opcode, owner, name, descriptor, isInterface, visitor) ->
+ visitor.visitMethodInsn(
+ opcode, binaryName(SuperInterface.class), name, descriptor, true))
+ .transform()
+ : transformer(ProgramClass.class).transform();
+ }
+
+ public abstract static class SuperClassAbstract {
+
+ public abstract void foo();
+ }
+
+ public abstract static class SuperClass {
+
+ public void foo() {
+ System.out.println("SuperClass::foo");
+ }
+ }
+
+ public interface SuperInterface {
+
+ void foo();
+ }
+
+ public static class ProgramClass extends SuperClass implements SuperInterface {
+
+ @Override
+ public void foo() {
+ super.foo();
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ new ProgramClass().foo();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/optimize/proto/ProtoNormalizationDestinationOverrideLibraryTest.java b/src/test/java/com/android/tools/r8/optimize/proto/ProtoNormalizationDestinationOverrideLibraryTest.java
index 3b7a115..f58336a 100644
--- a/src/test/java/com/android/tools/r8/optimize/proto/ProtoNormalizationDestinationOverrideLibraryTest.java
+++ b/src/test/java/com/android/tools/r8/optimize/proto/ProtoNormalizationDestinationOverrideLibraryTest.java
@@ -4,10 +4,6 @@
package com.android.tools.r8.optimize.proto;
-import static org.hamcrest.CoreMatchers.containsString;
-import static org.junit.Assert.assertThrows;
-
-import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.NoMethodStaticizing;
import com.android.tools.r8.TestBase;
@@ -44,26 +40,19 @@
@Test
public void testR8() throws Exception {
- // TODO(b/258720808): We should not fail compilation.
- assertThrows(
- CompilationFailedException.class,
- () ->
- testForR8(parameters.getBackend())
- .addProgramClasses(Main.class, ProgramClass.class, X.class)
- .addDefaultRuntimeLibrary(parameters)
- .addLibraryClasses(LibraryClass.class)
- .setMinApi(parameters.getApiLevel())
- .addKeepMainRule(Main.class)
- .addDontObfuscate()
- .enableInliningAnnotations()
- .enableNoMethodStaticizingAnnotations()
- .compileWithExpectedDiagnostics(
- diagnostics -> {
- diagnostics.assertErrorMessageThatMatches(
- containsString(
- "went from not overriding a library method to overriding a library"
- + " method"));
- }));
+ testForR8(parameters.getBackend())
+ .addProgramClasses(Main.class, ProgramClass.class, X.class)
+ .addDefaultRuntimeLibrary(parameters)
+ .addLibraryClasses(LibraryClass.class)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Main.class)
+ .addDontObfuscate()
+ .enableInliningAnnotations()
+ .enableNoMethodStaticizingAnnotations()
+ .compile()
+ .addBootClasspathClasses(LibraryClass.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
}
public static class LibraryClass {
diff --git a/src/test/java/com/android/tools/r8/optimize/proto/ProtoNormalizationDuplicateMethodTest.java b/src/test/java/com/android/tools/r8/optimize/proto/ProtoNormalizationDuplicateMethodTest.java
new file mode 100644
index 0000000..c935224
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/optimize/proto/ProtoNormalizationDuplicateMethodTest.java
@@ -0,0 +1,87 @@
+// 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.optimize.proto;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.references.Reference;
+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 ProtoNormalizationDuplicateMethodTest extends TestBase {
+
+ private final String[] EXPECTED = new String[] {"Base::foo-7Calling Sub::foo8"};
+
+ @Parameter() public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void testRuntime() throws Exception {
+ testForRuntime(parameters)
+ .addInnerClasses(getClass())
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Main.class)
+ .addKeepMethodRules(
+ Reference.methodFromMethod(
+ B.class.getDeclaredMethod("foo$1", int.class, int.class, String.class)))
+ .enableInliningAnnotations()
+ .enableNoHorizontalClassMergingAnnotations()
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ @NoHorizontalClassMerging
+ public static class B {
+
+ @NeverInline
+ public void foo(int i, String s, int j) {
+ System.out.println("Base::foo-" + i + s + j);
+ }
+
+ @NeverInline
+ public void foo$1(int i, int j, String s) {
+ throw new RuntimeException("Should never be called: " + i + j + s);
+ }
+ }
+
+ @NoHorizontalClassMerging
+ public static class A {
+
+ @NeverInline
+ public void foo(String s, int i, int j) {
+ System.out.println("B-" + i + s + j);
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ if (System.currentTimeMillis() == 0) {
+ new A().foo("Calling Sub::foo", 3, 4);
+ new B().foo$1(5, 6, "Calling Sub::foo");
+ }
+ new B().foo(7, "Calling Sub::foo", 8);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/optimize/proto/ProtoNormalizationIntroduceCollisionTest.java b/src/test/java/com/android/tools/r8/optimize/proto/ProtoNormalizationIntroduceCollisionTest.java
new file mode 100644
index 0000000..947e3e7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/optimize/proto/ProtoNormalizationIntroduceCollisionTest.java
@@ -0,0 +1,82 @@
+// 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.optimize.proto;
+
+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 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 ProtoNormalizationIntroduceCollisionTest extends TestBase {
+
+ private final String[] EXPECTED = new String[] {"Base::foo-42Calling B::foo1337"};
+
+ @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, Base.class, Sub.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Main.class)
+ .addKeepClassAndMembersRules(Base.class)
+ .enableInliningAnnotations()
+ .enableNoVerticalClassMergingAnnotations()
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ public static class Base {
+
+ @NeverInline
+ public void foo(int i, int j, String s) {
+ System.out.println("Base::foo-" + i + s + j);
+ }
+ }
+
+ @NoVerticalClassMerging
+ public static class Sub extends Base {
+
+ @NeverInline
+ public void foo(String s, int i, int j) {
+ System.out.println("Sub::foo-" + s + i + j);
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ if (System.currentTimeMillis() == 0) {
+ callFoo(new Base());
+ new Sub().foo("Calling Sub::foo", 1, 2);
+ }
+ callFoo(new Sub());
+ }
+
+ public static void callFoo(Base b) {
+ b.foo(42, 1337, "Calling B::foo");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/optimize/proto/ProtoNormalizationWithVirtualMethodCollisionTest.java b/src/test/java/com/android/tools/r8/optimize/proto/ProtoNormalizationWithVirtualMethodCollisionTest.java
index c179493..c3997c9 100644
--- a/src/test/java/com/android/tools/r8/optimize/proto/ProtoNormalizationWithVirtualMethodCollisionTest.java
+++ b/src/test/java/com/android/tools/r8/optimize/proto/ProtoNormalizationWithVirtualMethodCollisionTest.java
@@ -36,9 +36,6 @@
private final String[] EXPECTED =
new String[] {"A::foo", "B", "A", "B::foo", "B", "A", "B::foo", "B", "A"};
- private final String[] R8_EXPECTED =
- new String[] {"A::foo", "B", "A", "A::foo", "B", "A", "B::foo", "B", "A"};
-
@Test
public void testRuntime() throws Exception {
testForRuntime(parameters)
@@ -59,8 +56,7 @@
.setMinApi(parameters.getApiLevel())
.compile()
.run(parameters.getRuntime(), Main.class)
- // TODO(b/258720808): We should not produce incorrect results.
- .assertSuccessWithOutputLines(R8_EXPECTED)
+ .assertSuccessWithOutputLines(EXPECTED)
.inspect(
inspector -> {
ClassSubject bClassSubject = inspector.clazz(B.class);
@@ -72,7 +68,7 @@
TypeSubject bTypeSubject = bClassSubject.asTypeSubject();
TypeSubject aTypeSubject = aClassSubject.asTypeSubject();
- MethodSubject fooMethodSubject = aClassSubject.uniqueMethodWithOriginalName("foo");
+ MethodSubject fooMethodSubject = aClassSubject.uniqueMethodWithFinalName("foo$1");
assertThat(fooMethodSubject, isPresent());
assertThat(fooMethodSubject, hasParameters(aTypeSubject, bTypeSubject));
diff --git a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
index 63efc58..8108730 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -1239,25 +1239,47 @@
return setPredictiveLineNumbering(predicate, 0);
}
+ public interface LineTranslation {
+ int translate(MethodContext context, int line);
+ }
+
+ private static class IncrementingLineNumbers implements LineTranslation {
+ private final MethodPredicate predicate;
+ private final int startingLineNumber;
+ private final Map<MethodReference, Integer> lines = new HashMap<>();
+
+ public IncrementingLineNumbers(MethodPredicate predicate, int startingLineNumber) {
+ this.predicate = predicate;
+ this.startingLineNumber = startingLineNumber;
+ }
+
+ @Override
+ public int translate(MethodContext context, int line) {
+ if (MethodPredicate.testContext(predicate, context)) {
+ MethodReference method = context.getReference();
+ int nextLine = lines.getOrDefault(method, startingLineNumber);
+ // Increment the actual line content by 100 so that each one is clearly distinct
+ // from a PC value for any of the methods.
+ lines.put(method, nextLine + 100);
+ return nextLine;
+ }
+ return line;
+ }
+ }
+
public ClassFileTransformer setPredictiveLineNumbering(
MethodPredicate predicate, int startingLineNumber) {
+ return setPredictiveLineNumbering(new IncrementingLineNumbers(predicate, startingLineNumber));
+ }
+
+ public ClassFileTransformer setPredictiveLineNumbering(LineTranslation translation) {
return addMethodTransformer(
new MethodTransformer() {
- private final Map<MethodReference, Integer> lines = new HashMap<>();
-
@Override
public void visitLineNumber(int line, Label start) {
- if (MethodPredicate.testContext(predicate, getContext())) {
- Integer nextLine =
- lines.getOrDefault(getContext().getReference(), startingLineNumber);
- if (nextLine > 0) {
- super.visitLineNumber(nextLine, start);
- }
- // Increment the actual line content by 100 so that each one is clearly distinct
- // from a PC value for any of the methods.
- lines.put(getContext().getReference(), nextLine + 100);
- } else {
- super.visitLineNumber(line, start);
+ int newLine = translation.translate(getContext(), line);
+ if (newLine >= 0) {
+ super.visitLineNumber(newLine, start);
}
}
});
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
index e353ee6..ba1898d 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
@@ -260,7 +260,7 @@
}
@Override
- public String disassembleUsingJavap(boolean verbose) throws Exception {
+ public String javap(boolean verbose) throws Exception {
throw new Unreachable("Cannot disassembly an absent class");
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
index 0b2b52c..abcdc0f 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
@@ -264,7 +264,11 @@
public abstract ClassNamingForNameMapper getNaming();
- public abstract String disassembleUsingJavap(boolean verbose) throws Exception;
+ public abstract String javap(boolean verbose) throws Exception;
+
+ public void disassembleUsingJavap(boolean verbose) throws Exception {
+ System.out.println(javap(true));
+ }
public abstract String asmify(TemporaryFolder tempFolder, boolean debug) throws Exception;
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
index ff35c6e..99489c7 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
@@ -627,7 +627,7 @@
}
@Override
- public String disassembleUsingJavap(boolean verbose) throws Exception {
+ public String javap(boolean verbose) throws Exception {
assert dexClass.origin != null;
List<String> command = new ArrayList<>();
command.add(
@@ -644,7 +644,6 @@
command.add(parts.get(1).replace(".class", ""));
ProcessResult processResult = ToolHelper.runProcess(new ProcessBuilder(command));
assert processResult.exitCode == 0;
- System.out.println(processResult.stdout);
return processResult.stdout;
}
diff --git a/third_party/openjdk/desugar_jdk_libs_11.tar.gz.sha1 b/third_party/openjdk/desugar_jdk_libs_11.tar.gz.sha1
index 6a3c83d..a75d743 100644
--- a/third_party/openjdk/desugar_jdk_libs_11.tar.gz.sha1
+++ b/third_party/openjdk/desugar_jdk_libs_11.tar.gz.sha1
@@ -1 +1 @@
-9f148123a86ba0f40e58a71f9ad59c723ecb4607
\ No newline at end of file
+baf1112d9c5ee962bf5085a81289f43b9f38697e
\ No newline at end of file
diff --git a/tools/compiledump.py b/tools/compiledump.py
index 8149313..64a2a31 100755
--- a/tools/compiledump.py
+++ b/tools/compiledump.py
@@ -404,6 +404,13 @@
return len(version) == 40
def run1(out, args, otherargs, jdkhome=None):
+ jvmargs = []
+ compilerargs = []
+ for arg in otherargs:
+ if arg.startswith('-D'):
+ jvmargs.append(arg)
+ else:
+ compilerargs.append(arg)
with utils.TempDir() as temp:
if out:
temp = out
@@ -429,6 +436,7 @@
prepare_r8_wrapper(jar, temp, jdkhome)
prepare_d8_wrapper(jar, temp, jdkhome)
cmd = [jdk.GetJavaExecutable(jdkhome)]
+ cmd.extend(jvmargs)
if args.debug_agent:
if not args.nolib:
print("WARNING: Running debugging agent on r8lib is questionable...")
@@ -500,7 +508,7 @@
cmd.extend(['--enable-missing-library-api-modeling'])
if args.threads:
cmd.extend(['--threads', args.threads])
- cmd.extend(otherargs)
+ cmd.extend(compilerargs)
utils.PrintCmd(cmd)
try:
print(subprocess.check_output(cmd, stderr=subprocess.STDOUT).decode('utf-8'))