Merge commit '254a39d21274c47cdaed865ec0a5f0bb7ba51896' into dev-release
diff --git a/buildSrc/src/main/java/desugaredlibrary/CustomConversionAsmRewriteDescription.java b/buildSrc/src/main/java/desugaredlibrary/CustomConversionAsmRewriteDescription.java index b02dc00..3ac81cd 100644 --- a/buildSrc/src/main/java/desugaredlibrary/CustomConversionAsmRewriteDescription.java +++ b/buildSrc/src/main/java/desugaredlibrary/CustomConversionAsmRewriteDescription.java
@@ -28,6 +28,7 @@ "j$/nio/file/spi/FileTypeDetector", "j$/nio/file/Path", "j$/nio/file/WatchEvent", + "j$/nio/file/WatchEvent$Kind", "j$/nio/file/OpenOption", "j$/nio/file/attribute/FileAttribute");
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepCondition.java b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepCondition.java index 6d66d0a..01c493f 100644 --- a/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepCondition.java +++ b/src/keepanno/java/com/android/tools/r8/keepanno/annotations/KeepCondition.java
@@ -31,5 +31,11 @@ String methodName() default ""; + String methodReturnType() default ""; + + String[] methodParameters() default {""}; + String fieldName() default ""; + + String fieldType() default ""; }
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 260c31c..0018dfc 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
@@ -37,8 +37,23 @@ // Implicit hidden item which is "super type" of Condition and Target. public static final class Item { public static final String classConstant = "classConstant"; + public static final String methodName = "methodName"; + public static final String methodReturnType = "methodReturnType"; + public static final String methodParameters = "methodParameters"; + public static final String fieldName = "fieldName"; + public static final String fieldType = "fieldType"; + + // Default values for the optional entries. The defaults should be chosen such that they do + // not coincide with any actual valid value. E.g., the empty string in place of a name or type. + // These must be 1:1 with the value defined on the actual annotation definition. + public static final String methodNameDefaultValue = ""; + public static final String methodReturnTypeDefaultValue = ""; + public static final String[] methodParametersDefaultValue = new String[] {""}; + + public static final String fieldNameDefaultValue = ""; + public static final String fieldTypeDefaultValue = ""; } public static final class Condition {
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 f87e32c..73559d6 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
@@ -8,6 +8,20 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +/** + * A target for a keep edge. + * + * <p>The target denotes a keep item along with options for what to keep: + * + * <ul> + * <li>a class, or pattern on classes; + * <li>a method, or pattern on methods; or + * <li>a field, or pattern on fields. + * </ul> + * + * <p>The structure of a target item is the same as for a condition item but has the additional keep + * options. + */ @Target(ElementType.ANNOTATION_TYPE) @Retention(RetentionPolicy.CLASS) public @interface KeepTarget { @@ -17,5 +31,11 @@ String methodName() default ""; + String methodReturnType() default ""; + + String[] methodParameters() default {""}; + String fieldName() default ""; + + String fieldType() 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 3545a25..672a99e7 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
@@ -14,15 +14,23 @@ 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.KeepFieldTypePattern; 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; +import com.android.tools.r8.keepanno.ast.KeepMethodParametersPattern; import com.android.tools.r8.keepanno.ast.KeepMethodPattern; +import com.android.tools.r8.keepanno.ast.KeepMethodReturnTypePattern; import com.android.tools.r8.keepanno.ast.KeepPreconditions; import com.android.tools.r8.keepanno.ast.KeepQualifiedClassNamePattern; import com.android.tools.r8.keepanno.ast.KeepTarget; +import com.android.tools.r8.keepanno.ast.KeepTypePattern; +import java.util.ArrayList; +import java.util.Arrays; import java.util.HashSet; +import java.util.List; import java.util.Set; +import java.util.function.Consumer; import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; @@ -115,14 +123,24 @@ } private KeepItemPattern createItemContext() { - Type returnType = Type.getReturnType(methodDescriptor); + String returnTypeDescriptor = Type.getReturnType(methodDescriptor).getDescriptor(); Type[] argumentTypes = Type.getArgumentTypes(methodDescriptor); - // TODO(b/248408342): Defaults are "any", support setting actual return type and params. + KeepMethodParametersPattern.Builder builder = KeepMethodParametersPattern.builder(); + for (Type type : argumentTypes) { + builder.addParameterTypePattern(KeepTypePattern.fromDescriptor(type.getDescriptor())); + } + KeepMethodReturnTypePattern returnTypePattern = + "V".equals(returnTypeDescriptor) + ? KeepMethodReturnTypePattern.voidType() + : KeepMethodReturnTypePattern.fromType( + KeepTypePattern.fromDescriptor(returnTypeDescriptor)); return KeepItemPattern.builder() .setClassPattern(KeepQualifiedClassNamePattern.exact(className)) .setMemberPattern( KeepMethodPattern.builder() .setNamePattern(KeepMethodNamePattern.exact(methodName)) + .setReturnTypePattern(returnTypePattern) + .setParametersPattern(builder.build()) .build()) .build(); } @@ -159,12 +177,14 @@ } private KeepItemPattern createItemContext() { - // TODO(b/248408342): Default type is "any", support setting actual field type. + KeepFieldTypePattern typePattern = + KeepFieldTypePattern.fromType(KeepTypePattern.fromDescriptor(fieldDescriptor)); return KeepItemPattern.builder() .setClassPattern(KeepQualifiedClassNamePattern.exact(className)) .setMemberPattern( KeepFieldPattern.builder() .setNamePattern(KeepFieldNamePattern.exact(fieldName)) + .setTypePattern(typePattern) .build()) .build(); } @@ -320,13 +340,33 @@ private final Parent<KeepItemPattern> parent; private KeepQualifiedClassNamePattern classNamePattern = null; - private KeepMethodNamePattern methodName = null; - private KeepFieldNamePattern fieldName = null; + private KeepMethodPattern.Builder lazyMethodBuilder = null; + private KeepFieldPattern.Builder lazyFieldBuilder = null; public KeepItemVisitorBase(Parent<KeepItemPattern> parent) { this.parent = parent; } + private KeepMethodPattern.Builder methodBuilder() { + if (lazyFieldBuilder != null) { + throw new KeepEdgeException("Cannot define both a field and a method pattern"); + } + if (lazyMethodBuilder == null) { + lazyMethodBuilder = KeepMethodPattern.builder(); + } + return lazyMethodBuilder; + } + + private KeepFieldPattern.Builder fieldBuilder() { + if (lazyMethodBuilder != null) { + throw new KeepEdgeException("Cannot define both a field and a method pattern"); + } + if (lazyFieldBuilder == null) { + lazyFieldBuilder = KeepFieldPattern.builder(); + } + return lazyFieldBuilder; + } + @Override public void visit(String name, Object value) { if (name.equals(Item.classConstant) && value instanceof Type) { @@ -334,36 +374,100 @@ return; } if (name.equals(Item.methodName) && value instanceof String) { - methodName = KeepMethodNamePattern.exact((String) value); + String methodName = (String) value; + if (!Item.methodNameDefaultValue.equals(methodName)) { + methodBuilder().setNamePattern(KeepMethodNamePattern.exact(methodName)); + } + return; + } + if (name.equals(Item.methodReturnType) && value instanceof String) { + String returnType = (String) value; + if (!Item.methodReturnTypeDefaultValue.equals(returnType)) { + methodBuilder() + .setReturnTypePattern(KeepEdgeReaderUtils.methodReturnTypeFromString(returnType)); + } return; } if (name.equals(Item.fieldName) && value instanceof String) { - fieldName = KeepFieldNamePattern.exact((String) value); + String fieldName = (String) value; + if (!Item.fieldNameDefaultValue.equals(fieldName)) { + fieldBuilder().setNamePattern(KeepFieldNamePattern.exact(fieldName)); + } + return; + } + if (name.equals(Item.fieldType) && value instanceof String) { + String fieldType = (String) value; + if (!Item.fieldTypeDefaultValue.equals(fieldType)) { + fieldBuilder() + .setTypePattern( + KeepFieldTypePattern.fromType( + KeepEdgeReaderUtils.typePatternFromString(fieldType))); + } return; } super.visit(name, value); } @Override + public AnnotationVisitor visitArray(String name) { + if (name.equals(Item.methodParameters)) { + return new StringArrayVisitor( + params -> { + if (Arrays.asList(Item.methodParametersDefaultValue).equals(params)) { + return; + } + KeepMethodParametersPattern.Builder builder = KeepMethodParametersPattern.builder(); + for (String param : params) { + builder.addParameterTypePattern(KeepEdgeReaderUtils.typePatternFromString(param)); + } + methodBuilder().setParametersPattern(builder.build()); + }); + } + return super.visitArray(name); + } + + @Override public void visitEnd() { + assert lazyMethodBuilder == null || lazyFieldBuilder == null; Builder itemBuilder = KeepItemPattern.builder(); if (classNamePattern != null) { itemBuilder.setClassPattern(classNamePattern); } - if (methodName != null && fieldName != null) { - throw new KeepEdgeException("Cannot define both a field and a method pattern."); + if (lazyMethodBuilder != null) { + itemBuilder.setMemberPattern(lazyMethodBuilder.build()); } - if (methodName != null) { - itemBuilder.setMemberPattern( - KeepMethodPattern.builder().setNamePattern(methodName).build()); - } - if (fieldName != null) { - itemBuilder.setMemberPattern(KeepFieldPattern.builder().setNamePattern(fieldName).build()); + if (lazyFieldBuilder != null) { + itemBuilder.setMemberPattern(lazyFieldBuilder.build()); } parent.accept(itemBuilder.build()); } } + private static class StringArrayVisitor extends AnnotationVisitorBase { + + private final Consumer<List<String>> fn; + private final List<String> strings = new ArrayList<>(); + + public StringArrayVisitor(Consumer<List<String>> fn) { + this.fn = fn; + } + + @Override + public void visit(String name, Object value) { + if (value instanceof String) { + strings.add((String) value); + } else { + super.visit(name, value); + } + } + + @Override + public void visitEnd() { + super.visitEnd(); + fn.accept(strings); + } + } + private static class KeepTargetVisitor extends KeepItemVisitorBase { public KeepTargetVisitor(Parent<KeepTarget> parent) {
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReaderUtils.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReaderUtils.java new file mode 100644 index 0000000..e18bf1e --- /dev/null +++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReaderUtils.java
@@ -0,0 +1,69 @@ +// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.keepanno.asm; + +import com.android.tools.r8.keepanno.ast.KeepEdgeException; +import com.android.tools.r8.keepanno.ast.KeepMethodReturnTypePattern; +import com.android.tools.r8.keepanno.ast.KeepTypePattern; + +/** + * Utilities for mapping the syntax used in annotations to the keep-edge AST. + * + * <p>The AST explicitly avoids interpreting type strings as they are potentially ambiguous. These + * utilities define the mappings from such syntax strings into the AST. + */ +public class KeepEdgeReaderUtils { + + public static KeepTypePattern typePatternFromString(String string) { + if (string.equals("<any>")) { + return KeepTypePattern.any(); + } + return KeepTypePattern.fromDescriptor(javaTypeToDescriptor(string)); + } + + public static String javaTypeToDescriptor(String type) { + switch (type) { + case "boolean": + return "Z"; + case "byte": + return "B"; + case "short": + return "S"; + case "int": + return "I"; + case "long": + return "J"; + case "float": + return "F"; + case "double": + return "D"; + default: + { + StringBuilder builder = new StringBuilder(type.length()); + int i = type.length() - 1; + while (type.charAt(i) == ']') { + if (type.charAt(--i) != '[') { + throw new KeepEdgeException("Invalid type: " + type); + } + builder.append('['); + --i; + } + builder.append('L'); + for (int j = 0; j <= i; j++) { + char c = type.charAt(j); + builder.append(c == '.' ? '/' : c); + } + builder.append(';'); + return builder.toString(); + } + } + } + + public static KeepMethodReturnTypePattern methodReturnTypeFromString(String returnType) { + if ("void".equals(returnType)) { + return KeepMethodReturnTypePattern.voidType(); + } + return KeepMethodReturnTypePattern.fromType(typePatternFromString(returnType)); + } +}
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 b2da25a..e1d6d4e 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
@@ -33,7 +33,7 @@ * ::= any * | class QUALIFIED_CLASS_NAME_PATTERN extends EXTENDS_PATTERN { MEMBER_PATTERN } * - * TYPE_PATTERN ::= any + * TYPE_PATTERN ::= any | exact type-descriptor * PACKAGE_PATTERN ::= any | exact package-name * QUALIFIED_CLASS_NAME_PATTERN ::= any | PACKAGE_PATTERN | UNQUALIFIED_CLASS_NAME_PATTERN * UNQUALIFIED_CLASS_NAME_PATTERN ::= any | exact simple-class-name
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 index 7137033..1466fc1 100644 --- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepFieldPattern.java +++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepFieldPattern.java
@@ -14,7 +14,7 @@ public static class Builder { private KeepFieldAccessPattern accessPattern = KeepFieldAccessPattern.any(); - private KeepFieldNamePattern namePattern = null; + private KeepFieldNamePattern namePattern = KeepFieldNamePattern.any(); private KeepFieldTypePattern typePattern = KeepFieldTypePattern.any(); private Builder() {} @@ -39,9 +39,6 @@ } public KeepFieldPattern build() { - if (namePattern == null) { - throw new KeepEdgeException("Field pattern must declare a name pattern"); - } return new KeepFieldPattern(accessPattern, namePattern, 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 index beaaa55..3965699 100644 --- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepFieldTypePattern.java +++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepFieldTypePattern.java
@@ -9,6 +9,10 @@ return SomeType.ANY_TYPE_INSTANCE; } + public static KeepFieldTypePattern fromType(KeepTypePattern typePattern) { + return typePattern.isAny() ? any() : new SomeType(typePattern); + } + public boolean isAny() { return isType() && asType().isAny(); }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodParametersPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodParametersPattern.java index 29442ff..73f8669 100644 --- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodParametersPattern.java +++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodParametersPattern.java
@@ -3,11 +3,16 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.keepanno.ast; +import com.google.common.collect.ImmutableList; import java.util.Collections; import java.util.List; public abstract class KeepMethodParametersPattern { + public static Builder builder() { + return new Builder(); + } + public static KeepMethodParametersPattern any() { return Any.getInstance(); } @@ -30,6 +35,25 @@ return null; } + public static class Builder { + ImmutableList.Builder<KeepTypePattern> parameterPatterns = ImmutableList.builder(); + + private Builder() {} + + public Builder addParameterTypePattern(KeepTypePattern typePattern) { + parameterPatterns.add(typePattern); + return this; + } + + public KeepMethodParametersPattern build() { + List<KeepTypePattern> list = parameterPatterns.build(); + if (list.isEmpty()) { + return Some.EMPTY_INSTANCE; + } + return new Some(list); + } + } + private static class Some extends KeepMethodParametersPattern { private static final Some EMPTY_INSTANCE = new Some(Collections.emptyList());
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodPattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodPattern.java index e00fedd..f84a4b1 100644 --- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodPattern.java +++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodPattern.java
@@ -35,11 +35,15 @@ return self(); } - public Builder setReturnTypeVoid() { - returnTypePattern = KeepMethodReturnTypePattern.voidType(); + public Builder setReturnTypePattern(KeepMethodReturnTypePattern returnTypePattern) { + this.returnTypePattern = returnTypePattern; return self(); } + public Builder setReturnTypeVoid() { + return setReturnTypePattern(KeepMethodReturnTypePattern.voidType()); + } + public Builder setParametersPattern(KeepMethodParametersPattern parametersPattern) { this.parametersPattern = parametersPattern; return self();
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodReturnTypePattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodReturnTypePattern.java index ccfc183..807b1bc 100644 --- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodReturnTypePattern.java +++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepMethodReturnTypePattern.java
@@ -14,6 +14,10 @@ return VoidType.getInstance(); } + public static KeepMethodReturnTypePattern fromType(KeepTypePattern typePattern) { + return typePattern.isAny() ? any() : new SomeType(typePattern); + } + public boolean isAny() { return isType() && asType().isAny(); }
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepTypePattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepTypePattern.java index c7c24b5..872ad07 100644 --- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepTypePattern.java +++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepTypePattern.java
@@ -9,7 +9,57 @@ return Any.getInstance(); } + public static KeepTypePattern fromDescriptor(String typeDescriptor) { + return new Some(typeDescriptor); + } + + public boolean isAny() { + return false; + } + + public String getDescriptor() { + return null; + } + + private static class Some extends KeepTypePattern { + + private final String descriptor; + + private Some(String descriptor) { + assert descriptor != null; + this.descriptor = descriptor; + } + + @Override + public String getDescriptor() { + return descriptor; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Some some = (Some) o; + return descriptor.equals(some.descriptor); + } + + @Override + public int hashCode() { + return descriptor.hashCode(); + } + + @Override + public String toString() { + return descriptor; + } + } + private static class Any extends KeepTypePattern { + private static final Any INSTANCE = new Any(); public static Any getInstance() { @@ -33,9 +83,7 @@ @Override public String toString() { - return "*"; + return "<any>"; } } - - public abstract boolean isAny(); }
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 6781a22..286c07a 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,7 @@ 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.KeepFieldAccessPattern; import com.android.tools.r8.keepanno.ast.KeepFieldNamePattern; import com.android.tools.r8.keepanno.ast.KeepFieldPattern; @@ -157,7 +158,7 @@ .append('(') .append( parametersPattern.asList().stream() - .map(Object::toString) + .map(KeepRuleExtractor::getTypePatternString) .collect(Collectors.joining(", "))) .append(')'); } @@ -185,10 +186,7 @@ } private static StringBuilder printType(StringBuilder builder, KeepTypePattern typePattern) { - if (typePattern.isAny()) { - return builder.append("***"); - } - throw new Unimplemented(); + return builder.append(getTypePatternString(typePattern)); } private static StringBuilder printAccess( @@ -256,6 +254,70 @@ } } + private static String getTypePatternString(KeepTypePattern typePattern) { + if (typePattern.isAny()) { + return "***"; + } + return descriptorToJavaType(typePattern.getDescriptor()); + } + + private static String descriptorToJavaType(String descriptor) { + if (descriptor.isEmpty()) { + throw new KeepEdgeException("Invalid empty type descriptor"); + } + if (descriptor.length() == 1) { + return primitiveDescriptorToJavaType(descriptor.charAt(0)); + } + if (descriptor.charAt(0) == '[') { + return arrayDescriptorToJavaType(descriptor); + } + return classDescriptorToJavaType(descriptor); + } + + private static String primitiveDescriptorToJavaType(char descriptor) { + switch (descriptor) { + case 'Z': + return "boolean"; + case 'B': + return "byte"; + case 'S': + return "short"; + case 'I': + return "int"; + case 'J': + return "long"; + case 'F': + return "float"; + case 'D': + return "double"; + default: + throw new KeepEdgeException("Invalid primitive descriptor: " + descriptor); + } + } + + private static String classDescriptorToJavaType(String descriptor) { + int last = descriptor.length() - 1; + if (descriptor.charAt(0) != 'L' || descriptor.charAt(last) != ';') { + throw new KeepEdgeException("Invalid class descriptor: " + descriptor); + } + return descriptor.substring(1, last).replace('/', '.'); + } + + private static String arrayDescriptorToJavaType(String descriptor) { + for (int i = 0; i < descriptor.length(); i++) { + char c = descriptor.charAt(i); + if (c != '[') { + StringBuilder builder = new StringBuilder(); + builder.append(descriptorToJavaType(descriptor.substring(i))); + for (int j = 0; j < i; j++) { + builder.append("[]"); + } + return builder.toString(); + } + } + throw new KeepEdgeException("Invalid array descriptor: " + descriptor); + } + private static class ItemRule { private final KeepTarget target; private final KeepOptions options;
diff --git a/src/library_desugar/java/j$/nio/file/StandardWatchEventKinds.java b/src/library_desugar/java/j$/nio/file/StandardWatchEventKinds.java new file mode 100644 index 0000000..639b2c8 --- /dev/null +++ b/src/library_desugar/java/j$/nio/file/StandardWatchEventKinds.java
@@ -0,0 +1,13 @@ +// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package j$.nio.file; + +public class StandardWatchEventKinds implements j$.nio.file.WatchEvent.Kind { + + public static final j$.nio.file.WatchEvent.Kind ENTRY_CREATE = null; + public static final j$.nio.file.WatchEvent.Kind ENTRY_DELETE = null; + public static final j$.nio.file.WatchEvent.Kind ENTRY_MODIFY = null; + public static final j$.nio.file.WatchEvent.Kind OVERFLOW = null; +}
diff --git a/src/library_desugar/java/j$/nio/file/WatchEvent.java b/src/library_desugar/java/j$/nio/file/WatchEvent.java index fd977e9..41afd63 100644 --- a/src/library_desugar/java/j$/nio/file/WatchEvent.java +++ b/src/library_desugar/java/j$/nio/file/WatchEvent.java
@@ -13,4 +13,14 @@ public static j$.nio.file.WatchEvent<?> wrap_convert(java.nio.file.WatchEvent<?> option) { return null; } + + public static java.nio.file.WatchEvent.Kind wrap_convert(j$.nio.file.WatchEvent.Kind kind) { + return null; + } + + public static j$.nio.file.WatchEvent.Kind wrap_convert(java.nio.file.WatchEvent.Kind kind) { + return null; + } + + public static interface Kind {} }
diff --git a/src/library_desugar/java/java/nio/file/WatchEventKindConversions.java b/src/library_desugar/java/java/nio/file/WatchEventKindConversions.java new file mode 100644 index 0000000..b91a8cf --- /dev/null +++ b/src/library_desugar/java/java/nio/file/WatchEventKindConversions.java
@@ -0,0 +1,51 @@ +// 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 java.nio.file; + +/** + * The class java.nio.file.StandardWatchEventKinds.ENTRY_CREATE is final so it cannot be wrapped, + * but effectively it is used only for its 4 static fields, similarly to an enum while not being an + * enum. We convert them explicitely here when converting j$.nio.file.WatchEvent$Kind. + */ +public class WatchEventKindConversions { + + public static j$.nio.file.WatchEvent.Kind convert(java.nio.file.WatchEvent.Kind kind) { + if (kind == null) { + return null; + } + if (kind == java.nio.file.StandardWatchEventKinds.ENTRY_CREATE) { + return j$.nio.file.StandardWatchEventKinds.ENTRY_CREATE; + } + if (kind == java.nio.file.StandardWatchEventKinds.ENTRY_DELETE) { + return j$.nio.file.StandardWatchEventKinds.ENTRY_DELETE; + } + if (kind == java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY) { + return j$.nio.file.StandardWatchEventKinds.ENTRY_MODIFY; + } + if (kind == java.nio.file.StandardWatchEventKinds.OVERFLOW) { + return j$.nio.file.StandardWatchEventKinds.OVERFLOW; + } + return j$.nio.file.WatchEvent.wrap_convert(kind); + } + + public static java.nio.file.WatchEvent.Kind convert(j$.nio.file.WatchEvent.Kind kind) { + if (kind == null) { + return null; + } + if (kind == j$.nio.file.StandardWatchEventKinds.ENTRY_CREATE) { + return java.nio.file.StandardWatchEventKinds.ENTRY_CREATE; + } + if (kind == j$.nio.file.StandardWatchEventKinds.ENTRY_DELETE) { + return java.nio.file.StandardWatchEventKinds.ENTRY_DELETE; + } + if (kind == j$.nio.file.StandardWatchEventKinds.ENTRY_MODIFY) { + return java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY; + } + if (kind == j$.nio.file.StandardWatchEventKinds.OVERFLOW) { + return java.nio.file.StandardWatchEventKinds.OVERFLOW; + } + return j$.nio.file.WatchEvent.wrap_convert(kind); + } +}
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs_nio.json b/src/library_desugar/jdk11/desugar_jdk_libs_nio.json index 4bcf1a3..e963121 100644 --- a/src/library_desugar/jdk11/desugar_jdk_libs_nio.json +++ b/src/library_desugar/jdk11/desugar_jdk_libs_nio.json
@@ -283,7 +283,8 @@ }, "custom_conversion": { "java.nio.file.attribute.FileTime": "java.nio.file.attribute.FileAttributeConversions", - "java.nio.file.attribute.FileAttribute": "java.nio.file.attribute.FileAttributeConversions" + "java.nio.file.attribute.FileAttribute": "java.nio.file.attribute.FileAttributeConversions", + "java.nio.file.WatchEvent$Kind": "java.nio.file.WatchEventKindConversions" } }, { @@ -502,35 +503,11 @@ "sun.misc.DesugarUnsafe": { "jdk.internal.misc.Unsafe" : "j$.sun.misc.DesugarUnsafe" }, - "java.nio.file.attribute.": { - "j$.nio.file.attribute.": "java.nio.file.attribute." - }, - "java.nio.file.OpenOption": { - "j$.nio.file.OpenOption": "java.nio.file.OpenOption" - }, - "java.nio.file.StandardOpenOption": { - "j$.nio.file.StandardOpenOption": "java.nio.file.StandardOpenOption" - }, - "java.nio.file.LinkOption": { - "j$.nio.file.LinkOption": "java.nio.file.LinkOption" - }, - "java.nio.file.Files": { - "j$.nio.file.Files": "java.nio.file.Files" - }, - "java.nio.file.FileSystem": { - "j$.nio.file.FileSystem": "java.nio.file.FileSystem" - }, - "java.nio.file.spi.FileSystemProvider": { - "j$.nio.file.spi.FileSystemProvider": "java.nio.file.spi.FileSystemProvider" - }, - "java.nio.file.Path": { - "j$.nio.file.Path": "java.nio.file.Path" - }, "java.time.": { "j$.time.": "java.time." }, - "java.nio.file.WatchEvent": { - "j$.nio.file.WatchEvent": "java.nio.file.WatchEvent" + "java.nio.file.": { + "j$.nio.file.": "java.nio.file." } }, "retarget_method": {
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFrameVerifier.java b/src/main/java/com/android/tools/r8/cf/code/CfFrameVerifier.java index 44f3fde..58e9d2a 100644 --- a/src/main/java/com/android/tools/r8/cf/code/CfFrameVerifier.java +++ b/src/main/java/com/android/tools/r8/cf/code/CfFrameVerifier.java
@@ -117,6 +117,11 @@ if (appView.options().enableCheckAllInstructionsDuringStackMapVerification || instruction.canThrow()) { state = checkExceptionEdges(state, labelToFrameMap); + if (state.isError()) { + return fail( + CfCodeStackMapValidatingException.invalidStackMapForInstruction( + method, i, instruction, state.asError().getMessage(), appView)); + } } } eventConsumer.acceptInstructionState(instruction, state);
diff --git a/src/main/java/com/android/tools/r8/dex/DexParser.java b/src/main/java/com/android/tools/r8/dex/DexParser.java index 96a4596..e23386b 100644 --- a/src/main/java/com/android/tools/r8/dex/DexParser.java +++ b/src/main/java/com/android/tools/r8/dex/DexParser.java
@@ -1126,11 +1126,16 @@ boolean isValidOrder = stringIndex > prevStringIndex; assert isValidOrder : String.format( - "Out-of-order type ids (type #%s: `%s`, type #%s: `%s`)", + (indexedItems.getString(prevStringIndex).equals(indexedItems.getString(stringIndex)) + ? "Duplicate" + : "Out-of-order") + + " type ids (type #%s: `%s` string #%s, type #%s: `%s` string #%s)", index - 1, indexedItems.getString(prevStringIndex), + prevStringIndex, index, - indexedItems.getString(stringIndex)); + indexedItems.getString(stringIndex), + stringIndex); prevStringIndex = stringIndex; }
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 5b71a42..169a880 100644 --- a/src/main/java/com/android/tools/r8/graph/CfCode.java +++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -898,7 +898,8 @@ } @Override - public Code getCodeAsInlining(DexMethod caller, DexMethod callee, DexItemFactory factory) { + public Code getCodeAsInlining( + DexMethod caller, DexMethod callee, DexItemFactory factory, boolean isCalleeD8R8Synthesized) { Position callerPosition = SyntheticPosition.builder().setLine(0).setMethod(caller).build(); List<CfInstruction> newInstructions = new ArrayList<>(instructions.size() + 2); CfLabel firstLabel; @@ -916,15 +917,18 @@ newInstructions.add( new CfPosition( oldPosition.getLabel(), - oldPosition.getPosition().withOutermostCallerPosition(callerPosition))); + newInlineePosition( + callerPosition, oldPosition.getPosition(), isCalleeD8R8Synthesized))); } else { if (!instruction.isLabel() && !seenPosition) { Position preamblePosition = - SyntheticPosition.builder() - .setMethod(callee) - .setCallerPosition(callerPosition) - .setLine(0) - .build(); + isCalleeD8R8Synthesized + ? callerPosition + : SyntheticPosition.builder() + .setMethod(callee) + .setCallerPosition(callerPosition) + .setLine(0) + .build(); newInstructions.add(new CfPosition(firstLabel, preamblePosition)); seenPosition = true; }
diff --git a/src/main/java/com/android/tools/r8/graph/Code.java b/src/main/java/com/android/tools/r8/graph/Code.java index d0417b4..0475e2c 100644 --- a/src/main/java/com/android/tools/r8/graph/Code.java +++ b/src/main/java/com/android/tools/r8/graph/Code.java
@@ -12,6 +12,7 @@ import com.android.tools.r8.ir.code.IRCode; import com.android.tools.r8.ir.code.NumberGenerator; import com.android.tools.r8.ir.code.Position; +import com.android.tools.r8.ir.code.Position.PositionBuilder; import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions; import com.android.tools.r8.origin.Origin; import com.android.tools.r8.utils.RetracerForCodePrinting; @@ -176,7 +177,35 @@ return true; } - public Code getCodeAsInlining(DexMethod caller, DexMethod callee, DexItemFactory factory) { + public Code getCodeAsInlining(DexMethod caller, DexEncodedMethod callee, DexItemFactory factory) { + return getCodeAsInlining(caller, callee.getReference(), factory, callee.isD8R8Synthesized()); + } + + public Code getCodeAsInlining( + DexMethod caller, DexMethod callee, DexItemFactory factory, boolean isCalleeD8R8Synthesized) { throw new Unreachable(); } + + public Position newInlineePosition( + Position callerPosition, Position oldPosition, boolean isCalleeD8R8Synthesized) { + Position outermostCaller = oldPosition.getOutermostCaller(); + if (!isCalleeD8R8Synthesized) { + return oldPosition.withOutermostCallerPosition(callerPosition); + } + // We can replace the position since the callee was synthesized by the compiler, however, if + // the position carries special information we need to copy it. + if (!outermostCaller.isOutline() && !outermostCaller.isRemoveInnerFramesIfThrowingNpe()) { + return oldPosition.replacePosition(outermostCaller, callerPosition); + } + assert !callerPosition.isOutline(); + PositionBuilder<?, ?> positionBuilder = + outermostCaller + .builderWithCopy() + .setMethod(callerPosition.getMethod()) + .setLine(callerPosition.getLine()); + if (callerPosition.isRemoveInnerFramesIfThrowingNpe()) { + positionBuilder.setRemoveInnerFramesIfThrowingNpe(true); + } + return oldPosition.replacePosition(outermostCaller, positionBuilder.build()); + } }
diff --git a/src/main/java/com/android/tools/r8/graph/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java index 048e01d..8ca811b 100644 --- a/src/main/java/com/android/tools/r8/graph/DexCode.java +++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -279,7 +279,8 @@ } @Override - public Code getCodeAsInlining(DexMethod caller, DexMethod callee, DexItemFactory factory) { + public Code getCodeAsInlining( + DexMethod caller, DexMethod callee, DexItemFactory factory, boolean isCalleeD8R8Synthesized) { return new DexCode( registerSize, incomingRegisterSize, @@ -287,11 +288,11 @@ instructions, tries, handlers, - debugInfoAsInlining(caller, callee, factory)); + debugInfoAsInlining(caller, callee, isCalleeD8R8Synthesized, factory)); } private DexDebugInfo debugInfoAsInlining( - DexMethod caller, DexMethod callee, DexItemFactory factory) { + DexMethod caller, DexMethod callee, boolean isCalleeD8R8Synthesized, DexItemFactory factory) { Position callerPosition = SyntheticPosition.builder().setLine(0).setMethod(caller).build(); EventBasedDebugInfo eventBasedInfo = DexDebugInfo.convertToEventBased(this, factory); if (eventBasedInfo == null) { @@ -299,11 +300,13 @@ // This is consistent with the building IR for inlining which will always ensure the method // has a position. Position preamblePosition = - SyntheticPosition.builder() - .setMethod(callee) - .setCallerPosition(callerPosition) - .setLine(0) - .build(); + isCalleeD8R8Synthesized + ? callerPosition + : SyntheticPosition.builder() + .setMethod(callee) + .setCallerPosition(callerPosition) + .setLine(0) + .build(); return new EventBasedDebugInfo( 0, new DexString[callee.getArity()], @@ -321,14 +324,17 @@ int i = 0; newEvents[i++] = new SetPositionFrame( - frameBuilder.setMethod(callee).setCallerPosition(callerPosition).build()); + isCalleeD8R8Synthesized + ? callerPosition + : frameBuilder.setMethod(callee).setCallerPosition(callerPosition).build()); for (DexDebugEvent event : oldEvents) { if (event instanceof SetPositionFrame) { SetPositionFrame oldFrame = (SetPositionFrame) event; assert oldFrame.getPosition() != null; newEvents[i++] = new SetPositionFrame( - oldFrame.getPosition().withOutermostCallerPosition(callerPosition)); + newInlineePosition( + callerPosition, oldFrame.getPosition(), isCalleeD8R8Synthesized)); } else { newEvents[i++] = event; } @@ -554,7 +560,7 @@ .append("(warning: has unhandled debug events @ pc:") .append(debugInfo.address) .append(", line:") - .append(debugInfo.line); + .append(debugInfo.getPosition().getLine()); } else { builder.append("(has debug events past last pc)\n"); }
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEntry.java b/src/main/java/com/android/tools/r8/graph/DexDebugEntry.java index f89353c..6679383 100644 --- a/src/main/java/com/android/tools/r8/graph/DexDebugEntry.java +++ b/src/main/java/com/android/tools/r8/graph/DexDebugEntry.java
@@ -4,60 +4,38 @@ package com.android.tools.r8.graph; import com.android.tools.r8.ir.code.Position; -import com.android.tools.r8.ir.code.Position.OutlineCallerPosition; -import com.android.tools.r8.ir.code.Position.OutlineCallerPosition.OutlineCallerPositionBuilder; -import com.android.tools.r8.ir.code.Position.OutlinePosition; -import com.android.tools.r8.ir.code.Position.PositionBuilder; -import com.android.tools.r8.ir.code.Position.SourcePosition; -import com.android.tools.r8.utils.Int2StructuralItemArrayMap; import com.android.tools.r8.utils.StringUtils; import com.google.common.collect.ImmutableMap; import java.util.Map; import java.util.SortedSet; import java.util.TreeSet; -import java.util.function.Function; public class DexDebugEntry { public final boolean lineEntry; public final int address; - public final int line; public final DexString sourceFile; public final boolean prologueEnd; public final boolean epilogueBegin; public final Map<Integer, DebugLocalInfo> locals; - public final DexMethod method; - public final Position callerPosition; - public final boolean isOutline; - public final DexMethod outlineCallee; - public final Int2StructuralItemArrayMap<Position> outlineCallerPositions; + private final Position position; public DexDebugEntry( boolean lineEntry, int address, - int line, DexString sourceFile, boolean prologueEnd, boolean epilogueBegin, ImmutableMap<Integer, DebugLocalInfo> locals, - DexMethod method, - Position callerPosition, - boolean isOutline, - DexMethod outlineCallee, - Int2StructuralItemArrayMap<Position> outlineCallerPositions) { + Position position) { this.lineEntry = lineEntry; this.address = address; - this.line = line; this.sourceFile = sourceFile; this.prologueEnd = prologueEnd; this.epilogueBegin = epilogueBegin; this.locals = locals; - this.method = method; - assert method != null; - this.callerPosition = callerPosition; - this.isOutline = isOutline; - this.outlineCallee = outlineCallee; - this.outlineCallerPositions = outlineCallerPositions; + this.position = position; + assert position != null; } @Override @@ -74,24 +52,7 @@ if (sourceFile != null) { builder.append(", file ").append(sourceFile); } - builder.append(", line ").append(line); - if (callerPosition != null) { - builder.append(":").append(method.name); - Position caller = callerPosition; - while (caller != null) { - builder.append(";").append(caller.getLine()).append(":").append(caller.getMethod().name); - caller = caller.getCallerPosition(); - } - } - if (isOutline) { - builder.append(", isOutline = true"); - } - if (outlineCallee != null) { - builder.append(", outlineCallee = ").append(outlineCallee); - } - if (outlineCallerPositions != null) { - builder.append(", outlineCallerPositions = ").append(outlineCallerPositions); - } + builder.append(", ").append(position); if (prologueEnd) { builder.append(", prologue_end = true"); } @@ -115,22 +76,11 @@ return builder.toString(); } - public Position toPosition(Function<Position, Position> canonicalizeCallerPosition) { - PositionBuilder<?, ?> positionBuilder; - if (outlineCallee != null) { - OutlineCallerPositionBuilder outlineCallerPositionBuilder = - OutlineCallerPosition.builder().setOutlineCallee(outlineCallee).setIsOutline(isOutline); - outlineCallerPositions.forEach(outlineCallerPositionBuilder::addOutlinePosition); - positionBuilder = outlineCallerPositionBuilder; - } else if (isOutline) { - positionBuilder = OutlinePosition.builder(); - } else { - positionBuilder = SourcePosition.builder().setFile(sourceFile); - } - return positionBuilder - .setLine(line) - .setMethod(method) - .setCallerPosition(canonicalizeCallerPosition.apply(callerPosition)) - .build(); + public Position getPosition() { + return position; + } + + public int getLine() { + return position.getLine(); } }
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEntryBuilder.java b/src/main/java/com/android/tools/r8/graph/DexDebugEntryBuilder.java index 5a4fd84..e323ab8 100644 --- a/src/main/java/com/android/tools/r8/graph/DexDebugEntryBuilder.java +++ b/src/main/java/com/android/tools/r8/graph/DexDebugEntryBuilder.java
@@ -46,7 +46,6 @@ private boolean epilogueBegin = false; private final Map<Integer, LocalEntry> locals = new HashMap<>(); private final Int2ReferenceMap<DebugLocalInfo> arguments = new Int2ReferenceArrayMap<>(); - private final DexMethod method; // Delayed construction of an entry. Is finalized once locals information has been collected. private DexDebugEntry pending = null; @@ -60,13 +59,11 @@ public DexDebugEntryBuilder(int startLine, DexMethod method) { assert method != null; - this.method = method; positionState = new DexDebugPositionState(startLine, method); } public DexDebugEntryBuilder(DexEncodedMethod method, DexItemFactory factory) { assert method != null && method.getReference() != null; - this.method = method.getReference(); DexCode code = method.getCode().asDexCode(); EventBasedDebugInfo info = code.getDebugInfo().asEventBasedInfo(); // Only event based debug info supports conversion to entries. @@ -164,31 +161,21 @@ new DexDebugEntry( pending.lineEntry, pending.address, - pending.line, pending.sourceFile, pending.prologueEnd, pending.epilogueBegin, getLocals(), - pending.method, - pending.callerPosition, - pending.isOutline, - pending.outlineCallee, - pending.outlineCallerPositions)); + pending.getPosition())); } pending = new DexDebugEntry( lineEntry, positionState.getCurrentPc(), - positionState.getCurrentLine(), - positionState.getCurrentFile(), + null, prologueEnd, epilogueBegin, null, - positionState.getCurrentMethod(), - positionState.getCurrentCallerPosition(), - positionState.isOutline(), - positionState.getOutlineCallee(), - positionState.getOutlineCallerPositions()); + positionState.getPosition()); prologueEnd = false; epilogueBegin = false; }
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugPositionState.java b/src/main/java/com/android/tools/r8/graph/DexDebugPositionState.java index 10a06d5..96eb206 100644 --- a/src/main/java/com/android/tools/r8/graph/DexDebugPositionState.java +++ b/src/main/java/com/android/tools/r8/graph/DexDebugPositionState.java
@@ -15,7 +15,8 @@ import com.android.tools.r8.graph.DexDebugEvent.SetPrologueEnd; import com.android.tools.r8.graph.DexDebugEvent.StartLocal; import com.android.tools.r8.ir.code.Position; -import com.android.tools.r8.utils.Int2StructuralItemArrayMap; +import com.android.tools.r8.ir.code.Position.SourcePosition; +import com.android.tools.r8.ir.code.Position.SyntheticPosition; /** * State machine to process and accumulate position-related DexDebugEvents. Clients should retrieve @@ -25,12 +26,8 @@ private int currentPc = 0; private int currentLine; - private DexString currentFile = null; - private DexMethod currentMethod; - private Position currentCallerPosition = null; - private boolean isOutline; - private DexMethod outlineCallee; - private Int2StructuralItemArrayMap<Position> outlineCallerPositions; + protected DexMethod currentMethod; + protected Position currentPosition; public DexDebugPositionState(int startLine, DexMethod method) { currentLine = startLine; @@ -53,10 +50,7 @@ assert setPositionFrame.getPosition() != null; Position position = setPositionFrame.getPosition(); currentMethod = position.getMethod(); - currentCallerPosition = position.getCallerPosition(); - outlineCallee = position.getOutlineCallee(); - outlineCallerPositions = position.getOutlinePositions(); - isOutline = position.isOutline(); + currentPosition = position; } @Override @@ -68,7 +62,7 @@ @Override public void visit(SetFile setFile) { - currentFile = setFile.fileName; + // Empty. } @Override @@ -104,33 +98,14 @@ return currentLine; } - public DexString getCurrentFile() { - return currentFile; - } - - public DexMethod getCurrentMethod() { - return currentMethod; - } - - public Position getCurrentCallerPosition() { - return currentCallerPosition; - } - - public boolean isOutline() { - return isOutline; - } - - public DexMethod getOutlineCallee() { - return outlineCallee; - } - - public Int2StructuralItemArrayMap<Position> getOutlineCallerPositions() { - return outlineCallerPositions; - } - - public void resetOutlineInformation() { - isOutline = false; - outlineCallee = null; - outlineCallerPositions = null; + public Position getPosition() { + if (currentPosition == null) { + return (getCurrentLine() > 0 ? SourcePosition.builder() : SyntheticPosition.builder()) + .setLine(getCurrentLine()) + .setMethod(currentMethod) + .build(); + } else { + return currentPosition.builderWithCopy().setLine(getCurrentLine()).build(); + } } }
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java index 888bbfe..94b8840 100644 --- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java +++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -2289,7 +2289,7 @@ private final Set<DexString> varHandleSetMethods = createStrings(setString, "setOpaque", "setRelease", "setVolatile"); - private final Set<DexString> varHandleCompareAndSetMethods = + public final Set<DexString> varHandleCompareAndSetMethodNames = createStrings( compareAndSetString, "weakCompareAndSet", @@ -2308,7 +2308,7 @@ result = createMethod(varHandleType, signature, invokeProto.name); } else if (varHandleSetMethods.contains(invokeProto.name)) { result = createMethod(varHandleType, setSignature, invokeProto.name); - } else if (varHandleCompareAndSetMethods.contains(invokeProto.name)) { + } else if (varHandleCompareAndSetMethodNames.contains(invokeProto.name)) { result = createMethod(varHandleType, compareAndSetSignature, invokeProto.name); } } @@ -2333,7 +2333,7 @@ if (invokeProto.holder == varHandleType) { return varHandleMethods.contains(invokeProto.name) || varHandleSetMethods.contains(invokeProto.name) - || varHandleCompareAndSetMethods.contains(invokeProto.name); + || varHandleCompareAndSetMethodNames.contains(invokeProto.name); } return false; }
diff --git a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java index 3abc2e4..d98d0ea 100644 --- a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java +++ b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
@@ -175,7 +175,7 @@ } @Override - public Code getCodeAsInlining(DexMethod caller, DexMethod callee, DexItemFactory factory) { + public Code getCodeAsInlining(DexMethod caller, DexEncodedMethod callee, DexItemFactory factory) { return asCfCode().getCodeAsInlining(caller, callee, factory); }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Position.java b/src/main/java/com/android/tools/r8/ir/code/Position.java index 653e20e..5e88574 100644 --- a/src/main/java/com/android/tools/r8/ir/code/Position.java +++ b/src/main/java/com/android/tools/r8/ir/code/Position.java
@@ -46,10 +46,6 @@ return false; } - public boolean isAdditionalMappingInfoPosition() { - return false; - } - public boolean isRemoveInnerFramesIfThrowingNpe() { return removeInnerFramesIfThrowingNpe; } @@ -201,7 +197,8 @@ return HashCodeVisitor.run(this); } - private String toString(boolean forceMethod) { + @Override + public String toString() { if (isNone()) { return "--"; } @@ -210,7 +207,7 @@ builder.append(getFile()).append(":"); } builder.append("#").append(line); - if (method != null && (forceMethod || callerPosition != null)) { + if (method != null && callerPosition != null) { builder.append(":").append(method.name); } if (callerPosition != null) { @@ -220,14 +217,18 @@ caller = caller.callerPosition; } } + if (isOutline()) { + builder.append(", isOutline = true"); + } + if (getOutlineCallee() != null) { + builder.append(", outlineCallee = ").append(getOutlineCallee()); + } + if (getOutlinePositions() != null) { + builder.append(", outlineCallerPositions = ").append(getOutlinePositions()); + } return builder.toString(); } - @Override - public String toString() { - return toString(false); - } - public abstract PositionBuilder<?, ?> builderWithCopy(); public abstract static class PositionBuilder<
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java index 3fbcdc8..ea0f655 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
@@ -259,10 +259,15 @@ private Position getCanonicalPositionAppendCaller(DexDebugEntry entry) { // If this instruction has already been inlined then this.method must be the outermost caller. - assert entry.callerPosition == null - || entry.callerPosition.getOutermostCaller().getMethod() == originalMethod; + Position position = entry.getPosition(); + assert !position.hasCallerPosition() + || position.getOutermostCaller().getMethod() == originalMethod; return canonicalPositions.getCanonical( - entry.toPosition(canonicalPositions::canonicalizeCallerPosition)); + position + .builderWithCopy() + .setCallerPosition( + canonicalPositions.canonicalizeCallerPosition(position.getCallerPosition())) + .build()); } @Override
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java index 8d2c8c6..adf23fa 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java
@@ -210,7 +210,7 @@ definition .getCode() .getCodeAsInlining( - companion.getReference(), method.getReference(), appView.dexItemFactory()); + companion.getReference(), method.getDefinition(), appView.dexItemFactory()); if (!definition.isStatic()) { DexEncodedMethod.setDebugInfoWithFakeThisParameter( code, companion.getReference().getArity(), appView);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/varhandle/VarHandleDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/varhandle/VarHandleDesugaring.java index 9ed7493..ae232bd 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/varhandle/VarHandleDesugaring.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/varhandle/VarHandleDesugaring.java
@@ -379,7 +379,7 @@ } private DexType objectOrPrimitiveReturnType(DexType type) { - return type.isPrimitiveType() || type.isVoidType() ? type : factory.objectType; + return isPrimitiveThatIsNotBoxed(type) || type.isVoidType() ? type : factory.objectType; } private DexType objectOrPrimitiveParameterType(DexType type) { @@ -456,13 +456,23 @@ } } assert newParameters.size() == proto.parameters.size(); - // TODO(b/247076137): Also convert return type if reference type and not Object?. + DexString name = invoke.getMethod().getName(); DexProto newProto = - factory.createProto(objectOrPrimitiveReturnType(proto.returnType), newParameters); - DexMethod newMethod = - factory.createMethod(factory.desugarVarHandleType, newProto, invoke.getMethod().getName()); + factory.createProto( + factory.polymorphicMethods.varHandleCompareAndSetMethodNames.contains(name) + ? proto.returnType + : objectOrPrimitiveReturnType(proto.returnType), + newParameters); + DexMethod newMethod = factory.createMethod(factory.desugarVarHandleType, newProto, name); builder.add(new CfInvoke(Opcodes.INVOKEVIRTUAL, newMethod, false)); - if (proto.returnType.isClassType() + if (proto.returnType.isPrimitiveType() && !newProto.returnType.isPrimitiveType()) { + assert proto.returnType.isPrimitiveType(); + assert newProto.returnType == factory.objectType; + builder.add(new CfCheckCast(factory.getBoxedForPrimitiveType(proto.returnType))); + builder.add( + new CfInvoke( + Opcodes.INVOKEVIRTUAL, factory.getUnboxPrimitiveMethod(proto.returnType), false)); + } else if (proto.returnType.isClassType() && proto.returnType != factory.objectType && proto.returnType != factory.voidType) { builder.add(new CfCheckCast(proto.returnType));
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/varhandle/VarHandleDesugaringMethods.java b/src/main/java/com/android/tools/r8/ir/desugar/varhandle/VarHandleDesugaringMethods.java index e32fa57..112f246 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/varhandle/VarHandleDesugaringMethods.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/varhandle/VarHandleDesugaringMethods.java
@@ -52,6 +52,7 @@ public static void registerSynthesizedCodeReferences(DexItemFactory factory) { factory.createSynthesizedType("Lcom/android/tools/r8/DesugarVarHandle;"); factory.createSynthesizedType("Ljava/lang/Byte;"); + factory.createSynthesizedType("Ljava/lang/ClassCastException;"); factory.createSynthesizedType("Ljava/lang/Integer;"); factory.createSynthesizedType("Ljava/lang/Long;"); factory.createSynthesizedType("Ljava/lang/RuntimeException;"); @@ -98,6 +99,11 @@ .setAccessFlags(FieldAccessFlags.createPublicFinalSynthetic()) .disableAndroidApiLevelCheck() .build())); + DexMethod toIntIfPossible = + factory.createMethod( + builder.getType(), + factory.createProto(factory.intType, factory.objectType, factory.booleanType), + factory.createString("toIntIfPossible")); DexMethod get = factory.createMethod( builder.getType(), @@ -120,11 +126,6 @@ builder.getType(), factory.createProto(factory.voidType, factory.objectType, factory.intType), factory.createString("set")); - DexMethod toLongIfPossible = - factory.createMethod( - builder.getType(), - factory.createProto(factory.longType, factory.objectType), - factory.createString("toLongIfPossible")); DexMethod constructor_3 = factory.createMethod( builder.getType(), @@ -145,6 +146,11 @@ factory.createProto( factory.booleanType, factory.objectType, factory.longType, factory.longType), factory.createString("compareAndSet")); + DexMethod toLongIfPossible = + factory.createMethod( + builder.getType(), + factory.createProto(factory.longType, factory.objectType, factory.booleanType), + factory.createString("toLongIfPossible")); DexMethod set = factory.createMethod( builder.getType(), @@ -167,11 +173,6 @@ builder.getType(), factory.createProto(factory.intType, factory.objectType), factory.createString("get")); - DexMethod toIntIfPossible = - factory.createMethod( - builder.getType(), - factory.createProto(factory.intType, factory.objectType), - factory.createString("toIntIfPossible")); DexMethod getLong = factory.createMethod( builder.getType(), @@ -198,6 +199,14 @@ builder.setVirtualMethods( ImmutableList.of( DexEncodedMethod.syntheticBuilder() + .setMethod(toIntIfPossible) + .setAccessFlags( + MethodAccessFlags.fromSharedAccessFlags( + Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, false)) + .setCode(DesugarVarHandle_toIntIfPossible(factory, toIntIfPossible)) + .disableAndroidApiLevelCheck() + .build(), + DexEncodedMethod.syntheticBuilder() .setMethod(get) .setAccessFlags( MethodAccessFlags.fromSharedAccessFlags( @@ -222,14 +231,6 @@ .disableAndroidApiLevelCheck() .build(), DexEncodedMethod.syntheticBuilder() - .setMethod(toLongIfPossible) - .setAccessFlags( - MethodAccessFlags.fromSharedAccessFlags( - Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, false)) - .setCode(DesugarVarHandle_toLongIfPossible(factory, toLongIfPossible)) - .disableAndroidApiLevelCheck() - .build(), - DexEncodedMethod.syntheticBuilder() .setMethod(setLong) .setAccessFlags( MethodAccessFlags.fromSharedAccessFlags( @@ -246,6 +247,14 @@ .disableAndroidApiLevelCheck() .build(), DexEncodedMethod.syntheticBuilder() + .setMethod(toLongIfPossible) + .setAccessFlags( + MethodAccessFlags.fromSharedAccessFlags( + Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, false)) + .setCode(DesugarVarHandle_toLongIfPossible(factory, toLongIfPossible)) + .disableAndroidApiLevelCheck() + .build(), + DexEncodedMethod.syntheticBuilder() .setMethod(set) .setAccessFlags( MethodAccessFlags.fromSharedAccessFlags( @@ -280,14 +289,6 @@ .disableAndroidApiLevelCheck() .build(), DexEncodedMethod.syntheticBuilder() - .setMethod(toIntIfPossible) - .setAccessFlags( - MethodAccessFlags.fromSharedAccessFlags( - Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, false)) - .setCode(DesugarVarHandle_toIntIfPossible(factory, toIntIfPossible)) - .disableAndroidApiLevelCheck() - .build(), - DexEncodedMethod.syntheticBuilder() .setMethod(getLong) .setAccessFlags( MethodAccessFlags.fromSharedAccessFlags( @@ -659,7 +660,7 @@ CfLabel label9 = new CfLabel(); return new CfCode( method.holder, - 8, + 9, 4, ImmutableList.of( label0, @@ -691,21 +692,23 @@ factory.createString("offset"))), new CfLoad(ValueType.OBJECT, 0), new CfLoad(ValueType.OBJECT, 2), + new CfConstNumber(0, ValueType.INT), label2, new CfInvoke( 182, factory.createMethod( factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"), - factory.createProto(factory.intType, factory.objectType), + factory.createProto(factory.intType, factory.objectType, factory.booleanType), factory.createString("toIntIfPossible")), false), new CfLoad(ValueType.OBJECT, 0), new CfLoad(ValueType.OBJECT, 3), + new CfConstNumber(0, ValueType.INT), new CfInvoke( 182, factory.createMethod( factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"), - factory.createProto(factory.intType, factory.objectType), + factory.createProto(factory.intType, factory.objectType, factory.booleanType), factory.createString("toIntIfPossible")), false), label3, @@ -761,21 +764,23 @@ factory.createString("offset"))), new CfLoad(ValueType.OBJECT, 0), new CfLoad(ValueType.OBJECT, 2), + new CfConstNumber(0, ValueType.INT), label6, new CfInvoke( 182, factory.createMethod( factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"), - factory.createProto(factory.longType, factory.objectType), + factory.createProto(factory.longType, factory.objectType, factory.booleanType), factory.createString("toLongIfPossible")), false), new CfLoad(ValueType.OBJECT, 0), new CfLoad(ValueType.OBJECT, 3), + new CfConstNumber(0, ValueType.INT), new CfInvoke( 182, factory.createMethod( factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"), - factory.createProto(factory.longType, factory.objectType), + factory.createProto(factory.longType, factory.objectType, factory.booleanType), factory.createString("toLongIfPossible")), false), label7, @@ -1259,9 +1264,13 @@ public static CfCode DesugarVarHandle_getInt(DexItemFactory factory, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); + CfLabel label2 = new CfLabel(); + CfLabel label3 = new CfLabel(); + CfLabel label4 = new CfLabel(); + CfLabel label5 = new CfLabel(); return new CfCode( method.holder, - 4, + 5, 2, ImmutableList.of( label0, @@ -1269,6 +1278,19 @@ new CfInstanceFieldRead( factory.createField( factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"), + factory.classType, + factory.createString("type"))), + new CfStaticFieldRead( + factory.createField( + factory.createType("Ljava/lang/Integer;"), + factory.classType, + factory.createString("TYPE"))), + new CfIfCmp(If.Type.NE, ValueType.OBJECT, label2), + label1, + new CfLoad(ValueType.OBJECT, 0), + new CfInstanceFieldRead( + factory.createField( + factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"), factory.createType("Lsun/misc/Unsafe;"), factory.createString("U"))), new CfLoad(ValueType.OBJECT, 1), @@ -1286,7 +1308,90 @@ factory.createString("getInt")), false), new CfReturn(ValueType.INT), - label1), + label2, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1}, + new FrameType[] { + FrameType.initializedNonNullReference( + factory.createType("Lcom/android/tools/r8/DesugarVarHandle;")), + FrameType.initializedNonNullReference(factory.objectType) + })), + new CfLoad(ValueType.OBJECT, 0), + new CfInstanceFieldRead( + factory.createField( + factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"), + factory.classType, + factory.createString("type"))), + new CfStaticFieldRead( + factory.createField( + factory.createType("Ljava/lang/Long;"), + factory.classType, + factory.createString("TYPE"))), + new CfIfCmp(If.Type.NE, ValueType.OBJECT, label4), + label3, + new CfLoad(ValueType.OBJECT, 0), + new CfInstanceFieldRead( + factory.createField( + factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"), + factory.createType("Lsun/misc/Unsafe;"), + factory.createString("U"))), + new CfLoad(ValueType.OBJECT, 1), + new CfLoad(ValueType.OBJECT, 0), + new CfInstanceFieldRead( + factory.createField( + factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"), + factory.longType, + factory.createString("offset"))), + new CfInvoke( + 182, + factory.createMethod( + factory.createType("Lsun/misc/Unsafe;"), + factory.createProto(factory.longType, factory.objectType, factory.longType), + factory.createString("getLong")), + false), + new CfNumberConversion(NumericType.LONG, NumericType.INT), + new CfReturn(ValueType.INT), + label4, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1}, + new FrameType[] { + FrameType.initializedNonNullReference( + factory.createType("Lcom/android/tools/r8/DesugarVarHandle;")), + FrameType.initializedNonNullReference(factory.objectType) + })), + new CfLoad(ValueType.OBJECT, 0), + new CfLoad(ValueType.OBJECT, 0), + new CfInstanceFieldRead( + factory.createField( + factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"), + factory.createType("Lsun/misc/Unsafe;"), + factory.createString("U"))), + new CfLoad(ValueType.OBJECT, 1), + new CfLoad(ValueType.OBJECT, 0), + new CfInstanceFieldRead( + factory.createField( + factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"), + factory.longType, + factory.createString("offset"))), + new CfInvoke( + 182, + factory.createMethod( + factory.createType("Lsun/misc/Unsafe;"), + factory.createProto(factory.objectType, factory.objectType, factory.longType), + factory.createString("getObject")), + false), + new CfConstNumber(1, ValueType.INT), + new CfInvoke( + 182, + factory.createMethod( + factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"), + factory.createProto(factory.intType, factory.objectType, factory.booleanType), + factory.createString("toIntIfPossible")), + false), + new CfReturn(ValueType.INT), + label5), ImmutableList.of(), ImmutableList.of()); } @@ -1294,9 +1399,13 @@ public static CfCode DesugarVarHandle_getLong(DexItemFactory factory, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); + CfLabel label2 = new CfLabel(); + CfLabel label3 = new CfLabel(); + CfLabel label4 = new CfLabel(); + CfLabel label5 = new CfLabel(); return new CfCode( method.holder, - 4, + 5, 2, ImmutableList.of( label0, @@ -1304,6 +1413,19 @@ new CfInstanceFieldRead( factory.createField( factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"), + factory.classType, + factory.createString("type"))), + new CfStaticFieldRead( + factory.createField( + factory.createType("Ljava/lang/Long;"), + factory.classType, + factory.createString("TYPE"))), + new CfIfCmp(If.Type.NE, ValueType.OBJECT, label2), + label1, + new CfLoad(ValueType.OBJECT, 0), + new CfInstanceFieldRead( + factory.createField( + factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"), factory.createType("Lsun/misc/Unsafe;"), factory.createString("U"))), new CfLoad(ValueType.OBJECT, 1), @@ -1321,7 +1443,90 @@ factory.createString("getLong")), false), new CfReturn(ValueType.LONG), - label1), + label2, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1}, + new FrameType[] { + FrameType.initializedNonNullReference( + factory.createType("Lcom/android/tools/r8/DesugarVarHandle;")), + FrameType.initializedNonNullReference(factory.objectType) + })), + new CfLoad(ValueType.OBJECT, 0), + new CfInstanceFieldRead( + factory.createField( + factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"), + factory.classType, + factory.createString("type"))), + new CfStaticFieldRead( + factory.createField( + factory.createType("Ljava/lang/Integer;"), + factory.classType, + factory.createString("TYPE"))), + new CfIfCmp(If.Type.NE, ValueType.OBJECT, label4), + label3, + new CfLoad(ValueType.OBJECT, 0), + new CfInstanceFieldRead( + factory.createField( + factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"), + factory.createType("Lsun/misc/Unsafe;"), + factory.createString("U"))), + new CfLoad(ValueType.OBJECT, 1), + new CfLoad(ValueType.OBJECT, 0), + new CfInstanceFieldRead( + factory.createField( + factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"), + factory.longType, + factory.createString("offset"))), + new CfInvoke( + 182, + factory.createMethod( + factory.createType("Lsun/misc/Unsafe;"), + factory.createProto(factory.intType, factory.objectType, factory.longType), + factory.createString("getInt")), + false), + new CfNumberConversion(NumericType.INT, NumericType.LONG), + new CfReturn(ValueType.LONG), + label4, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1}, + new FrameType[] { + FrameType.initializedNonNullReference( + factory.createType("Lcom/android/tools/r8/DesugarVarHandle;")), + FrameType.initializedNonNullReference(factory.objectType) + })), + new CfLoad(ValueType.OBJECT, 0), + new CfLoad(ValueType.OBJECT, 0), + new CfInstanceFieldRead( + factory.createField( + factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"), + factory.createType("Lsun/misc/Unsafe;"), + factory.createString("U"))), + new CfLoad(ValueType.OBJECT, 1), + new CfLoad(ValueType.OBJECT, 0), + new CfInstanceFieldRead( + factory.createField( + factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"), + factory.longType, + factory.createString("offset"))), + new CfInvoke( + 182, + factory.createMethod( + factory.createType("Lsun/misc/Unsafe;"), + factory.createProto(factory.objectType, factory.objectType, factory.longType), + factory.createString("getObject")), + false), + new CfConstNumber(1, ValueType.INT), + new CfInvoke( + 182, + factory.createMethod( + factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"), + factory.createProto(factory.longType, factory.objectType, factory.booleanType), + factory.createString("toLongIfPossible")), + false), + new CfReturn(ValueType.LONG), + label5), ImmutableList.of(), ImmutableList.of()); } @@ -1357,11 +1562,12 @@ new CfLoad(ValueType.OBJECT, 1), new CfLoad(ValueType.OBJECT, 0), new CfLoad(ValueType.OBJECT, 2), + new CfConstNumber(0, ValueType.INT), new CfInvoke( 182, factory.createMethod( factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"), - factory.createProto(factory.intType, factory.objectType), + factory.createProto(factory.intType, factory.objectType, factory.booleanType), factory.createString("toIntIfPossible")), false), new CfInvoke( @@ -1399,11 +1605,12 @@ new CfLoad(ValueType.OBJECT, 1), new CfLoad(ValueType.OBJECT, 0), new CfLoad(ValueType.OBJECT, 2), + new CfConstNumber(0, ValueType.INT), new CfInvoke( 182, factory.createMethod( factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"), - factory.createProto(factory.longType, factory.objectType), + factory.createProto(factory.longType, factory.objectType, factory.booleanType), factory.createString("toLongIfPossible")), false), new CfInvoke( @@ -1608,6 +1815,8 @@ CfLabel label2 = new CfLabel(); CfLabel label3 = new CfLabel(); CfLabel label4 = new CfLabel(); + CfLabel label5 = new CfLabel(); + CfLabel label6 = new CfLabel(); return new CfCode( method.holder, 6, @@ -1649,7 +1858,7 @@ factory.voidType, factory.objectType, factory.longType, factory.longType), factory.createString("putLong")), false), - new CfGoto(label3), + new CfGoto(label5), label2, new CfFrame( new Int2ObjectAVLTreeMap<>( @@ -1662,6 +1871,19 @@ FrameType.longHighType() })), new CfLoad(ValueType.OBJECT, 0), + new CfInstanceFieldRead( + factory.createField( + factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"), + factory.classType, + factory.createString("type"))), + new CfStaticFieldRead( + factory.createField( + factory.createType("Ljava/lang/Integer;"), + factory.classType, + factory.createString("TYPE"))), + new CfIfCmp(If.Type.NE, ValueType.OBJECT, label4), + label3, + new CfLoad(ValueType.OBJECT, 0), new CfInvoke( 182, factory.createMethod( @@ -1670,7 +1892,47 @@ factory.createString("desugarWrongMethodTypeException")), false), new CfThrow(), - label3, + label4, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2, 3}, + new FrameType[] { + FrameType.initializedNonNullReference( + factory.createType("Lcom/android/tools/r8/DesugarVarHandle;")), + FrameType.initializedNonNullReference(factory.objectType), + FrameType.longType(), + FrameType.longHighType() + })), + new CfLoad(ValueType.OBJECT, 0), + new CfInstanceFieldRead( + factory.createField( + factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"), + factory.createType("Lsun/misc/Unsafe;"), + factory.createString("U"))), + new CfLoad(ValueType.OBJECT, 1), + new CfLoad(ValueType.OBJECT, 0), + new CfInstanceFieldRead( + factory.createField( + factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"), + factory.longType, + factory.createString("offset"))), + new CfLoad(ValueType.LONG, 2), + new CfInvoke( + 184, + factory.createMethod( + factory.createType("Ljava/lang/Long;"), + factory.createProto(factory.createType("Ljava/lang/Long;"), factory.longType), + factory.createString("valueOf")), + false), + new CfInvoke( + 182, + factory.createMethod( + factory.createType("Lsun/misc/Unsafe;"), + factory.createProto( + factory.voidType, factory.objectType, factory.longType, factory.objectType), + factory.createString("putObject")), + false), + label5, new CfFrame( new Int2ObjectAVLTreeMap<>( new int[] {0, 1, 2, 3}, @@ -1682,7 +1944,7 @@ FrameType.longHighType() })), new CfReturnVoid(), - label4), + label6), ImmutableList.of(), ImmutableList.of()); } @@ -1698,10 +1960,12 @@ CfLabel label7 = new CfLabel(); CfLabel label8 = new CfLabel(); CfLabel label9 = new CfLabel(); + CfLabel label10 = new CfLabel(); + CfLabel label11 = new CfLabel(); return new CfCode( method.holder, - 1, 2, + 3, ImmutableList.of( label0, new CfLoad(ValueType.OBJECT, 1), @@ -1721,11 +1985,12 @@ label2, new CfFrame( new Int2ObjectAVLTreeMap<>( - new int[] {0, 1}, + new int[] {0, 1, 2}, new FrameType[] { FrameType.initializedNonNullReference( factory.createType("Lcom/android/tools/r8/DesugarVarHandle;")), - FrameType.initializedNonNullReference(factory.objectType) + FrameType.initializedNonNullReference(factory.objectType), + FrameType.intType() })), new CfLoad(ValueType.OBJECT, 1), new CfInstanceOf(factory.createType("Ljava/lang/Byte;")), @@ -1744,11 +2009,12 @@ label4, new CfFrame( new Int2ObjectAVLTreeMap<>( - new int[] {0, 1}, + new int[] {0, 1, 2}, new FrameType[] { FrameType.initializedNonNullReference( factory.createType("Lcom/android/tools/r8/DesugarVarHandle;")), - FrameType.initializedNonNullReference(factory.objectType) + FrameType.initializedNonNullReference(factory.objectType), + FrameType.intType() })), new CfLoad(ValueType.OBJECT, 1), new CfInstanceOf(factory.boxedCharType), @@ -1767,11 +2033,12 @@ label6, new CfFrame( new Int2ObjectAVLTreeMap<>( - new int[] {0, 1}, + new int[] {0, 1, 2}, new FrameType[] { FrameType.initializedNonNullReference( factory.createType("Lcom/android/tools/r8/DesugarVarHandle;")), - FrameType.initializedNonNullReference(factory.objectType) + FrameType.initializedNonNullReference(factory.objectType), + FrameType.intType() })), new CfLoad(ValueType.OBJECT, 1), new CfInstanceOf(factory.createType("Ljava/lang/Short;")), @@ -1790,11 +2057,35 @@ label8, new CfFrame( new Int2ObjectAVLTreeMap<>( - new int[] {0, 1}, + new int[] {0, 1, 2}, new FrameType[] { FrameType.initializedNonNullReference( factory.createType("Lcom/android/tools/r8/DesugarVarHandle;")), - FrameType.initializedNonNullReference(factory.objectType) + FrameType.initializedNonNullReference(factory.objectType), + FrameType.intType() + })), + new CfLoad(ValueType.INT, 2), + new CfIf(If.Type.EQ, ValueType.INT, label10), + label9, + new CfNew(factory.createType("Ljava/lang/ClassCastException;")), + new CfStackInstruction(CfStackInstruction.Opcode.Dup), + new CfInvoke( + 183, + factory.createMethod( + factory.createType("Ljava/lang/ClassCastException;"), + factory.createProto(factory.voidType), + factory.createString("<init>")), + false), + new CfThrow(), + label10, + new CfFrame( + new Int2ObjectAVLTreeMap<>( + new int[] {0, 1, 2}, + new FrameType[] { + FrameType.initializedNonNullReference( + factory.createType("Lcom/android/tools/r8/DesugarVarHandle;")), + FrameType.initializedNonNullReference(factory.objectType), + FrameType.intType() })), new CfLoad(ValueType.OBJECT, 0), new CfInvoke( @@ -1805,7 +2096,7 @@ factory.createString("desugarWrongMethodTypeException")), false), new CfThrow(), - label9), + label11), ImmutableList.of(), ImmutableList.of()); } @@ -1817,8 +2108,8 @@ CfLabel label3 = new CfLabel(); return new CfCode( method.holder, - 2, - 2, + 3, + 3, ImmutableList.of( label0, new CfLoad(ValueType.OBJECT, 1), @@ -1838,19 +2129,21 @@ label2, new CfFrame( new Int2ObjectAVLTreeMap<>( - new int[] {0, 1}, + new int[] {0, 1, 2}, new FrameType[] { FrameType.initializedNonNullReference( factory.createType("Lcom/android/tools/r8/DesugarVarHandle;")), - FrameType.initializedNonNullReference(factory.objectType) + FrameType.initializedNonNullReference(factory.objectType), + FrameType.intType() })), new CfLoad(ValueType.OBJECT, 0), new CfLoad(ValueType.OBJECT, 1), + new CfLoad(ValueType.INT, 2), new CfInvoke( 182, factory.createMethod( factory.createType("Lcom/android/tools/r8/DesugarVarHandle;"), - factory.createProto(factory.intType, factory.objectType), + factory.createProto(factory.intType, factory.objectType, factory.booleanType), factory.createString("toIntIfPossible")), false), new CfNumberConversion(NumericType.INT, NumericType.LONG),
diff --git a/src/main/java/com/android/tools/r8/utils/IntBox.java b/src/main/java/com/android/tools/r8/utils/IntBox.java index 79684cd..47f2093 100644 --- a/src/main/java/com/android/tools/r8/utils/IntBox.java +++ b/src/main/java/com/android/tools/r8/utils/IntBox.java
@@ -48,6 +48,12 @@ increment(1); } + public void incrementIf(boolean value) { + if (value) { + increment(); + } + } + public void increment(int i) { assert i >= 0; value += i;
diff --git a/src/main/java/com/android/tools/r8/utils/positions/ClassFilePositionToMappedRangeMapper.java b/src/main/java/com/android/tools/r8/utils/positions/ClassFilePositionToMappedRangeMapper.java index dd60a3e..7360a6c 100644 --- a/src/main/java/com/android/tools/r8/utils/positions/ClassFilePositionToMappedRangeMapper.java +++ b/src/main/java/com/android/tools/r8/utils/positions/ClassFilePositionToMappedRangeMapper.java
@@ -116,27 +116,7 @@ positionRemapper.createRemappedPosition(currentPosition); Position oldPosition = remappedPosition.getFirst(); Position newPosition = remappedPosition.getSecond(); - if (isFirstEntry) { - mappedPositions.add( - new MappedPosition( - oldPosition.getMethod(), - oldPosition.getLine(), - oldPosition.getCallerPosition(), - newPosition.getLine(), - oldPosition.isOutline(), - oldPosition.getOutlineCallee(), - oldPosition.getOutlinePositions())); - } else { - mappedPositions.add( - new MappedPosition( - oldPosition.getMethod(), - oldPosition.getLine(), - oldPosition.getCallerPosition(), - newPosition.getLine(), - false, - null, - null)); - } + mappedPositions.add(new MappedPosition(oldPosition, newPosition.getLine())); CfPosition position = new CfPosition(new CfLabel(), newPosition); newInstructions.add(position); newInstructions.add(position.getLabel());
diff --git a/src/main/java/com/android/tools/r8/utils/positions/DexPositionToNoPcMappedRangeMapper.java b/src/main/java/com/android/tools/r8/utils/positions/DexPositionToNoPcMappedRangeMapper.java index 6e540b8..a660a37 100644 --- a/src/main/java/com/android/tools/r8/utils/positions/DexPositionToNoPcMappedRangeMapper.java +++ b/src/main/java/com/android/tools/r8/utils/positions/DexPositionToNoPcMappedRangeMapper.java
@@ -14,7 +14,6 @@ import com.android.tools.r8.graph.DexDebugEvent.RestartLocal; import com.android.tools.r8.graph.DexDebugEvent.SetEpilogueBegin; import com.android.tools.r8.graph.DexDebugEvent.SetFile; -import com.android.tools.r8.graph.DexDebugEvent.SetPositionFrame; import com.android.tools.r8.graph.DexDebugEvent.SetPrologueEnd; import com.android.tools.r8.graph.DexDebugEvent.StartLocal; import com.android.tools.r8.graph.DexDebugEventBuilder; @@ -26,12 +25,7 @@ import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.ir.code.Position; -import com.android.tools.r8.ir.code.Position.OutlineCallerPosition; -import com.android.tools.r8.ir.code.Position.OutlineCallerPosition.OutlineCallerPositionBuilder; -import com.android.tools.r8.ir.code.Position.OutlinePosition; -import com.android.tools.r8.ir.code.Position.PositionBuilder; import com.android.tools.r8.ir.code.Position.SourcePosition; -import com.android.tools.r8.utils.Box; import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization; import java.util.ArrayList; import java.util.List; @@ -87,6 +81,122 @@ } } + private static class DexDebugPositionStateVisitor extends DexDebugPositionState { + + private final PositionEventEmitter positionEventEmitter; + private final List<MappedPosition> mappedPositions; + private final PositionRemapper positionRemapper; + private final List<DexDebugEvent> processedEvents; + private final DexItemFactory factory; + + private final DexMethod startMethod; + + // Keep track of what PC has been emitted. + private int emittedPc = 0; + + private boolean inlinedOriginalPosition; + + public DexDebugPositionStateVisitor( + PositionEventEmitter positionEventEmitter, + List<MappedPosition> mappedPositions, + PositionRemapper positionRemapper, + List<DexDebugEvent> processedEvents, + DexItemFactory factory, + int startLine, + DexMethod method) { + super(startLine, method); + this.positionEventEmitter = positionEventEmitter; + this.mappedPositions = mappedPositions; + this.positionRemapper = positionRemapper; + this.processedEvents = processedEvents; + this.factory = factory; + this.startMethod = method; + } + + // Force the current PC to emitted. + private void flushPc() { + if (emittedPc != getCurrentPc()) { + positionEventEmitter.emitAdvancePc(getCurrentPc()); + emittedPc = getCurrentPc(); + } + } + + // A default event denotes a line table entry and must always be emitted. Remap its line. + @Override + public void visit(Default defaultEvent) { + if (hasPreamblePosition(defaultEvent)) { + emitPreamblePosition(); + } + super.visit(defaultEvent); + assert getCurrentLine() >= 0; + Position position = getPosition(); + Position mappedPosition = + PositionUtils.remapAndAdd(position, positionRemapper, mappedPositions); + positionEventEmitter.emitPositionEvents(getCurrentPc(), mappedPosition); + if (mappedPosition != position) { + inlinedOriginalPosition = true; + } + emittedPc = getCurrentPc(); + } + + private boolean hasPreamblePosition(Default defaultEvent) { + return getCurrentPc() == 0 + && defaultEvent.getPCDelta() > 0 + && currentPosition != null + && currentPosition.getLine() != getCurrentLine(); + } + + // Non-materializing events use super, ie, AdvancePC, AdvanceLine and SetInlineFrame. + + // Materializing events are just amended to the stream. + + @Override + public void visit(SetFile setFile) { + processedEvents.add(setFile); + } + + @Override + public void visit(SetPrologueEnd setPrologueEnd) { + processedEvents.add(setPrologueEnd); + } + + @Override + public void visit(SetEpilogueBegin setEpilogueBegin) { + processedEvents.add(setEpilogueBegin); + } + + // Local changes must force flush the PC ensuing they pertain to the correct point. + + @Override + public void visit(StartLocal startLocal) { + flushPc(); + processedEvents.add(startLocal); + } + + @Override + public void visit(EndLocal endLocal) { + flushPc(); + processedEvents.add(endLocal); + } + + @Override + public void visit(RestartLocal restartLocal) { + flushPc(); + processedEvents.add(restartLocal); + } + + public void emitPreamblePosition() { + if (currentPosition == null || positionEventEmitter.didEmitLineEvents()) { + return; + } + Position mappedPosition = + PositionUtils.remapAndAdd(currentPosition, positionRemapper, mappedPositions); + processedEvents.add(factory.createPositionFrame(mappedPosition)); + currentPosition = null; + currentMethod = startMethod; + } + } + private final AppView<?> appView; private final boolean isIdentityMapping; @@ -112,95 +222,24 @@ appView.graphLens().getOriginalMethodSignature(method.getReference()), processedEvents); - Box<Boolean> inlinedOriginalPosition = new Box<>(false); - - // Debug event visitor to map line numbers. - DexDebugPositionState visitor = - new DexDebugPositionState( + DexDebugPositionStateVisitor visitor = + new DexDebugPositionStateVisitor( + positionEventEmitter, + mappedPositions, + positionRemapper, + processedEvents, + appView.dexItemFactory(), debugInfo.startLine, - appView.graphLens().getOriginalMethodSignature(method.getReference())) { - - // Keep track of what PC has been emitted. - private int emittedPc = 0; - - // Force the current PC to emitted. - private void flushPc() { - if (emittedPc != getCurrentPc()) { - positionEventEmitter.emitAdvancePc(getCurrentPc()); - emittedPc = getCurrentPc(); - } - } - - // A default event denotes a line table entry and must always be emitted. Remap its line. - @Override - public void visit(Default defaultEvent) { - super.visit(defaultEvent); - assert getCurrentLine() >= 0; - Position position = getPositionFromPositionState(this); - Position currentPosition = - PositionUtils.remapAndAdd(position, positionRemapper, mappedPositions); - positionEventEmitter.emitPositionEvents(getCurrentPc(), currentPosition); - if (currentPosition != position) { - inlinedOriginalPosition.set(true); - } - emittedPc = getCurrentPc(); - resetOutlineInformation(); - } - - // Non-materializing events use super, ie, AdvancePC, AdvanceLine and SetInlineFrame. - - // Materializing events are just amended to the stream. - - @Override - public void visit(SetFile setFile) { - processedEvents.add(setFile); - } - - @Override - public void visit(SetPrologueEnd setPrologueEnd) { - processedEvents.add(setPrologueEnd); - } - - @Override - public void visit(SetEpilogueBegin setEpilogueBegin) { - processedEvents.add(setEpilogueBegin); - } - - // Local changes must force flush the PC ensuing they pertain to the correct point. - - @Override - public void visit(StartLocal startLocal) { - flushPc(); - processedEvents.add(startLocal); - } - - @Override - public void visit(EndLocal endLocal) { - flushPc(); - processedEvents.add(endLocal); - } - - @Override - public void visit(RestartLocal restartLocal) { - flushPc(); - processedEvents.add(restartLocal); - } - }; + appView.graphLens().getOriginalMethodSignature(method.getReference())); DexDebugEvent[] events = debugInfo.events; - if (events.length > 0) { - SetPositionFrame preambleFrame = getAsPreambleFrame(events[0]); - if (preambleFrame != null) { - // The preamble is specially identified here as it is active at method entry and thus not - // part of the instruction stream events. - Position position = preambleFrame.getPosition(); - Position newPosition = - PositionUtils.remapAndAdd(position, positionRemapper, mappedPositions); - processedEvents.add(appView.dexItemFactory().createPositionFrame(newPosition)); - } - for (int i = (preambleFrame == null) ? 0 : 1; i < events.length; i++) { - events[i].accept(visitor); - } + for (DexDebugEvent event : events) { + event.accept(visitor); + } + + // We still need to emit a preamble if we did not materialize any other instructions. + if (mappedPositions.isEmpty()) { + visitor.emitPreamblePosition(); } EventBasedDebugInfo optimizedDebugInfo = @@ -210,23 +249,13 @@ processedEvents.toArray(DexDebugEvent.EMPTY_ARRAY)); assert !isIdentityMapping - || inlinedOriginalPosition.get() + || visitor.inlinedOriginalPosition || verifyIdentityMapping(debugInfo, optimizedDebugInfo); dexCode.setDebugInfo(optimizedDebugInfo); return mappedPositions; } - private SetPositionFrame getAsPreambleFrame(DexDebugEvent event) { - SetPositionFrame positionFrame = event.asSetPositionFrame(); - if (positionFrame != null - && positionFrame.getPosition().isSyntheticPosition() - && positionFrame.getPosition().getLine() == 0) { - return positionFrame; - } - return null; - } - // This conversion *always* creates an event based debug info encoding as any non-info will // be created as an implicit PC encoding. private static EventBasedDebugInfo getEventBasedDebugInfo( @@ -243,27 +272,6 @@ return debugInfo; } - private static Position getPositionFromPositionState(DexDebugPositionState state) { - PositionBuilder<?, ?> positionBuilder; - if (state.getOutlineCallee() != null) { - OutlineCallerPositionBuilder outlineCallerPositionBuilder = - OutlineCallerPosition.builder() - .setOutlineCallee(state.getOutlineCallee()) - .setIsOutline(state.isOutline()); - state.getOutlineCallerPositions().forEach(outlineCallerPositionBuilder::addOutlinePosition); - positionBuilder = outlineCallerPositionBuilder; - } else if (state.isOutline()) { - positionBuilder = OutlinePosition.builder(); - } else { - positionBuilder = SourcePosition.builder().setFile(state.getCurrentFile()); - } - return positionBuilder - .setLine(state.getCurrentLine()) - .setMethod(state.getCurrentMethod()) - .setCallerPosition(state.getCurrentCallerPosition()) - .build(); - } - private static boolean verifyIdentityMapping( EventBasedDebugInfo originalDebugInfo, EventBasedDebugInfo optimizedDebugInfo) { assert optimizedDebugInfo.startLine == originalDebugInfo.startLine;
diff --git a/src/main/java/com/android/tools/r8/utils/positions/DexPositionToPcMappedRangeMapper.java b/src/main/java/com/android/tools/r8/utils/positions/DexPositionToPcMappedRangeMapper.java index 890cb80..40f80b4 100644 --- a/src/main/java/com/android/tools/r8/utils/positions/DexPositionToPcMappedRangeMapper.java +++ b/src/main/java/com/android/tools/r8/utils/positions/DexPositionToPcMappedRangeMapper.java
@@ -16,11 +16,6 @@ import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.ProgramMethod; import com.android.tools.r8.ir.code.Position; -import com.android.tools.r8.ir.code.Position.OutlineCallerPosition; -import com.android.tools.r8.ir.code.Position.OutlineCallerPosition.OutlineCallerPositionBuilder; -import com.android.tools.r8.ir.code.Position.OutlinePosition; -import com.android.tools.r8.ir.code.Position.PositionBuilder; -import com.android.tools.r8.ir.code.Position.SourcePosition; import com.android.tools.r8.utils.IntBox; import com.android.tools.r8.utils.Pair; import com.android.tools.r8.utils.positions.PositionToMappedRangeMapper.PcBasedDebugInfoRecorder; @@ -58,7 +53,7 @@ if (firstDefaultEventPc.get() < 0) { firstDefaultEventPc.set(getCurrentPc()); } - Position currentPosition = getPositionFromPositionState(this); + Position currentPosition = getPosition(); if (lastPosition.getSecond() != null) { remapAndAddForPc( pcBasedDebugInfo, @@ -70,7 +65,6 @@ } lastPosition.setFirst(getCurrentPc()); lastPosition.setSecond(currentPosition); - resetOutlineInformation(); } }; @@ -104,18 +98,8 @@ Pair<Position, Position> remappedPosition = remapper.createRemappedPosition(position); Position oldPosition = remappedPosition.getFirst(); for (int currentPc = startPc; currentPc < endPc; currentPc++) { - boolean firstEntry = currentPc == startPc; mappedPositions.add( - new MappedPosition( - oldPosition.getMethod(), - oldPosition.getLine(), - oldPosition.getCallerPosition(), - debugInfoProvider.getPcEncoding(currentPc), - // Outline info is placed exactly on the positions that relate to it so we should - // only emit it for the first entry. - firstEntry && oldPosition.isOutline(), - firstEntry ? oldPosition.getOutlineCallee() : null, - firstEntry ? oldPosition.getOutlinePositions() : null)); + new MappedPosition(oldPosition, debugInfoProvider.getPcEncoding(currentPc))); } } @@ -135,27 +119,6 @@ return debugInfo; } - private static Position getPositionFromPositionState(DexDebugPositionState state) { - PositionBuilder<?, ?> positionBuilder; - if (state.getOutlineCallee() != null) { - OutlineCallerPositionBuilder outlineCallerPositionBuilder = - OutlineCallerPosition.builder() - .setOutlineCallee(state.getOutlineCallee()) - .setIsOutline(state.isOutline()); - state.getOutlineCallerPositions().forEach(outlineCallerPositionBuilder::addOutlinePosition); - positionBuilder = outlineCallerPositionBuilder; - } else if (state.isOutline()) { - positionBuilder = OutlinePosition.builder(); - } else { - positionBuilder = SourcePosition.builder().setFile(state.getCurrentFile()); - } - return positionBuilder - .setLine(state.getCurrentLine()) - .setMethod(state.getCurrentMethod()) - .setCallerPosition(state.getCurrentCallerPosition()) - .build(); - } - private static boolean verifyIdentityMapping( EventBasedDebugInfo originalDebugInfo, EventBasedDebugInfo optimizedDebugInfo) { assert optimizedDebugInfo.startLine == originalDebugInfo.startLine;
diff --git a/src/main/java/com/android/tools/r8/utils/positions/MappedPosition.java b/src/main/java/com/android/tools/r8/utils/positions/MappedPosition.java index a156c80..3b29392 100644 --- a/src/main/java/com/android/tools/r8/utils/positions/MappedPosition.java +++ b/src/main/java/com/android/tools/r8/utils/positions/MappedPosition.java
@@ -4,66 +4,23 @@ package com.android.tools.r8.utils.positions; -import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.ir.code.Position; -import com.android.tools.r8.utils.Int2StructuralItemArrayMap; public class MappedPosition { - private final DexMethod method; - private final int originalLine; - private final Position caller; private final int obfuscatedLine; - private final boolean isOutline; - private final DexMethod outlineCallee; - private final Int2StructuralItemArrayMap<Position> outlinePositions; + private final Position position; - public MappedPosition( - DexMethod method, - int originalLine, - Position caller, - int obfuscatedLine, - boolean isOutline, - DexMethod outlineCallee, - Int2StructuralItemArrayMap<Position> outlinePositions) { - this.method = method; - this.originalLine = originalLine; - this.caller = caller; + public MappedPosition(Position position, int obfuscatedLine) { + this.position = position; this.obfuscatedLine = obfuscatedLine; - this.isOutline = isOutline; - this.outlineCallee = outlineCallee; - this.outlinePositions = outlinePositions; - } - - public DexMethod getMethod() { - return method; - } - - public int getOriginalLine() { - return originalLine; - } - - public Position getCaller() { - return caller; } public int getObfuscatedLine() { return obfuscatedLine; } - public boolean isOutline() { - return isOutline; - } - - public DexMethod getOutlineCallee() { - return outlineCallee; - } - - public Int2StructuralItemArrayMap<Position> getOutlinePositions() { - return outlinePositions; - } - - public boolean isOutlineCaller() { - return outlineCallee != null; + public Position getPosition() { + return position; } }
diff --git a/src/main/java/com/android/tools/r8/utils/positions/MappedPositionToClassNameMapperBuilder.java b/src/main/java/com/android/tools/r8/utils/positions/MappedPositionToClassNameMapperBuilder.java index 13f013e..2915d14 100644 --- a/src/main/java/com/android/tools/r8/utils/positions/MappedPositionToClassNameMapperBuilder.java +++ b/src/main/java/com/android/tools/r8/utils/positions/MappedPositionToClassNameMapperBuilder.java
@@ -248,7 +248,7 @@ m, key -> MethodSignature.fromDexMethod(m, m.holder != clazz.getType())); // Check if mapped position is an outline - DexMethod outlineMethod = getOutlineMethod(mappedPositions.get(0)); + DexMethod outlineMethod = getOutlineMethod(mappedPositions.get(0).getPosition()); if (outlineMethod != null) { outlinesToFix .computeIfAbsent( @@ -261,38 +261,48 @@ // Update memberNaming with the collected positions, merging multiple positions into a // single region whenever possible. for (int i = 0; i < mappedPositions.size(); /* updated in body */ ) { - MappedPosition firstPosition = mappedPositions.get(i); + MappedPosition firstMappedPosition = mappedPositions.get(i); int j = i + 1; - MappedPosition lastPosition = firstPosition; + MappedPosition lastMappedPosition = firstMappedPosition; MappedPositionRange mappedPositionRange = MappedPositionRange.SINGLE_LINE; for (; j < mappedPositions.size(); j++) { // Break if this position cannot be merged with lastPosition. - MappedPosition currentPosition = mappedPositions.get(j); + MappedPosition currentMappedPosition = mappedPositions.get(j); mappedPositionRange = - mappedPositionRange.canAddNextMappingToRange(lastPosition, currentPosition); + mappedPositionRange.canAddNextMappingToRange( + lastMappedPosition, currentMappedPosition); // Note that currentPosition.caller and lastPosition.class must be deep-compared since // multiple inlining passes lose the canonical property of the positions. + Position currentPosition = currentMappedPosition.getPosition(); + Position lastPosition = lastMappedPosition.getPosition(); if (currentPosition.getMethod() != lastPosition.getMethod() || mappedPositionRange.isOutOfRange() - || !Objects.equals(currentPosition.getCaller(), lastPosition.getCaller()) + || !Objects.equals( + currentPosition.getCallerPosition(), lastPosition.getCallerPosition()) // Break when we see a mapped outline || currentPosition.getOutlineCallee() != null // Ensure that we break when we start iterating with an outline caller again. - || firstPosition.getOutlineCallee() != null) { + || firstMappedPosition.getPosition().getOutlineCallee() != null) { break; } // The mapped positions are not guaranteed to be in order, so maintain first and last // position. - if (firstPosition.getObfuscatedLine() > currentPosition.getObfuscatedLine()) { - firstPosition = currentPosition; + if (firstMappedPosition.getObfuscatedLine() > currentMappedPosition.getObfuscatedLine()) { + firstMappedPosition = currentMappedPosition; } - if (lastPosition.getObfuscatedLine() < currentPosition.getObfuscatedLine()) { - lastPosition = currentPosition; + if (lastMappedPosition.getObfuscatedLine() < currentMappedPosition.getObfuscatedLine()) { + lastMappedPosition = currentMappedPosition; } } Range obfuscatedRange = nonCardinalRangeCache.get( - firstPosition.getObfuscatedLine(), lastPosition.getObfuscatedLine()); + firstMappedPosition.getObfuscatedLine(), lastMappedPosition.getObfuscatedLine()); + + Position firstPosition = firstMappedPosition.getPosition(); + Position lastPosition = lastMappedPosition.getPosition(); + + Range originalRange = + nonCardinalRangeCache.get(firstPosition.getLine(), lastPosition.getLine()); MappedRange lastMappedRange = getMappedRangesForPosition( @@ -302,9 +312,8 @@ firstPosition.getMethod(), residualSignature, obfuscatedRange, - nonCardinalRangeCache.get( - firstPosition.getOriginalLine(), lastPosition.getOriginalLine()), - firstPosition.getCaller(), + originalRange, + firstPosition.getCallerPosition(), prunedInlinedClasses, cardinalRangeCache); methodSpecificMappingInformation.consume( @@ -407,11 +416,11 @@ return lastMappedRange; } - private DexMethod getOutlineMethod(MappedPosition mappedPosition) { + private DexMethod getOutlineMethod(Position mappedPosition) { if (mappedPosition.isOutline()) { return mappedPosition.getMethod(); } - Position caller = mappedPosition.getCaller(); + Position caller = mappedPosition.getCallerPosition(); if (caller == null) { return null; } @@ -500,8 +509,9 @@ // is OK since retrace(a(:7)) = 42, however, the following is not OK: // 1:10:void foo():42:43 -> a // since retrace(a(:7)) = 49, which is not correct. - boolean hasSameRightHandSide = - lastPosition.getOriginalLine() == currentPosition.getOriginalLine(); + int currentOriginalLine = currentPosition.getPosition().getLine(); + int lastOriginalLine = lastPosition.getPosition().getLine(); + boolean hasSameRightHandSide = lastOriginalLine == currentOriginalLine; if (hasSameRightHandSide) { if (isSameDelta()) { return OUT_OF_RANGE; @@ -515,7 +525,7 @@ return OUT_OF_RANGE; } boolean sameDelta = - currentPosition.getOriginalLine() - lastPosition.getOriginalLine() + currentOriginalLine - lastOriginalLine == currentPosition.getObfuscatedLine() - lastPosition.getObfuscatedLine(); return sameDelta ? SAME_DELTA : OUT_OF_RANGE; } @@ -569,7 +579,7 @@ private int getMinifiedLinePosition( int originalPosition, List<MappedPosition> mappedPositions) { for (MappedPosition mappedPosition : mappedPositions) { - if (mappedPosition.getOriginalLine() == originalPosition) { + if (mappedPosition.getPosition().getLine() == originalPosition) { return mappedPosition.getObfuscatedLine(); } }
diff --git a/src/main/java/com/android/tools/r8/utils/positions/PositionUtils.java b/src/main/java/com/android/tools/r8/utils/positions/PositionUtils.java index b7247e7..0b0b20a 100644 --- a/src/main/java/com/android/tools/r8/utils/positions/PositionUtils.java +++ b/src/main/java/com/android/tools/r8/utils/positions/PositionUtils.java
@@ -24,15 +24,7 @@ Pair<Position, Position> remappedPosition = remapper.createRemappedPosition(position); Position oldPosition = remappedPosition.getFirst(); Position newPosition = remappedPosition.getSecond(); - mappedPositions.add( - new MappedPosition( - oldPosition.getMethod(), - oldPosition.getLine(), - oldPosition.getCallerPosition(), - newPosition.getLine(), - oldPosition.isOutline(), - oldPosition.getOutlineCallee(), - oldPosition.getOutlinePositions())); + mappedPositions.add(new MappedPosition(oldPosition, newPosition.getLine())); return newPosition; }
diff --git a/src/test/examplesJava9/varhandle/InstanceObjectField.java b/src/test/examplesJava9/varhandle/InstanceObjectField.java index e7b4970..1b4a4c9 100644 --- a/src/test/examplesJava9/varhandle/InstanceObjectField.java +++ b/src/test/examplesJava9/varhandle/InstanceObjectField.java
@@ -5,19 +5,160 @@ import java.lang.invoke.MethodHandles; import java.lang.invoke.VarHandle; +import java.lang.invoke.WrongMethodTypeException; public class InstanceObjectField { private Object field; + public static class A { + + private final int i; + + public A(int i) { + this.i = i; + } + + public String toString() { + return new StringBuilder().append("A(").append(i).append(")").toString(); + } + } + public static void testSet(VarHandle varHandle) { - System.out.println("testGet"); + System.out.println("testSet"); InstanceObjectField instance = new InstanceObjectField(); System.out.println(varHandle.get(instance)); - varHandle.set(instance, 1); + A a1 = new A(1); + varHandle.set(instance, a1); System.out.println(varHandle.get(instance)); + System.out.println(varHandle.get(instance) == a1); + A a2 = new A(2); + varHandle.set(instance, a2); + System.out.println(varHandle.get(instance)); + System.out.println(varHandle.get(instance) == a2); + + Object o; + { + int i; + varHandle.set(instance, 1); + System.out.println(varHandle.get(instance)); + System.out.println((int) varHandle.get(instance)); + o = varHandle.get(instance); + System.out.println(o instanceof Integer); + i = (int) varHandle.get(instance); + System.out.println(i == 1); + varHandle.set(instance, Integer.valueOf(2)); + System.out.println(varHandle.get(instance)); + System.out.println((int) varHandle.get(instance)); + o = varHandle.get(instance); + System.out.println(o instanceof Integer); + i = (int) varHandle.get(instance); + System.out.println(i == 2); + } + { + long l; + varHandle.set(instance, 3L); + System.out.println(varHandle.get(instance)); + System.out.println((long) varHandle.get(instance)); + o = varHandle.get(instance); + System.out.println(o instanceof Long); + l = (long) varHandle.get(instance); + System.out.println(l == 3L); + varHandle.set(instance, Long.valueOf(4L)); + System.out.println(varHandle.get(instance)); + System.out.println((long) varHandle.get(instance)); + o = varHandle.get(instance); + System.out.println(o instanceof Long); + l = (long) varHandle.get(instance); + System.out.println(l == 4L); + } + { + byte b; + varHandle.set(instance, (byte) 5); + System.out.println(varHandle.get(instance)); + System.out.println((byte) varHandle.get(instance)); + o = varHandle.get(instance); + System.out.println(o instanceof Byte); + b = (byte) varHandle.get(instance); + System.out.println(b == (byte) 5); + varHandle.set(instance, Byte.valueOf((byte) 6)); + System.out.println(varHandle.get(instance)); + System.out.println((byte) varHandle.get(instance)); + o = varHandle.get(instance); + System.out.println(o instanceof Byte); + b = (byte) varHandle.get(instance); + System.out.println(b == 6); + } + { + short s; + varHandle.set(instance, (short) 7); + System.out.println(varHandle.get(instance)); + System.out.println((short) varHandle.get(instance)); + o = varHandle.get(instance); + System.out.println(o instanceof Short); + s = (short) varHandle.get(instance); + System.out.println(s == (short) 7); + varHandle.set(instance, Short.valueOf((short) 8)); + System.out.println(varHandle.get(instance)); + System.out.println((short) varHandle.get(instance)); + o = varHandle.get(instance); + System.out.println(o instanceof Short); + s = (short) varHandle.get(instance); + System.out.println(s == 8); + } + { + float f; + varHandle.set(instance, (float) 9.0f); + System.out.println(varHandle.get(instance)); + System.out.println((float) varHandle.get(instance)); + o = varHandle.get(instance); + System.out.println(o instanceof Float); + f = (float) varHandle.get(instance); + System.out.println(f == (float) 9.0f); + varHandle.set(instance, Float.valueOf(10.0f)); + System.out.println(varHandle.get(instance)); + System.out.println((float) varHandle.get(instance)); + o = varHandle.get(instance); + System.out.println(o instanceof Float); + f = (float) varHandle.get(instance); + System.out.println(f == 10.0f); + } + { + double d; + varHandle.set(instance, (double) 11.0); + System.out.println(varHandle.get(instance)); + System.out.println((double) varHandle.get(instance)); + o = varHandle.get(instance); + System.out.println(o instanceof Double); + d = (double) varHandle.get(instance); + System.out.println(d == (double) 11.0); + varHandle.set(instance, Double.valueOf(12.0)); + System.out.println(varHandle.get(instance)); + System.out.println((double) varHandle.get(instance)); + o = varHandle.get(instance); + System.out.println(o instanceof Double); + d = (double) varHandle.get(instance); + System.out.println(d == 12.0); + } + { + char c; + varHandle.set(instance, 'A'); + System.out.println(varHandle.get(instance)); + System.out.println((char) varHandle.get(instance)); + o = varHandle.get(instance); + System.out.println(o instanceof Character); + c = (char) varHandle.get(instance); + System.out.println(c == 'A'); + varHandle.set(instance, Character.valueOf('B')); + System.out.println(varHandle.get(instance)); + System.out.println((char) varHandle.get(instance)); + o = varHandle.get(instance); + System.out.println(o instanceof Character); + c = (char) varHandle.get(instance); + System.out.println(c == 'B'); + } } public static void testCompareAndSet(VarHandle varHandle) { @@ -25,10 +166,106 @@ InstanceObjectField instance = new InstanceObjectField(); - varHandle.compareAndSet(instance, 0, 1); + A a1 = new A(1); + varHandle.compareAndSet(instance, 0, a1); System.out.println(varHandle.get(instance)); - varHandle.compareAndSet(instance, null, 1); + varHandle.compareAndSet(instance, null, a1); System.out.println(varHandle.get(instance)); + System.out.println(varHandle.get(instance) == a1); + A a2 = new A(2); + varHandle.compareAndSet(instance, a1, a2); + System.out.println(varHandle.get(instance)); + System.out.println(varHandle.get(instance) == a2); + + varHandle.compareAndSet(instance, a2, 1); + System.out.println(varHandle.get(instance)); + varHandle.compareAndSet(instance, Integer.valueOf(1), 2); + System.out.println(varHandle.get(instance)); + varHandle.compareAndSet(instance, Integer.valueOf(2), Integer.valueOf(3)); + System.out.println(varHandle.get(instance)); + varHandle.compareAndSet(instance, Integer.valueOf(3), 4); + System.out.println(varHandle.get(instance)); + varHandle.compareAndSet(instance, 4L, 5); + System.out.println(varHandle.get(instance)); + varHandle.compareAndSet(instance, (byte) 4, 5); + System.out.println(varHandle.get(instance)); + varHandle.compareAndSet(instance, (short) 4, 5); + System.out.println(varHandle.get(instance)); + + varHandle.compareAndSet(instance, 4, 5L); + System.out.println(varHandle.get(instance)); + varHandle.compareAndSet(instance, Long.valueOf(5), 6L); + System.out.println(varHandle.get(instance)); + varHandle.compareAndSet(instance, Long.valueOf(6), Long.valueOf(7)); + System.out.println(varHandle.get(instance)); + varHandle.compareAndSet(instance, Long.valueOf(7), 8L); + System.out.println(varHandle.get(instance)); + varHandle.compareAndSet(instance, 8, 9L); + System.out.println(varHandle.get(instance)); + varHandle.compareAndSet(instance, (byte) 8, 9); + System.out.println(varHandle.get(instance)); + varHandle.compareAndSet(instance, (short) 8, 9); + System.out.println(varHandle.get(instance)); + System.out.println(varHandle.get(instance) == a1); + varHandle.compareAndSet(instance, a1, a2); + System.out.println(varHandle.get(instance)); + System.out.println(varHandle.get(instance) == a2); + + varHandle.compareAndSet(instance, a2, 1); + System.out.println(varHandle.get(instance)); + varHandle.compareAndSet(instance, Integer.valueOf(1), 2); + System.out.println(varHandle.get(instance)); + varHandle.compareAndSet(instance, Integer.valueOf(2), Integer.valueOf(3)); + System.out.println(varHandle.get(instance)); + varHandle.compareAndSet(instance, Integer.valueOf(3), 4); + System.out.println(varHandle.get(instance)); + varHandle.compareAndSet(instance, 4L, 5); + System.out.println(varHandle.get(instance)); + varHandle.compareAndSet(instance, (byte) 4, 5); + System.out.println(varHandle.get(instance)); + varHandle.compareAndSet(instance, (short) 4, 5); + System.out.println(varHandle.get(instance)); + + varHandle.compareAndSet(instance, 4, 5L); + System.out.println(varHandle.get(instance)); + varHandle.compareAndSet(instance, Long.valueOf(5), 6L); + System.out.println(varHandle.get(instance)); + varHandle.compareAndSet(instance, Long.valueOf(6), Long.valueOf(7)); + System.out.println(varHandle.get(instance)); + varHandle.compareAndSet(instance, Long.valueOf(7), 8L); + System.out.println(varHandle.get(instance)); + varHandle.compareAndSet(instance, 8, 9L); + System.out.println(varHandle.get(instance)); + varHandle.compareAndSet(instance, (byte) 8, 9); + System.out.println(varHandle.get(instance)); + varHandle.compareAndSet(instance, (short) 8, 9); + System.out.println(varHandle.get(instance)); + } + + public static void testReturnValueClassCastException(VarHandle varHandle) { + System.out.println("testReturnValueClassCastException"); + + InstanceObjectField instance = new InstanceObjectField(); + A a = new A(1); + + varHandle.set(instance, a); + try { + System.out.println((int) varHandle.get(instance)); + System.out.println("Expected ClassCastException"); + } catch (ClassCastException e) { + System.out.println("Reference implementation"); + } catch (WrongMethodTypeException e) { + System.out.println("Art implementation"); + } + varHandle.set(instance, a); + try { + System.out.println((int) (Integer) (int) varHandle.get(instance)); + System.out.println("Expected ClassCastException"); + } catch (ClassCastException e) { + System.out.println("Reference implementation"); + } catch (WrongMethodTypeException e) { + System.out.println("Art implementation"); + } } public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException { @@ -36,5 +273,6 @@ MethodHandles.lookup().findVarHandle(InstanceObjectField.class, "field", Object.class); testSet(varHandle); testCompareAndSet(varHandle); + testReturnValueClassCastException(varHandle); } }
diff --git a/src/test/java/com/android/tools/r8/apimodel/ClassValueTest.java b/src/test/java/com/android/tools/r8/apimodel/ClassValueTest.java new file mode 100644 index 0000000..ad9ea40 --- /dev/null +++ b/src/test/java/com/android/tools/r8/apimodel/ClassValueTest.java
@@ -0,0 +1,224 @@ +// 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.apimodel; + +import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage; +import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent; +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assume.assumeTrue; + +import com.android.tools.r8.ProguardVersion; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestRuntime.CfVm; +import com.android.tools.r8.ToolHelper; +import com.android.tools.r8.utils.AndroidApiLevel; +import com.android.tools.r8.utils.StringUtils; +import com.android.tools.r8.utils.codeinspector.CodeInspector; +import com.google.common.collect.ImmutableList; +import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class ClassValueTest extends TestBase { + + @Parameter() public TestParameters parameters; + + @Parameters(name = "{0}") + public static List<Object[]> data() { + return buildParameters(getTestParameters().withAllRuntimesAndApiLevels().build()); + } + + private static final String EXPECTED_OUTPUT = StringUtils.lines(TestClass.class.getTypeName()); + + private void computeValuePresent(CodeInspector inspector) { + assertThat( + inspector.clazz(ClassValueSub.class).uniqueMethodWithOriginalName("computeValue"), + isPresent()); + } + + private void computeValueAbsent(CodeInspector inspector) { + assertThat( + inspector.clazz(ClassValueSub.class).uniqueMethodWithOriginalName("computeValue"), + isAbsent()); + } + + @Test + public void testReference() throws Exception { + assumeTrue(parameters.isCfRuntime()); + testForRuntime(parameters) + .addInnerClasses(getClass()) + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutput(EXPECTED_OUTPUT); + } + + @Test + public void testD8() throws Exception { + assumeTrue(parameters.isDexRuntime()); + testForD8(parameters.getBackend()) + .addInnerClasses(getClass()) + .setMinApi(parameters.getApiLevel()) + .compile() + .inspect(this::computeValuePresent) + .run(parameters.getRuntime(), TestClass.class) + .assertFailureWithErrorThatThrows(NoClassDefFoundError.class); + } + + @Test + public void testR8() throws Exception { + testForR8(parameters.getBackend()) + .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T)) + .addInnerClasses(getClass()) + .addKeepMainRule(TestClass.class) + .setMinApi(parameters.getApiLevel()) + .addDontWarn(ClassValue.class) + .compile() + .inspect(this::computeValueAbsent) + .run(parameters.getRuntime(), TestClass.class) + .applyIf( + parameters.isCfRuntime(), + r -> r.assertFailureWithErrorThatThrows(AbstractMethodError.class), + r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class)); + } + + @Test + public void testProguard() throws Exception { + assumeTrue(parameters.isCfRuntime()); + testForProguard(ProguardVersion.V7_0_0) + .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T)) + .addInnerClasses(getClass()) + .addKeepMainRule(TestClass.class) + .setMinApi(parameters.getApiLevel()) + // ProGuard warns about the inner class attributes referring to this outer class. + .addDontWarn(this.getClass().getTypeName()) + // ProGuard also warns about ClassValueSub not having method get. + .addDontWarn(ClassValueSub.class.getTypeName()) + .compile() + .inspect(this::computeValueAbsent) + .run(parameters.getRuntime(), TestClass.class) + .applyIf( + parameters.isCfRuntime(), + r -> r.assertFailureWithErrorThatThrows(AbstractMethodError.class), + r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class)); + } + + @Test + public void testR8KeepExtendsMissingType() throws Exception { + testForR8(parameters.getBackend()) + .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T)) + .addInnerClasses(getClass()) + .addKeepMainRule(TestClass.class) + .setMinApi(parameters.getApiLevel()) + .addDontWarn(ClassValue.class) + // Try to keep computeValue on classes extending unknown type. + .addKeepRules("-keep class * extends " + ClassValue.class.getTypeName() + " { *; }") + .allowUnusedProguardConfigurationRules() + .compileWithExpectedDiagnostics( + diagnostics -> + diagnostics.assertInfoThatMatches( + diagnosticMessage( + containsString( + "Proguard configuration rule does not match anything: " + + "`-keep class * extends " + + ClassValue.class.getTypeName() + + " {")))) + .inspect(this::computeValueAbsent) + .run(parameters.getRuntime(), TestClass.class) + .applyIf( + parameters.isCfRuntime(), + r -> r.assertFailureWithErrorThatThrows(AbstractMethodError.class), + r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class)); + } + + @Test + public void testProguardKeepExtendsMissingTypeProguard() throws Exception { + assumeTrue(parameters.isCfRuntime() && parameters.asCfRuntime().getVm() == CfVm.JDK11); + testForProguard(ProguardVersion.V7_0_0) + .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T)) + .addInnerClasses(getClass()) + .addKeepMainRule(TestClass.class) + .setMinApi(parameters.getApiLevel()) + // ProGuard warns about the inner class attributes referring to this outer class. + .addDontWarn(this.getClass().getTypeName()) + // Just -dontwarn on ClassValue is not sufficient. ProGuard also warns about ClassValueSub + // not having method get. + .addDontWarn(ClassValueSub.class.getTypeName()) + // Try to keep computeValue on classes extending unknown type. + .addKeepRules("-keep class * extends " + ClassValue.class.getTypeName() + " { *; }") + // TODO(b/261971620): Support extends matching names of missing classes. + .compile() + .inspect(this::computeValueAbsent) + .run(parameters.getRuntime(), TestClass.class) + .assertFailureWithErrorThatThrows(AbstractMethodError.class); + } + + @Test + public void testR8KeepConcreteMethod() throws Exception { + for (String dontWarn : + ImmutableList.of(ClassValue.class.getTypeName(), ClassValueSub.class.getTypeName())) { + testForR8(parameters.getBackend()) + .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T)) + .addInnerClasses(getClass()) + .addKeepMainRule(TestClass.class) + .setMinApi(parameters.getApiLevel()) + .addDontWarn(dontWarn) + .addKeepRules( + "-keep class " + + ClassValueSub.class.getTypeName() + + " { ** computeValue(java.lang.Class); }") + .compile() + .inspect(this::computeValuePresent) + .run(parameters.getRuntime(), TestClass.class) + .applyIf( + parameters.isCfRuntime(), + r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT), + r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class)); + } + } + + @Test + public void testProguardKeepConcreteMethod() throws Exception { + assumeTrue(parameters.isCfRuntime() && parameters.asCfRuntime().getVm() == CfVm.JDK11); + testForProguard(ProguardVersion.V7_0_0) + .addInnerClasses(getClass()) + .addKeepMainRule(TestClass.class) + // ProGuard warns about the inner class attributes referring to this outer class. + .addDontWarn(this.getClass().getTypeName()) + // Just -dontwarn on ClassValue is not sufficient. ProGuard also warns about ClassValueSub + // not having method get. + .addDontWarn(ClassValueSub.class.getTypeName()) + .addKeepRules( + "-keep class " + + ClassValueSub.class.getTypeName() + + " { ** computeValue(java.lang.Class); }") + .compile() + .inspect(this::computeValuePresent) + .run(parameters.getRuntime(), TestClass.class) + .applyIf( + parameters.isCfRuntime(), + r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT), + r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class)); + } + + static class ClassValueSub extends ClassValue<Object> { + @Override + protected Object computeValue(Class<?> clazz) { + return clazz.getTypeName(); + } + } + + static class TestClass { + static ClassValueSub classValueSub = new ClassValueSub(); + + public static void main(String[] args) { + System.out.println(classValueSub.get(TestClass.class)); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/cf/stackmap/UndefinedTypesAssignmentTest.java b/src/test/java/com/android/tools/r8/cf/stackmap/UndefinedTypesAssignmentTest.java new file mode 100644 index 0000000..9e8c086e --- /dev/null +++ b/src/test/java/com/android/tools/r8/cf/stackmap/UndefinedTypesAssignmentTest.java
@@ -0,0 +1,84 @@ +// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. +package com.android.tools.r8.cf.stackmap; + +import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType; + +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.utils.AndroidApiLevel; +import com.android.tools.r8.utils.StringUtils; +import com.android.tools.r8.utils.UnverifiableCfCodeDiagnostic; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +/** Regression test for b/b/261967650 */ +@RunWith(Parameterized.class) +public class UndefinedTypesAssignmentTest extends TestBase { + + static final String EXPECTED = StringUtils.lines("Hello, world"); + + private final TestParameters parameters; + + @Parameterized.Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters() + .withDefaultRuntimes() + .withApiLevel(AndroidApiLevel.B) + .enableApiLevelsForCf() + .build(); + } + + public UndefinedTypesAssignmentTest(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void testReference() throws Exception { + testForD8(parameters.getBackend()) + .addProgramClasses(TestClass.class) + .setMinApi(parameters.getApiLevel()) + .compile() + .addRunClasspathClasses(I.class, A.class) + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutput(EXPECTED); + } + + @Test + public void testR8() throws Exception { + testForR8(parameters.getBackend()) + .addProgramClasses(TestClass.class) + .addDontWarn(I.class, A.class) + .addKeepMainRule(TestClass.class) + .setMinApi(parameters.getApiLevel()) + .allowDiagnosticWarningMessages() + .compileWithExpectedDiagnostics( + diagnostics -> + diagnostics + .assertOnlyWarnings() + .assertWarningsMatch(diagnosticType(UnverifiableCfCodeDiagnostic.class))) + .addRunClasspathClasses(I.class, A.class) + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutput(EXPECTED); + } + + interface I {} + + static class A implements I {} + + static class TestClass { + + public static void main(String[] args) { + I i = null; + try { + i = new A(); + System.out.println("Hello, world"); + } catch (Exception e) { + System.out.println(i); + } + } + } +}
diff --git a/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringArrayOfIntTest.java b/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringArrayOfIntTest.java index af62516..d7ca20f 100644 --- a/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringArrayOfIntTest.java +++ b/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringArrayOfIntTest.java
@@ -6,6 +6,8 @@ import com.android.tools.r8.examples.jdk9.VarHandle; import com.android.tools.r8.utils.StringUtils; +import com.google.common.collect.ImmutableList; +import java.util.List; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -23,12 +25,12 @@ } @Override - protected String getJarEntry() { - return JAR_ENTRY; + protected List<String> getJarEntries() { + return ImmutableList.of(JAR_ENTRY); } @Override - protected String getExpectedOutput() { + protected String getExpectedOutputForReferenceImplementation() { return EXPECTED_OUTPUT; } }
diff --git a/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringArrayOfLongTest.java b/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringArrayOfLongTest.java index 46e130f..c3c81da 100644 --- a/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringArrayOfLongTest.java +++ b/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringArrayOfLongTest.java
@@ -6,6 +6,8 @@ import com.android.tools.r8.examples.jdk9.VarHandle; import com.android.tools.r8.utils.StringUtils; +import com.google.common.collect.ImmutableList; +import java.util.List; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -23,12 +25,12 @@ } @Override - protected String getJarEntry() { - return JAR_ENTRY; + protected List<String> getJarEntries() { + return ImmutableList.of(JAR_ENTRY); } @Override - protected String getExpectedOutput() { + protected String getExpectedOutputForReferenceImplementation() { return EXPECTED_OUTPUT; } }
diff --git a/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringInstanceIntFieldTest.java b/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringInstanceIntFieldTest.java index 9ae8ecd..7cfc0e5 100644 --- a/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringInstanceIntFieldTest.java +++ b/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringInstanceIntFieldTest.java
@@ -6,6 +6,8 @@ import com.android.tools.r8.examples.jdk9.VarHandle; import com.android.tools.r8.utils.StringUtils; +import com.google.common.collect.ImmutableList; +import java.util.List; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -76,12 +78,12 @@ } @Override - protected String getJarEntry() { - return JAR_ENTRY; + protected List<String> getJarEntries() { + return ImmutableList.of(JAR_ENTRY); } @Override - protected String getExpectedOutput() { + protected String getExpectedOutputForReferenceImplementation() { return EXPECTED_OUTPUT; }
diff --git a/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringInstanceLongFieldTest.java b/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringInstanceLongFieldTest.java index c50db54..227cc76 100644 --- a/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringInstanceLongFieldTest.java +++ b/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringInstanceLongFieldTest.java
@@ -6,6 +6,8 @@ import com.android.tools.r8.examples.jdk9.VarHandle; import com.android.tools.r8.utils.StringUtils; +import com.google.common.collect.ImmutableList; +import java.util.List; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -75,12 +77,12 @@ } @Override - protected String getJarEntry() { - return JAR_ENTRY; + protected List<String> getJarEntries() { + return ImmutableList.of(JAR_ENTRY); } @Override - protected String getExpectedOutput() { + protected String getExpectedOutputForReferenceImplementation() { return EXPECTED_OUTPUT; }
diff --git a/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringInstanceObjectFieldTest.java b/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringInstanceObjectFieldTest.java index b9b2b8e..ac43573 100644 --- a/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringInstanceObjectFieldTest.java +++ b/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringInstanceObjectFieldTest.java
@@ -6,6 +6,8 @@ import com.android.tools.r8.examples.jdk9.VarHandle; import com.android.tools.r8.utils.StringUtils; +import com.google.common.collect.ImmutableList; +import java.util.List; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -13,9 +15,111 @@ public class VarHandleDesugaringInstanceObjectFieldTest extends VarHandleDesugaringTestBase { private static final String EXPECTED_OUTPUT = - StringUtils.lines("testGet", "null", "1", "testCompareAndSet", "null", "1"); + StringUtils.lines( + "testSet", + "null", + "A(1)", + "true", + "A(2)", + "true", + "1", + "1", + "true", + "true", + "2", + "2", + "true", + "true", + "3", + "3", + "true", + "true", + "4", + "4", + "true", + "true", + "5", + "5", + "true", + "true", + "6", + "6", + "true", + "true", + "7", + "7", + "true", + "true", + "8", + "8", + "true", + "true", + "9.0", + "9.0", + "true", + "true", + "10.0", + "10.0", + "true", + "true", + "11.0", + "11.0", + "true", + "true", + "12.0", + "12.0", + "true", + "true", + "A", + "A", + "true", + "true", + "B", + "B", + "true", + "true", + "testCompareAndSet", + "null", + "A(1)", + "true", + "A(2)", + "true", + "1", + "2", + "3", + "4", + "4", + "4", + "4", + "5", + "6", + "7", + "8", + "8", + "8", + "8", + "false", + "8", + "false", + "8", + "8", + "8", + "8", + "8", + "8", + "8", + "8", + "8", + "8", + "8", + "8", + "8", + "8", + "testReturnValueClassCastException"); private static final String MAIN_CLASS = VarHandle.InstanceObjectField.typeName(); - private static final String JAR_ENTRY = "varhandle/InstanceObjectField.class"; + private static final List<String> JAR_ENTRIES = + ImmutableList.of( + "varhandle/InstanceObjectField.class", "varhandle/InstanceObjectField$A.class"); @Override protected String getMainClass() { @@ -28,12 +132,24 @@ } @Override - protected String getJarEntry() { - return JAR_ENTRY; + protected List<String> getJarEntries() { + return JAR_ENTRIES; } @Override - protected String getExpectedOutput() { - return EXPECTED_OUTPUT; + protected String getExpectedOutputForReferenceImplementation() { + return StringUtils.lines( + EXPECTED_OUTPUT.trim(), "Reference implementation", "Reference implementation"); + } + + @Override + protected String getExpectedOutputForArtImplementation() { + assert parameters.isDexRuntime(); + return StringUtils.lines(EXPECTED_OUTPUT.trim(), "Art implementation", "Art implementation"); + } + + @Override + protected boolean getTestWithDesugaring() { + return true; } }
diff --git a/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringInstanceStringFieldTest.java b/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringInstanceStringFieldTest.java index dfd32cf..f33f52c 100644 --- a/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringInstanceStringFieldTest.java +++ b/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringInstanceStringFieldTest.java
@@ -6,6 +6,8 @@ import com.android.tools.r8.examples.jdk9.VarHandle; import com.android.tools.r8.utils.StringUtils; +import com.google.common.collect.ImmutableList; +import java.util.List; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -28,12 +30,12 @@ } @Override - protected String getJarEntry() { - return JAR_ENTRY; + protected List<String> getJarEntries() { + return ImmutableList.of(JAR_ENTRY); } @Override - protected String getExpectedOutput() { + protected String getExpectedOutputForReferenceImplementation() { return EXPECTED_OUTPUT; } }
diff --git a/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringTestBase.java b/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringTestBase.java index 6a6c9d5..6a73d66 100644 --- a/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringTestBase.java +++ b/src/test/java/com/android/tools/r8/cf/varhandle/VarHandleDesugaringTestBase.java
@@ -4,8 +4,12 @@ package com.android.tools.r8.cf.varhandle; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertSame; import static org.junit.Assume.assumeTrue; +import com.android.tools.r8.JdkClassFileProvider; import com.android.tools.r8.R8TestBuilder; import com.android.tools.r8.TestBase; import com.android.tools.r8.TestParameters; @@ -14,8 +18,20 @@ import com.android.tools.r8.ToolHelper; import com.android.tools.r8.ToolHelper.DexVm.Version; import com.android.tools.r8.examples.jdk9.VarHandle; +import com.android.tools.r8.graph.DexItemFactory; +import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexString; +import com.android.tools.r8.references.MethodReference; +import com.android.tools.r8.references.Reference; +import com.android.tools.r8.synthesis.SyntheticItemsTestUtils; import com.android.tools.r8.utils.AndroidApiLevel; +import com.android.tools.r8.utils.IntBox; import com.android.tools.r8.utils.ZipUtils; +import com.android.tools.r8.utils.codeinspector.CodeInspector; +import com.google.common.collect.ImmutableList; +import java.io.IOException; +import java.util.List; +import java.util.stream.Collectors; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -45,9 +61,13 @@ return ""; } - protected abstract String getJarEntry(); + protected abstract List<String> getJarEntries(); - protected abstract String getExpectedOutput(); + protected abstract String getExpectedOutputForReferenceImplementation(); + + protected String getExpectedOutputForArtImplementation() { + return getExpectedOutputForReferenceImplementation(); + } // TODO(b/247076137): Remove this when all tests can run with desugaring. protected boolean getTestWithDesugaring() { @@ -60,29 +80,129 @@ testForJvm() .addProgramFiles(VarHandle.jar()) .run(parameters.getRuntime(), getMainClass()) - .assertSuccessWithOutput(getExpectedOutput()); + .assertSuccessWithOutput(getExpectedOutputForReferenceImplementation()); } + private List<byte[]> getProgramClassFileData() { + return getJarEntries().stream() + .map( + entry -> { + try { + return ZipUtils.readSingleEntry(VarHandle.jar(), entry); + } catch (IOException e) { + throw new RuntimeException(e); + } + }) + .collect(Collectors.toList()); + } + + private boolean willDesugarVarHandle() { + return parameters.isDexRuntime() && parameters.getApiLevel().isLessThan(AndroidApiLevel.T); + } + + private void inspect(CodeInspector inspector) { + IntBox unsafeCompareAndSwapInt = new IntBox(); + IntBox unsafeCompareAndSwapLong = new IntBox(); + IntBox unsafeCompareAndSwapObject = new IntBox(); + DexString compareAndSwapInt = inspector.getFactory().createString("compareAndSwapInt"); + DexString compareAndSwapLong = inspector.getFactory().createString("compareAndSwapLong"); + DexString compareAndSwapObject = inspector.getFactory().createString("compareAndSwapObject"); + // Right now we only expect one backport coming out of DesugarVarHandle - the backport with + // forwarding of Unsafe.compareAndSwapObject. + MethodReference firstBackportFromDesugarVarHandle = + SyntheticItemsTestUtils.syntheticBackportWithForwardingMethod( + Reference.classFromDescriptor(DexItemFactory.desugarVarHandleDescriptorString), + 0, + Reference.method( + Reference.classFromDescriptor("Lsun/misc/Unsafe;"), + "compareAndSwapObject", + ImmutableList.of( + Reference.typeFromDescriptor("Ljava/lang/Object;"), + Reference.LONG, + Reference.typeFromDescriptor("Ljava/lang/Object;"), + Reference.typeFromDescriptor("Ljava/lang/Object;")), + Reference.BOOL)); + inspector.forAllClasses( + clazz -> { + clazz.forAllMethods( + method -> { + method + .instructions() + .forEach( + instruction -> { + if (instruction.isInvoke()) { + DexMethod target = instruction.getMethod(); + if (target.getHolderType() == inspector.getFactory().unsafeType) { + if (target.getName() == compareAndSwapInt + || target.getName() == compareAndSwapLong) { + // All compareAndSwapInt and compareAndSwapLong stay on + // DesugarVarHandle. + assertSame( + clazz.getDexProgramClass().getType(), + inspector.getFactory().desugarVarHandleType); + unsafeCompareAndSwapInt.incrementIf( + target.getName() == compareAndSwapInt); + unsafeCompareAndSwapLong.incrementIf( + target.getName() == compareAndSwapLong); + } else if (target.getName() == compareAndSwapObject) { + // compareAndSwapObject is not on DesugarVarHandle - it must be + // backported. + assertNotSame( + clazz.getDexProgramClass().getType(), + inspector.getFactory().desugarVarHandleType); + assertEquals( + clazz.getFinalReference(), + firstBackportFromDesugarVarHandle.getHolderClass()); + assertEquals(1, clazz.allMethods().size()); + assertEquals( + firstBackportFromDesugarVarHandle, + clazz.allMethods().iterator().next().getFinalReference()); + unsafeCompareAndSwapObject.increment(); + } + } + } + }); + }); + }); + if (willDesugarVarHandle()) { + assertEquals(2, unsafeCompareAndSwapInt.get()); + assertEquals(3, unsafeCompareAndSwapLong.get()); + assertEquals(1, unsafeCompareAndSwapObject.get()); + } else { + assertEquals(0, unsafeCompareAndSwapInt.get()); + assertEquals(0, unsafeCompareAndSwapLong.get()); + assertEquals(0, unsafeCompareAndSwapObject.get()); + } + } + + // TODO(b/247076137: Also turn on VarHandle desugaring for R8 tests. @Test public void testD8() throws Throwable { assumeTrue(parameters.isDexRuntime()); if (getTestWithDesugaring()) { testForD8(parameters.getBackend()) - .addProgramClassFileData(ZipUtils.readSingleEntry(VarHandle.jar(), getJarEntry())) + .addProgramClassFileData(getProgramClassFileData()) .setMinApi(parameters.getApiLevel()) .addOptionsModification(options -> options.enableVarHandleDesugaring = true) .run(parameters.getRuntime(), getMainClass()) .applyIf( parameters.isDexRuntime() && parameters.asDexRuntime().getVersion().isOlderThanOrEqual(Version.V4_4_4), - // TODO(sgjesse): Running on 4.0.4 and 4.4.4 needs to be checked. Output seems - // correct, - // but at the same time there are VFY errors on stderr. + // TODO(b/247076137): Running on 4.0.4 and 4.4.4 needs to be checked. Output seems + // correct, but at the same time there are VFY errors on stderr. r -> r.assertFailureWithErrorThatThrows(NoSuchFieldException.class), - r -> r.assertSuccessWithOutput(getExpectedOutput())); + r -> + r.assertSuccessWithOutput( + parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.T) + && parameters + .getDexRuntimeVersion() + .isNewerThanOrEqual(Version.V13_0_0) + ? getExpectedOutputForArtImplementation() + : getExpectedOutputForReferenceImplementation())) + .inspect(this::inspect); } else { testForD8(parameters.getBackend()) - .addProgramClassFileData(ZipUtils.readSingleEntry(VarHandle.jar(), getJarEntry())) + .addProgramClassFileData(getProgramClassFileData()) .setMinApi(parameters.getApiLevel()) .run(parameters.getRuntime(), getMainClass()) .applyIf( @@ -93,7 +213,7 @@ parameters.getApiLevel().isLessThan(AndroidApiLevel.P) || parameters.asDexRuntime().getVersion().isOlderThanOrEqual(Version.V8_1_0), r -> r.assertFailure(), - r -> r.assertSuccessWithOutput(getExpectedOutput())); + r -> r.assertSuccessWithOutput(getExpectedOutputForArtImplementation())); } } @@ -101,10 +221,13 @@ @Test public void testR8() throws Throwable { testForR8(parameters.getBackend()) - // Use android.jar from Android T to get the VarHandle type. - .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T)) - .addProgramClassFileData(ZipUtils.readSingleEntry(VarHandle.jar(), getJarEntry())) - .addLibraryFiles() + .applyIf( + parameters.isDexRuntime(), + // Use android.jar from Android T to get the VarHandle type. + b -> b.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T)), + // Use system JDK to have references types including StringConcatFactory. + b -> b.addLibraryProvider(JdkClassFileProvider.fromSystemJdk())) + .addProgramClassFileData(getProgramClassFileData()) .setMinApi(parameters.getApiLevel()) .addKeepMainRule(getMainClass()) .addKeepRules(getKeepRules()) @@ -121,6 +244,10 @@ && (parameters.getApiLevel().isLessThan(AndroidApiLevel.P) || parameters.asDexRuntime().getVersion().isOlderThanOrEqual(Version.V8_1_0)), r -> r.assertFailure(), - r -> r.assertSuccessWithOutput(getExpectedOutput())); + r -> + r.assertSuccessWithOutput( + parameters.isCfRuntime() + ? getExpectedOutputForReferenceImplementation() + : getExpectedOutputForArtImplementation())); } }
diff --git a/src/test/java/com/android/tools/r8/debug/SmaliDebugTest.java b/src/test/java/com/android/tools/r8/debug/SmaliDebugTest.java index 1f41abc..1d4a205 100644 --- a/src/test/java/com/android/tools/r8/debug/SmaliDebugTest.java +++ b/src/test/java/com/android/tools/r8/debug/SmaliDebugTest.java
@@ -122,7 +122,7 @@ int target = cond.getTargets()[0] + cond.getOffset(); int linePC = -1; for (DexDebugEntry entry : info.getEntries()) { - if (entry.line == 4) { + if (entry.getPosition().getLine() == 4) { linePC = entry.address; break; }
diff --git a/src/test/java/com/android/tools/r8/debuginfo/DebugInfoInspector.java b/src/test/java/com/android/tools/r8/debuginfo/DebugInfoInspector.java index 605865f..00409e1 100644 --- a/src/test/java/com/android/tools/r8/debuginfo/DebugInfoInspector.java +++ b/src/test/java/com/android/tools/r8/debuginfo/DebugInfoInspector.java
@@ -125,7 +125,7 @@ for (int i = 0; i < entries.size(); i++) { DexDebugEntry entry = entries.get(i); // Matches each entry at 'line' that is not a zero-line increment. - if (entry.line == line && (i == 0 || entries.get(i - 1).line != line)) { + if (entry.getLine() == line && (i == 0 || entries.get(i - 1).getLine() != line)) { found++; check.accept(entry); } @@ -139,7 +139,11 @@ for (DebugLocalInfo local : entry.locals.values()) { if (local.name.toString().equals(name)) { if (found != null) { - fail("Line " + entry.line + ". Local defined multiple times for name: " + name); + fail( + "Line " + + entry.getPosition().getLine() + + ". Local defined multiple times for name: " + + name); } assertEquals(type, local.type.toString()); if (typeParameters.length > 0) { @@ -155,7 +159,7 @@ found = local; } } - assertNotNull("Line " + entry.line + ". Failed to find local with name: " + name, found); + assertNotNull("Line " + entry.getLine() + ". Failed to find local with name: " + name, found); return found; } @@ -176,9 +180,14 @@ remaining.remove(local); } } - assertEquals("Line " + entry.line + ". Found unexpected locals: " + - String.join(",", remaining.stream().map(Object::toString).collect(Collectors.toList())), - expected, expected + remaining.size()); + assertEquals( + "Line " + + entry.getLine() + + ". Found unexpected locals: " + + String.join( + ",", remaining.stream().map(Object::toString).collect(Collectors.toList())), + expected, + expected + remaining.size()); } public DexEncodedMethod getMethod() {
diff --git a/src/test/java/com/android/tools/r8/debuginfo/EnsureNoDebugInfoEmittedForPcOnlyTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/EnsureNoDebugInfoEmittedForPcOnlyTestRunner.java index c6f0994..9d8cf281 100644 --- a/src/test/java/com/android/tools/r8/debuginfo/EnsureNoDebugInfoEmittedForPcOnlyTestRunner.java +++ b/src/test/java/com/android/tools/r8/debuginfo/EnsureNoDebugInfoEmittedForPcOnlyTestRunner.java
@@ -146,7 +146,8 @@ MethodSubject main = clazz.uniqueMethodWithOriginalName("main"); List<DexDebugEntry> entries = new DexDebugEntryBuilder(main.getMethod(), inspector.getFactory()).build(); - Set<Integer> lines = entries.stream().map(e -> e.line).collect(Collectors.toSet()); + Set<Integer> lines = + entries.stream().map(e -> e.getPosition().getLine()).collect(Collectors.toSet()); assertFalse(lines.isEmpty()); }
diff --git a/src/test/java/com/android/tools/r8/debuginfo/InliningWithoutPositionsTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/InliningWithoutPositionsTestRunner.java index 6af4440..944b163 100644 --- a/src/test/java/com/android/tools/r8/debuginfo/InliningWithoutPositionsTestRunner.java +++ b/src/test/java/com/android/tools/r8/debuginfo/InliningWithoutPositionsTestRunner.java
@@ -97,7 +97,8 @@ assertEquals(TEST_CLASS, line.className); assertEquals("main", line.methodName); // Expected line number could be PC based or increments. - // The test need not check what it is, just that all methods have been fully inlined. + // The test need not check what it is, just that all methods have been fully + // inlined. assertEquals(2, rawStackTrace.size()); }) .inspectStackTrace(
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesWatchEventTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesWatchEventTest.java new file mode 100644 index 0000000..6f3b986 --- /dev/null +++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/FilesWatchEventTest.java
@@ -0,0 +1,124 @@ +// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.android.tools.r8.desugar.desugaredlibrary.jdk11; + +import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.DEFAULT_SPECIFICATIONS; +import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11_PATH; +import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE; + +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestRuntime.CfVm; +import com.android.tools.r8.ToolHelper; +import com.android.tools.r8.ToolHelper.DexVm.Version; +import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase; +import com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification; +import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification; +import com.android.tools.r8.utils.StringUtils; +import com.google.common.collect.ImmutableList; +import java.io.IOException; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.WatchEvent; +import java.nio.file.WatchKey; +import java.nio.file.WatchService; +import java.util.List; +import org.junit.Assume; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class FilesWatchEventTest extends DesugaredLibraryTestBase { + + private static final String EXPECTED_RESULT = + StringUtils.lines("true", "true", "true", "ENTRY_CREATE", "foo", "true"); + private static final String EXPECTED_RESULT_DESUGARING = + StringUtils.lines("class java.lang.UnsupportedOperationException :: null"); + + private final TestParameters parameters; + private final LibraryDesugaringSpecification libraryDesugaringSpecification; + private final CompilationSpecification compilationSpecification; + + @Parameters(name = "{0}, spec: {1}, {2}") + public static List<Object[]> data() { + return buildParameters( + // Skip Android 4.4.4 due to missing libjavacrypto. + getTestParameters() + .withCfRuntime(CfVm.JDK11) + .withDexRuntime(Version.V4_0_4) + .withDexRuntimesStartingFromIncluding(Version.V5_1_1) + .withAllApiLevels() + .build(), + ImmutableList.of(JDK11_PATH), + DEFAULT_SPECIFICATIONS); + } + + public FilesWatchEventTest( + TestParameters parameters, + LibraryDesugaringSpecification libraryDesugaringSpecification, + CompilationSpecification compilationSpecification) { + this.parameters = parameters; + this.libraryDesugaringSpecification = libraryDesugaringSpecification; + this.compilationSpecification = compilationSpecification; + } + + @Test + public void test() throws Throwable { + if (parameters.isCfRuntime() && !ToolHelper.isWindows()) { + // Reference runtime, we use Jdk 11 since this is Jdk 11 desugared library, not that Jdk 8 + // behaves differently on this test. + Assume.assumeTrue(parameters.isCfRuntime(CfVm.JDK11) && !ToolHelper.isWindows()); + testForJvm() + .addInnerClasses(getClass()) + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutput(EXPECTED_RESULT); + return; + } + testForDesugaredLibrary(parameters, libraryDesugaringSpecification, compilationSpecification) + .addInnerClasses(getClass()) + .addKeepMainRule(TestClass.class) + .compile() + .withArt6Plus64BitsLib() + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutput( + libraryDesugaringSpecification.usesPlatformFileSystem(parameters) + ? EXPECTED_RESULT + : EXPECTED_RESULT_DESUGARING); + } + + public static class TestClass { + + public static void main(String[] args) throws IOException, InterruptedException { + Path dir = Files.createTempDirectory("tmpDictWatch"); + FileSystem fs = FileSystems.getDefault(); + + try (WatchService watcher = fs.newWatchService()) { + WatchKey myKey = dir.register(watcher, ENTRY_CREATE); + System.out.println(myKey.isValid()); + System.out.println(myKey.watchable().equals(dir)); + + Path file = dir.resolve("foo"); + Files.createFile(file); + + WatchKey key = watcher.take(); + System.out.println(key.equals(myKey)); + + WatchEvent<?> event = myKey.pollEvents().iterator().next(); + System.out.println(event.kind()); + System.out.println(event.context()); + + System.out.println(myKey.reset()); + Files.delete(file); + } catch (UnsupportedOperationException e) { + System.out.println(e.getClass() + " :: " + e.getMessage()); + } + + Files.delete(dir); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11NioFileTests.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11NioFileTests.java index 8274628..9f3a07b 100644 --- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11NioFileTests.java +++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdktests/Jdk11NioFileTests.java
@@ -12,6 +12,7 @@ import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.D8_L8SHRINK; import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION; import static com.android.tools.r8.utils.FileUtils.JAVA_EXTENSION; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import com.android.tools.r8.D8TestCompileResult; @@ -27,8 +28,10 @@ import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification; import com.android.tools.r8.references.Reference; import com.android.tools.r8.transformers.ClassFileTransformer; +import com.android.tools.r8.utils.AndroidApiLevel; import com.android.tools.r8.utils.StringUtils; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -39,6 +42,7 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Set; import java.util.stream.Collectors; import org.jetbrains.annotations.NotNull; import org.junit.Assume; @@ -74,7 +78,8 @@ // TODO(134732760): Support Dalvik VMs, currently fails because libjavacrypto is required // and present only in ART runtimes. getTestParameters() - .withDexRuntimesStartingFromIncluding(Version.V10_0_0) + .withDexRuntime(Version.V4_0_4) + .withDexRuntimesStartingFromIncluding(Version.V5_1_1) .withAllApiLevels() .build(), specs, @@ -106,6 +111,16 @@ "module-info.java" }); + private static final Set<String> EXPECTED_FAILING_CLASSES_DESUGARING_26 = + ImmutableSet.of( + // SecureDirectoryStream is not supported with desugared library, which leads to issues. + "DirectoryStreamSecureDS", + // Watch services are supported on high apis with desugared library, however, only + // equality + // and not identity is preserved which fails the tests. + "WatchServiceBasic", + "WatchServiceSensitivityModifier"); + // We distinguish 2 kinds of tests: // - Main tests, which are run by running the main method, and succeed if no error is raised. // - TestNG tests, which are run using testNG. @@ -272,12 +287,12 @@ .compile() .withArt6Plus64BitsLib(); int success = 0; - int failures = 0; + List<String> failingClasses = new ArrayList<>(); for (String mainTestClass : SUCCESSFUL_MAIN_TESTS) { SingleTestRunResult<?> run = compileResult.run(parameters.getRuntime(), mainTestClass); if (run.getExitCode() != 0) { System.out.println("Main Fail " + mainTestClass); - failures++; + failingClasses.add(mainTestClass); } else { success++; } @@ -288,17 +303,31 @@ parameters.getRuntime(), "TestNGMainRunner", verbosity, testNGTestClass); if (!result.getStdOut().contains(StringUtils.lines(testNGTestClass + ": SUCCESS"))) { System.out.println("TestNG Fail " + testNGTestClass); - failures++; + failingClasses.add(testNGTestClass); } else { success++; } } - // TODO(b/234689867): Understand and fix these issues. - // Most issues seem to come from the missing secure.properties file. This file is not accessed - // in all tests on all API levels, hence a different number of failures on each level. - System.out.println("Successes :" + success + "; failures " + failures); - assertTrue(success >= 11); - assertTrue(failures <= 20); + System.out.println("Successes:" + success + "; failures:" + failingClasses.size()); + if (!failingClasses.isEmpty()) { + System.out.println("Failing classes: " + failingClasses); + } + if (parameters.getDexRuntimeVersion().isOlderThan(Version.V8_1_0)) { + // TODO(b/234689867): DesugaredFileSystemProvider is exercised, fix or understand remaining + // failures. + int sevenOffset = parameters.getDexRuntimeVersion() == Version.V7_0_0 ? -1 : 0; + int shrinkOffset = compilationSpecification.isL8Shrink() ? -1 : 0; + assertTrue(success >= 18 + sevenOffset + shrinkOffset); + } else if (parameters.getApiLevel().isLessThan(AndroidApiLevel.O)) { + // Desugaring high api level. + assertEquals(26, success); + assertEquals(3, failingClasses.size()); + assertTrue(failingClasses.containsAll(EXPECTED_FAILING_CLASSES_DESUGARING_26)); + } else { + // No desugaring or partial desugaring, high api level. + assertEquals(29, success); + assertEquals(0, failingClasses.size()); + } } @Test
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/varhandle/DesugarVarHandle.java b/src/test/java/com/android/tools/r8/ir/desugar/varhandle/DesugarVarHandle.java index af2bb5b..30b3240 100644 --- a/src/test/java/com/android/tools/r8/ir/desugar/varhandle/DesugarVarHandle.java +++ b/src/test/java/com/android/tools/r8/ir/desugar/varhandle/DesugarVarHandle.java
@@ -96,7 +96,7 @@ return new RuntimeException("java.lang.invoke.WrongMethodTypeException"); } - int toIntIfPossible(Object value) { + int toIntIfPossible(Object value, boolean forReturnType) { if (value instanceof Integer) { return (Integer) value; } @@ -109,14 +109,18 @@ if (value instanceof Short) { return (Short) value; } - throw desugarWrongMethodTypeException(); + if (forReturnType) { + throw new ClassCastException(); + } else { + throw desugarWrongMethodTypeException(); + } } - long toLongIfPossible(Object value) { + long toLongIfPossible(Object value, boolean forReturnType) { if (value instanceof Long) { return (Long) value; } - return toIntIfPossible(value); + return toIntIfPossible(value, forReturnType); } // get variants. @@ -131,19 +135,31 @@ } int getInt(Object ct1) { - return U.getInt(ct1, offset); + if (type == int.class) { + return U.getInt(ct1, offset); + } else if (type == long.class) { + return (int) U.getLong(ct1, offset); + } else { + return toIntIfPossible(U.getObject(ct1, offset), true); + } } long getLong(Object ct1) { - return U.getLong(ct1, offset); + if (type == long.class) { + return U.getLong(ct1, offset); + } else if (type == int.class) { + return U.getInt(ct1, offset); + } else { + return toLongIfPossible(U.getObject(ct1, offset), true); + } } // set variants. void set(Object ct1, Object newValue) { if (type == int.class) { - setInt(ct1, toIntIfPossible(newValue)); + setInt(ct1, toIntIfPossible(newValue, false)); } else if (type == long.class) { - setLong(ct1, toLongIfPossible(newValue)); + setLong(ct1, toLongIfPossible(newValue, false)); } else { U.putObject(ct1, offset, newValue); } @@ -162,37 +178,39 @@ void setLong(Object ct1, long newValue) { if (type == long.class) { U.putLong(ct1, offset, newValue); - } else { + } else if (type == int.class) { throw desugarWrongMethodTypeException(); + } else { + U.putObject(ct1, offset, Long.valueOf(newValue)); } } - boolean compareAndSet(Object ct1, Object expetedValue, Object newValue) { + boolean compareAndSet(Object ct1, Object expectedValue, Object newValue) { if (type == int.class) { return U.compareAndSwapInt( - ct1, offset, toIntIfPossible(expetedValue), toIntIfPossible((newValue))); + ct1, offset, toIntIfPossible(expectedValue, false), toIntIfPossible(newValue, false)); } if (type == long.class) { return U.compareAndSwapLong( - ct1, offset, toLongIfPossible(expetedValue), toLongIfPossible((newValue))); + ct1, offset, toLongIfPossible(expectedValue, false), toLongIfPossible(newValue, false)); } - return U.compareAndSwapObject(ct1, offset, expetedValue, newValue); + return U.compareAndSwapObject(ct1, offset, expectedValue, newValue); } - boolean compareAndSetInt(Object ct1, int expetedValue, int newValue) { + boolean compareAndSetInt(Object ct1, int expectedValue, int newValue) { if (type == int.class) { - return U.compareAndSwapInt(ct1, offset, expetedValue, newValue); + return U.compareAndSwapInt(ct1, offset, expectedValue, newValue); } else if (type == long.class) { - return U.compareAndSwapLong(ct1, offset, expetedValue, newValue); + return U.compareAndSwapLong(ct1, offset, expectedValue, newValue); } else { - return compareAndSet(ct1, expetedValue, newValue); + return compareAndSet(ct1, expectedValue, newValue); } } - boolean compareAndSetLong(Object ct1, long expetedValue, long newValue) { + boolean compareAndSetLong(Object ct1, long expectedValue, long newValue) { if (type == long.class) { - return U.compareAndSwapLong(ct1, offset, expetedValue, newValue); + return U.compareAndSwapLong(ct1, offset, expectedValue, newValue); } - return compareAndSet(ct1, expetedValue, newValue); + return compareAndSet(ct1, expectedValue, newValue); } }
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionAnnotationTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionAnnotationTest.java index 9bee408..f9d98e3 100644 --- a/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionAnnotationTest.java +++ b/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionAnnotationTest.java
@@ -3,6 +3,7 @@ // 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; @@ -53,13 +54,14 @@ .addKeepRules(rules) .addKeepMainRule(TestClass.class) .setMinApi(parameters.getApiLevel()) + .allowUnusedProguardConfigurationRules() .run(parameters.getRuntime(), TestClass.class) .assertSuccessWithOutput(EXPECTED) .inspect(this::checkOutput); } public List<Class<?>> getInputClasses() { - return ImmutableList.of(TestClass.class, A.class, B.class); + return ImmutableList.of(TestClass.class, A.class, B.class, C.class); } public List<byte[]> getInputClassesWithoutAnnotations() throws Exception { @@ -83,7 +85,9 @@ private void checkOutput(CodeInspector inspector) { assertThat(inspector.clazz(A.class), isPresent()); assertThat(inspector.clazz(B.class), isPresent()); - assertThat(inspector.clazz(B.class).uniqueMethodWithOriginalName("bar"), isPresent()); + assertThat(inspector.clazz(C.class), isAbsent()); + assertThat(inspector.clazz(B.class).method("void", "bar"), isPresent()); + assertThat(inspector.clazz(B.class).method("void", "bar", "int"), isAbsent()); } static class A { @@ -94,18 +98,36 @@ // Ensure that the class B remains as we are looking it up by reflected name. @KeepTarget(classConstant = B.class), // Ensure the method 'bar' remains as we are invoking it by reflected name. - @KeepTarget(classConstant = B.class, methodName = "bar") + @KeepTarget( + classConstant = B.class, + methodName = "bar", + methodParameters = {}, + methodReturnType = "void") }) public void foo() throws Exception { Class<?> clazz = Class.forName(A.class.getTypeName().replace("$A", "$B")); clazz.getDeclaredMethod("bar").invoke(clazz); } + + // This annotation is not active as its implicit precondition "void A.foo(int)" is not used. + @UsesReflection({@KeepTarget(classConstant = C.class)}) + public void foo(int unused) { + // Unused. + } } static class B { public static void bar() { System.out.println("Hello, world"); } + + public static void bar(int ignore) { + throw new RuntimeException("UNUSED"); + } + } + + static class C { + // Unused. } static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionFieldAnnotationTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionFieldAnnotationTest.java new file mode 100644 index 0000000..b6cc560 --- /dev/null +++ b/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionFieldAnnotationTest.java
@@ -0,0 +1,131 @@ +// 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 com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.keepanno.annotations.KeepTarget; +import com.android.tools.r8.keepanno.annotations.UsesReflection; +import com.android.tools.r8.utils.AndroidApiLevel; +import com.android.tools.r8.utils.StringUtils; +import com.android.tools.r8.utils.codeinspector.CodeInspector; +import com.google.common.collect.ImmutableList; +import java.util.ArrayList; +import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class KeepUsesReflectionFieldAnnotationTest extends TestBase { + + static final String EXPECTED = StringUtils.lines("Hello, world"); + + private final TestParameters parameters; + + @Parameterized.Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withDefaultRuntimes().withApiLevel(AndroidApiLevel.B).build(); + } + + public KeepUsesReflectionFieldAnnotationTest(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void testReference() throws Exception { + testForRuntime(parameters) + .addProgramClasses(getInputClasses()) + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutput(EXPECTED); + } + + @Test + public void testWithRuleExtraction() throws Exception { + List<String> rules = getExtractedKeepRules(); + testForR8(parameters.getBackend()) + .addProgramClassFileData(getInputClassesWithoutAnnotations()) + .addKeepRules(rules) + .addKeepMainRule(TestClass.class) + .setMinApi(parameters.getApiLevel()) + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutput(EXPECTED) + .inspect(this::checkOutput); + } + + public List<Class<?>> getInputClasses() { + return ImmutableList.of(TestClass.class, A.class, B.class); + } + + public List<byte[]> getInputClassesWithoutAnnotations() throws Exception { + List<Class<?>> classes = getInputClasses(); + List<byte[]> transformed = new ArrayList<>(classes.size()); + for (Class<?> clazz : classes) { + transformed.add(transformer(clazz).removeAllAnnotations().transform()); + } + return transformed; + } + + public List<String> getExtractedKeepRules() throws Exception { + List<Class<?>> classes = getInputClasses(); + List<String> rules = new ArrayList<>(); + for (Class<?> clazz : classes) { + rules.addAll(KeepEdgeAnnotationsTest.getKeepRulesForClass(clazz)); + } + return rules; + } + + private void checkOutput(CodeInspector inspector) { + assertThat(inspector.clazz(A.class), isPresent()); + assertThat(inspector.clazz(A.class).uniqueFieldWithOriginalName("classNameForB"), isPresent()); + assertThat(inspector.clazz(B.class), isPresent()); + assertThat(inspector.clazz(B.class).init(), isPresent()); + assertThat(inspector.clazz(B.class).init("int"), isAbsent()); + } + + static class A { + + @UsesReflection({ + @KeepTarget(classConstant = B.class), + @KeepTarget( + classConstant = B.class, + methodName = "<init>", + methodParameters = {}), + }) + public final String classNameForB = + System.nanoTime() == 0 + ? null + : "com.android.tools.r8.keepanno.KeepUsesReflectionFieldAnnotationTest$B"; + + public B foo() throws Exception { + return (B) Class.forName(classNameForB).getDeclaredConstructor().newInstance(); + } + } + + static class B { + B() { + // Used. + } + + B(int unused) { + // Unused. + } + + public void bar() { + System.out.println("Hello, world"); + } + } + + static class TestClass { + + public static void main(String[] args) throws Exception { + new A().foo().bar(); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionOnFieldTest.java b/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionOnFieldTest.java new file mode 100644 index 0000000..6739144 --- /dev/null +++ b/src/test/java/com/android/tools/r8/keepanno/KeepUsesReflectionOnFieldTest.java
@@ -0,0 +1,113 @@ +// 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 com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.keepanno.annotations.KeepTarget; +import com.android.tools.r8.keepanno.annotations.UsesReflection; +import com.android.tools.r8.utils.AndroidApiLevel; +import com.android.tools.r8.utils.StringUtils; +import com.android.tools.r8.utils.codeinspector.CodeInspector; +import com.google.common.collect.ImmutableList; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class KeepUsesReflectionOnFieldTest extends TestBase { + + static final String EXPECTED = StringUtils.lines("Hello, world"); + + private final TestParameters parameters; + + @Parameterized.Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withDefaultRuntimes().withApiLevel(AndroidApiLevel.B).build(); + } + + public KeepUsesReflectionOnFieldTest(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void testReference() throws Exception { + testForRuntime(parameters) + .addProgramClasses(getInputClasses()) + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutput(EXPECTED); + } + + @Test + public void testWithRuleExtraction() throws Exception { + List<String> rules = getExtractedKeepRules(); + System.out.println(rules); + testForR8(parameters.getBackend()) + .addProgramClassFileData(getInputClassesWithoutAnnotations()) + .addKeepRules(rules) + .addKeepMainRule(TestClass.class) + .setMinApi(parameters.getApiLevel()) + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutput(EXPECTED) + .inspect(this::checkOutput); + } + + public List<Class<?>> getInputClasses() { + return ImmutableList.of(TestClass.class, A.class); + } + + public List<byte[]> getInputClassesWithoutAnnotations() throws Exception { + List<Class<?>> classes = getInputClasses(); + List<byte[]> transformed = new ArrayList<>(classes.size()); + for (Class<?> clazz : classes) { + transformed.add(transformer(clazz).removeAllAnnotations().transform()); + } + return transformed; + } + + public List<String> getExtractedKeepRules() throws Exception { + List<Class<?>> classes = getInputClasses(); + List<String> rules = new ArrayList<>(); + for (Class<?> clazz : classes) { + rules.addAll(KeepEdgeAnnotationsTest.getKeepRulesForClass(clazz)); + } + return rules; + } + + private void checkOutput(CodeInspector inspector) { + assertThat(inspector.clazz(A.class), isPresent()); + assertThat(inspector.clazz(A.class).uniqueFieldWithOriginalName("fieldA"), isPresent()); + assertThat(inspector.clazz(A.class).uniqueFieldWithOriginalName("fieldB"), isAbsent()); + } + + static class A { + + public String fieldA = "Hello, world"; + public Integer fieldB = 42; + + @UsesReflection({@KeepTarget(classConstant = A.class, fieldType = "java.lang.String")}) + public void foo() throws Exception { + for (Field field : getClass().getDeclaredFields()) { + if (field.getType().equals(String.class)) { + System.out.println(field.get(this)); + } + } + } + } + + static class TestClass { + + public static void main(String[] args) throws Exception { + new A().foo(); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java b/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java index ff769a2..1b67cfc 100644 --- a/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java +++ b/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
@@ -10,8 +10,10 @@ import com.android.tools.r8.references.ClassReference; import com.android.tools.r8.references.MethodReference; import com.android.tools.r8.references.Reference; +import com.android.tools.r8.references.TypeReference; import com.android.tools.r8.synthesis.SyntheticNaming.Phase; import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind; +import com.google.common.collect.ImmutableList; import java.lang.reflect.Method; import org.hamcrest.Matcher; @@ -51,6 +53,24 @@ originalMethod.getMethodDescriptor()); } + public static MethodReference syntheticBackportWithForwardingMethod( + ClassReference clazz, int id, MethodReference method) { + // For backports with forwarding the backported method is not static, so the original method + // signature has the receiver type pre-pended. + ImmutableList.Builder<TypeReference> builder = ImmutableList.builder(); + builder.add(method.getHolderClass()).addAll(method.getFormalTypes()); + MethodReference methodWithReceiverForForwarding = + Reference.method( + method.getHolderClass(), + method.getMethodName(), + builder.build(), + method.getReturnType()); + return Reference.methodFromDescriptor( + syntheticBackportWithForwardingClass(clazz, id), + syntheticMethodName(), + methodWithReceiverForForwarding.getMethodDescriptor()); + } + public static ClassReference syntheticOutlineClass(Class<?> clazz, int id) { return syntheticClass(clazz, naming.OUTLINE, id); } @@ -85,6 +105,11 @@ return syntheticClass(classReference, naming.BACKPORT, id); } + public static ClassReference syntheticBackportWithForwardingClass( + ClassReference classReference, int id) { + return syntheticClass(classReference, naming.BACKPORT_WITH_FORWARDING, id); + } + public static ClassReference syntheticTwrCloseResourceClass(Class<?> clazz, int id) { return syntheticClass(clazz, naming.TWR_CLOSE_RESOURCE, id); }
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 bcd5360..1974bcd 100644 --- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java +++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -1494,7 +1494,7 @@ @Override public FieldVisitor visitField( int access, String name, String descriptor, String signature, Object value) { - FieldVisitor fv = visitField(access, name, descriptor, signature, value); + FieldVisitor fv = super.visitField(access, name, descriptor, signature, value); return new FieldVisitor(ASM_VERSION, fv) { @Override public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
diff --git a/tools/r8_release.py b/tools/r8_release.py index fe921e2..78fd031 100755 --- a/tools/r8_release.py +++ b/tools/r8_release.py
@@ -193,8 +193,7 @@ # Don't upload if requested not to, or if changes are not committed due # to --use-existing-work-branch if not options.no_upload and not options.use_existing_work_branch: - # TODO(b/260680525): Consider removing "-o banned-words~skip" if b/260680525 is resolved. - process = subprocess.Popen(['repo', 'upload', '.', '--verify', '-o' 'banned-words~skip'], + process = subprocess.Popen(['repo', 'upload', '.', '--verify'], stdin=subprocess.PIPE) return process.communicate(input=b'y\n')[0]