[KeepAnno] Extend types with patterns for primitives, arrays and classes
Bug: b/248408342
Change-Id: Ib6cd70cc883e4d3efdafcb12060a407d0c276e77
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/ClassNameParser.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/ClassNameParser.java
index 3034d7a..9137b3e 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/ClassNameParser.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/ClassNameParser.java
@@ -11,9 +11,11 @@
import com.android.tools.r8.keepanno.ast.AnnotationConstants.ClassNamePattern;
import com.android.tools.r8.keepanno.ast.KeepPackagePattern;
import com.android.tools.r8.keepanno.ast.KeepQualifiedClassNamePattern;
+import com.android.tools.r8.keepanno.ast.KeepTypePattern;
import com.android.tools.r8.keepanno.ast.KeepUnqualfiedClassNamePattern;
import com.android.tools.r8.keepanno.ast.ParsingContext;
import com.android.tools.r8.keepanno.ast.ParsingContext.AnnotationParsingContext;
+import com.android.tools.r8.keepanno.ast.ParsingContext.PropertyParsingContext;
import com.google.common.collect.ImmutableList;
import java.util.function.Consumer;
import org.objectweb.asm.AnnotationVisitor;
@@ -44,23 +46,32 @@
TypeProperty.TYPE_NAME,
name,
value,
- type ->
- setValue.accept(
- KeepQualifiedClassNamePattern.exactFromDescriptor(type.getDescriptor())));
+ type -> setValue.accept(typeToClassType(type, getParsingContext().property(name))));
case CONSTANT:
return new TypeParser(getParsingContext())
.tryProperty(
TypeProperty.TYPE_CONSTANT,
name,
value,
- type ->
- setValue.accept(
- KeepQualifiedClassNamePattern.exactFromDescriptor(type.getDescriptor())));
+ type -> setValue.accept(typeToClassType(type, getParsingContext().property(name))));
default:
return false;
}
}
+ KeepQualifiedClassNamePattern typeToClassType(
+ KeepTypePattern typePattern, PropertyParsingContext parsingContext) {
+ return typePattern.match(
+ KeepQualifiedClassNamePattern::any,
+ primitiveTypePattern -> {
+ throw parsingContext.error("Invalid use of primitive type where class type was expected");
+ },
+ arrayTypePattern -> {
+ throw parsingContext.error("Invalid use of array type where class type was expected");
+ },
+ classNamePattern -> classNamePattern);
+ }
+
@Override
AnnotationVisitor tryPropertyAnnotation(
ClassNameProperty property,
@@ -71,7 +82,7 @@
case PATTERN:
{
AnnotationParsingContext parsingContext =
- new AnnotationParsingContext(getParsingContext(), descriptor);
+ getParsingContext().property(name).annotation(descriptor);
PackageNameParser packageParser = new PackageNameParser(parsingContext);
ClassSimpleNameParser nameParser = new ClassSimpleNameParser(parsingContext);
packageParser.setProperty(ClassNamePattern.packageName, PackageNameProperty.NAME);
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
index 6f963c4..21645d5 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReaderUtils.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReaderUtils.java
@@ -3,9 +3,9 @@
// 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;
+import com.android.tools.r8.keepanno.ast.ParsingContext.PropertyParsingContext;
+import java.util.function.Function;
/**
* Utilities for mapping the syntax used in annotations to the keep-edge AST.
@@ -57,14 +57,20 @@
throw new IllegalStateException("Unexpected descriptor: " + descriptor);
}
- public static KeepTypePattern typePatternFromString(String string) {
+ public static KeepTypePattern typePatternFromString(
+ String string, PropertyParsingContext property) {
if (string.equals("<any>")) {
return KeepTypePattern.any();
}
- return KeepTypePattern.fromDescriptor(getDescriptorFromJavaType(string));
+ return KeepTypePattern.fromDescriptor(internalDescriptorFromJavaType(string, property::error));
}
public static String getDescriptorFromJavaType(String type) {
+ return internalDescriptorFromJavaType(type, IllegalStateException::new);
+ }
+
+ private static String internalDescriptorFromJavaType(
+ String type, Function<String, RuntimeException> onError) {
switch (type) {
case "boolean":
return "Z";
@@ -87,11 +93,11 @@
StringBuilder builder = new StringBuilder(type.length());
int i = type.length() - 1;
if (i < 0) {
- throw new KeepEdgeException("Invalid empty type");
+ throw onError.apply("Invalid empty type");
}
while (type.charAt(i) == ']') {
if (type.charAt(--i) != '[') {
- throw new KeepEdgeException("Invalid type: '" + type + "'");
+ throw onError.apply("Invalid type: '" + type + "'");
}
builder.append('[');
--i;
@@ -106,19 +112,4 @@
}
}
}
-
- public static KeepMethodReturnTypePattern methodReturnTypeFromTypeName(String returnType) {
- if ("void".equals(returnType)) {
- return KeepMethodReturnTypePattern.voidType();
- }
- return KeepMethodReturnTypePattern.fromType(typePatternFromString(returnType));
- }
-
- public static KeepMethodReturnTypePattern methodReturnTypeFromTypeDescriptor(
- String returnTypeDesc) {
- if ("V".equals(returnTypeDesc)) {
- return KeepMethodReturnTypePattern.voidType();
- }
- return KeepMethodReturnTypePattern.fromType(KeepTypePattern.fromDescriptor(returnTypeDesc));
- }
}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/MethodReturnTypeParser.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/MethodReturnTypeParser.java
index 8a87a70..ccf5183 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/MethodReturnTypeParser.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/MethodReturnTypeParser.java
@@ -8,26 +8,74 @@
import com.android.tools.r8.keepanno.ast.KeepMethodReturnTypePattern;
import com.android.tools.r8.keepanno.ast.KeepTypePattern;
import com.android.tools.r8.keepanno.ast.ParsingContext;
+import java.util.function.Consumer;
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.Type;
+/**
+ * Parser for parsing method return types.
+ *
+ * <p>This parser wraps a type parser and adds support for parsing the string name {@code "void"} or
+ * the class constant {@code void.class} as the void method return type.
+ */
public class MethodReturnTypeParser
- extends ConvertingPropertyParser<KeepTypePattern, KeepMethodReturnTypePattern, TypeProperty> {
+ extends PropertyParserBase<KeepMethodReturnTypePattern, TypeProperty> {
+
+ private final TypeParser typeParser;
public MethodReturnTypeParser(ParsingContext parsingContext) {
- super(new TypeParser(parsingContext), MethodReturnTypeParser::fromType);
+ super(parsingContext);
+ typeParser = new TypeParser(parsingContext);
}
- // TODO(b/248408342): It may be problematic dealing with the "void" value at the point of return
- // as it is not actually a type in the normal sense. Consider a set up where the "void" cases are
- // special cased before dispatch to the underlying type parser.
- private static KeepMethodReturnTypePattern fromType(KeepTypePattern typePattern) {
- if (typePattern == null) {
- return null;
+ static Consumer<KeepTypePattern> wrap(Consumer<KeepMethodReturnTypePattern> fn) {
+ return t -> fn.accept(KeepMethodReturnTypePattern.fromType(t));
+ }
+
+ @Override
+ public KeepMethodReturnTypePattern getValue() {
+ return super.getValue();
+ }
+
+ @Override
+ boolean tryProperty(
+ TypeProperty property,
+ String name,
+ Object value,
+ Consumer<KeepMethodReturnTypePattern> setValue) {
+ if (property == TypeProperty.TYPE_NAME && "void".equals(value)) {
+ setValue.accept(KeepMethodReturnTypePattern.voidType());
+ return true;
}
- // Special-case method return types to allow void.
- String descriptor = typePattern.getDescriptor();
- if (descriptor.equals("V") || descriptor.equals("Lvoid;")) {
- return KeepMethodReturnTypePattern.voidType();
+ if (property == TypeProperty.TYPE_CONSTANT && Type.getType("V").equals(value)) {
+ setValue.accept(KeepMethodReturnTypePattern.voidType());
+ return true;
}
- return KeepMethodReturnTypePattern.fromType(typePattern);
+ return typeParser.tryProperty(property, name, value, wrap(setValue));
+ }
+
+ @Override
+ public boolean tryPropertyEnum(
+ TypeProperty property,
+ String name,
+ String descriptor,
+ String value,
+ Consumer<KeepMethodReturnTypePattern> setValue) {
+ return typeParser.tryPropertyEnum(property, name, descriptor, value, wrap(setValue));
+ }
+
+ @Override
+ AnnotationVisitor tryPropertyArray(
+ TypeProperty property, String name, Consumer<KeepMethodReturnTypePattern> setValue) {
+ return typeParser.tryPropertyArray(property, name, wrap(setValue));
+ }
+
+ @Override
+ AnnotationVisitor tryPropertyAnnotation(
+ TypeProperty property,
+ String name,
+ String descriptor,
+ Consumer<KeepMethodReturnTypePattern> setValue) {
+ return typeParser.tryPropertyAnnotation(property, name, descriptor, wrap(setValue));
}
}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/TypeParser.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/TypeParser.java
index c1682c9..a40f450 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/TypeParser.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/TypeParser.java
@@ -10,7 +10,6 @@
import com.android.tools.r8.keepanno.ast.KeepTypePattern;
import com.android.tools.r8.keepanno.ast.ParsingContext;
import com.android.tools.r8.keepanno.ast.ParsingContext.AnnotationParsingContext;
-import com.android.tools.r8.keepanno.utils.Unimplemented;
import java.util.function.Consumer;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.Type;
@@ -33,7 +32,9 @@
TypeProperty property, String name, Object value, Consumer<KeepTypePattern> setValue) {
switch (property) {
case TYPE_NAME:
- setValue.accept(KeepEdgeReaderUtils.typePatternFromString((String) value));
+ setValue.accept(
+ KeepEdgeReaderUtils.typePatternFromString(
+ (String) value, getParsingContext().property(name)));
return true;
case TYPE_CONSTANT:
setValue.accept(KeepTypePattern.fromDescriptor(((Type) value).getDescriptor()));
@@ -49,15 +50,14 @@
switch (property) {
case TYPE_PATTERN:
{
- AnnotationParsingContext parsingContext =
- new AnnotationParsingContext(getParsingContext(), descriptor);
- TypeParser typeParser =
- new TypeParser(parsingContext.group(TypePattern.typePatternGroup));
+ AnnotationParsingContext context =
+ getParsingContext().property(name).annotation(descriptor);
+ TypeParser typeParser = new TypeParser(context);
typeParser.setProperty(TypePattern.name, TypeProperty.TYPE_NAME);
typeParser.setProperty(TypePattern.constant, TypeProperty.TYPE_CONSTANT);
typeParser.setProperty(TypePattern.classNamePattern, TypeProperty.CLASS_NAME_PATTERN);
return new ParserVisitor(
- parsingContext,
+ context,
descriptor,
typeParser,
() -> setValue.accept(typeParser.getValueOrDefault(KeepTypePattern.any())));
@@ -69,15 +69,7 @@
ClassNameProperty.PATTERN,
name,
descriptor,
- classNamePattern -> {
- if (classNamePattern.isExact()) {
- setValue.accept(
- KeepTypePattern.fromDescriptor(classNamePattern.getExactDescriptor()));
- } else {
- // TODO(b/248408342): Extend the AST type patterns.
- throw new Unimplemented("Non-exact class patterns are not implemented yet");
- }
- });
+ value -> setValue.accept(KeepTypePattern.fromClass(value)));
}
default:
return null;
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepArrayTypePattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepArrayTypePattern.java
new file mode 100644
index 0000000..96bdd8a
--- /dev/null
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepArrayTypePattern.java
@@ -0,0 +1,56 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.keepanno.ast;
+
+import java.util.Objects;
+
+public class KeepArrayTypePattern {
+
+ private static final KeepArrayTypePattern ANY =
+ new KeepArrayTypePattern(KeepTypePattern.any(), 1);
+
+ public static KeepArrayTypePattern getAny() {
+ return ANY;
+ }
+
+ private final KeepTypePattern baseType;
+ private final int dimensions;
+
+ public KeepArrayTypePattern(KeepTypePattern baseType, int dimensions) {
+ assert baseType != null;
+ assert dimensions > 0;
+ this.baseType = baseType;
+ this.dimensions = dimensions;
+ }
+
+ public boolean isAny() {
+ return ANY.equals(this);
+ }
+
+ public KeepTypePattern getBaseType() {
+ return baseType;
+ }
+
+ public int getDimensions() {
+ return dimensions;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof KeepArrayTypePattern)) {
+ return false;
+ }
+ KeepArrayTypePattern that = (KeepArrayTypePattern) o;
+ return dimensions == that.dimensions && Objects.equals(baseType, that.baseType);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(baseType, dimensions);
+ }
+}
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 34dcab5..04485d7 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
@@ -44,7 +44,15 @@
* CLASS_ITEM_PATTERN ::= class QUALIFIED_CLASS_NAME_PATTERN instance-of INSTANCE_OF_PATTERN
* MEMBER_ITEM_PATTERN ::= CLASS_ITEM_REFERENCE { MEMBER_PATTERN }
*
- * TYPE_PATTERN ::= any | exact type-descriptor
+ * TYPE_PATTERN
+ * ::= any
+ * | PRIMITIVE_TYPE_PATTERN
+ * | ARRAY_TYPE_PATTERN
+ * | QUALIFIED_CLASS_NAME_PATTERN
+ *
+ * PRIMITIVE_TYPE_PATTERN ::= any | boolean | byte | char | short | int | long | float | double
+ * ARRAY_TYPE_PATTERN ::= any | TYPE_PATTERN dimensions(N > 0)
+ *
* PACKAGE_PATTERN ::= any | exact package-name
*
* QUALIFIED_CLASS_NAME_PATTERN
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepPrimitiveTypePattern.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepPrimitiveTypePattern.java
new file mode 100644
index 0000000..f79093d
--- /dev/null
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/KeepPrimitiveTypePattern.java
@@ -0,0 +1,95 @@
+// Copyright (c) 2024, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.keepanno.ast;
+
+import com.google.common.collect.ImmutableMap;
+import java.util.Map;
+import java.util.function.Consumer;
+
+public class KeepPrimitiveTypePattern {
+
+ private static final KeepPrimitiveTypePattern ANY = new KeepPrimitiveTypePattern('*');
+ private static final KeepPrimitiveTypePattern BOOLEAN = new KeepPrimitiveTypePattern('Z');
+ private static final KeepPrimitiveTypePattern BYTE = new KeepPrimitiveTypePattern('B');
+ private static final KeepPrimitiveTypePattern CHAR = new KeepPrimitiveTypePattern('C');
+ private static final KeepPrimitiveTypePattern SHORT = new KeepPrimitiveTypePattern('S');
+ private static final KeepPrimitiveTypePattern INT = new KeepPrimitiveTypePattern('I');
+ private static final KeepPrimitiveTypePattern LONG = new KeepPrimitiveTypePattern('J');
+ private static final KeepPrimitiveTypePattern FLOAT = new KeepPrimitiveTypePattern('F');
+ private static final KeepPrimitiveTypePattern DOUBLE = new KeepPrimitiveTypePattern('D');
+
+ private static final Map<String, KeepPrimitiveTypePattern> PRIMITIVES =
+ populate(BOOLEAN, BYTE, CHAR, SHORT, INT, LONG, FLOAT, DOUBLE);
+
+ private static ImmutableMap<String, KeepPrimitiveTypePattern> populate(
+ KeepPrimitiveTypePattern... types) {
+ ImmutableMap.Builder<String, KeepPrimitiveTypePattern> builder = ImmutableMap.builder();
+ for (KeepPrimitiveTypePattern type : types) {
+ builder.put(type.getDescriptor(), type);
+ }
+ return builder.build();
+ }
+
+ public static KeepPrimitiveTypePattern getAny() {
+ return ANY;
+ }
+
+ public static KeepPrimitiveTypePattern getBoolean() {
+ return BOOLEAN;
+ }
+
+ public static KeepPrimitiveTypePattern getByte() {
+ return BYTE;
+ }
+
+ public static KeepPrimitiveTypePattern getChar() {
+ return CHAR;
+ }
+
+ public static KeepPrimitiveTypePattern getShort() {
+ return SHORT;
+ }
+
+ public static KeepPrimitiveTypePattern getInt() {
+ return INT;
+ }
+
+ public static KeepPrimitiveTypePattern getLong() {
+ return LONG;
+ }
+
+ public static KeepPrimitiveTypePattern getFloat() {
+ return FLOAT;
+ }
+
+ public static KeepPrimitiveTypePattern getDouble() {
+ return DOUBLE;
+ }
+
+ private final char descriptor;
+
+ public KeepPrimitiveTypePattern(char descriptor) {
+ this.descriptor = descriptor;
+ }
+
+ public boolean isAny() {
+ return this == ANY;
+ }
+
+ public char getDescriptorChar() {
+ if (isAny()) {
+ throw new KeepEdgeException("No descriptor exists for 'any' primitive");
+ }
+ return descriptor;
+ }
+
+ public String getDescriptor() {
+ return Character.toString(getDescriptorChar());
+ }
+
+ public static void forEachPrimitive(Consumer<KeepPrimitiveTypePattern> fn) {
+ PRIMITIVES.values().forEach(fn);
+ }
+}
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 dd56be4..b7c2b0d 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
@@ -3,16 +3,60 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.keepanno.ast;
+import com.google.common.collect.ImmutableMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
public abstract class KeepTypePattern {
public static KeepTypePattern any() {
return Any.getInstance();
}
- public static KeepTypePattern fromDescriptor(String typeDescriptor) {
- return new Some(typeDescriptor);
+ public static KeepTypePattern fromPrimitive(KeepPrimitiveTypePattern type) {
+ return type.isAny() ? PrimitiveType.ANY : PrimitiveType.PRIMITIVES.get(type.getDescriptor());
}
+ public static KeepTypePattern fromArray(KeepArrayTypePattern type) {
+ return new ArrayType(type);
+ }
+
+ public static KeepTypePattern fromClass(KeepQualifiedClassNamePattern type) {
+ return new ClassType(type);
+ }
+
+ public static KeepTypePattern fromDescriptor(String typeDescriptor) {
+ char c = typeDescriptor.charAt(0);
+ if (c == 'L') {
+ int end = typeDescriptor.length() - 1;
+ if (typeDescriptor.charAt(end) != ';') {
+ throw new KeepEdgeException("Invalid type descriptor: " + typeDescriptor);
+ }
+ return fromClass(KeepQualifiedClassNamePattern.exactFromDescriptor(typeDescriptor));
+ }
+ if (c == '[') {
+ int dim = 1;
+ while (typeDescriptor.charAt(dim) == '[') {
+ dim++;
+ }
+ KeepTypePattern baseType = fromDescriptor(typeDescriptor.substring(dim));
+ return fromArray(new KeepArrayTypePattern(baseType, dim));
+ }
+ PrimitiveType primitiveType = PrimitiveType.PRIMITIVES.get(typeDescriptor);
+ if (primitiveType != null) {
+ return primitiveType;
+ }
+ throw new KeepEdgeException("Invalid type descriptor: " + typeDescriptor);
+ }
+
+ public abstract <T> T match(
+ Supplier<T> onAny,
+ Function<KeepPrimitiveTypePattern, T> onPrimitive,
+ Function<KeepArrayTypePattern, T> onArray,
+ Function<KeepQualifiedClassNamePattern, T> onClass);
+
public boolean isAny() {
return false;
}
@@ -21,44 +65,6 @@
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
- @SuppressWarnings("EqualsGetClass")
- 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();
@@ -68,6 +74,15 @@
}
@Override
+ public <T> T match(
+ Supplier<T> onAny,
+ Function<KeepPrimitiveTypePattern, T> onPrimitive,
+ Function<KeepArrayTypePattern, T> onArray,
+ Function<KeepQualifiedClassNamePattern, T> onClass) {
+ return onAny.get();
+ }
+
+ @Override
public boolean isAny() {
return true;
}
@@ -87,4 +102,117 @@
return "<any>";
}
}
+
+ private static class PrimitiveType extends KeepTypePattern {
+
+ private static final PrimitiveType ANY = new PrimitiveType(KeepPrimitiveTypePattern.getAny());
+ private static final Map<String, PrimitiveType> PRIMITIVES = populate();
+
+ private static Map<String, PrimitiveType> populate() {
+ ImmutableMap.Builder<String, PrimitiveType> builder = ImmutableMap.builder();
+ KeepPrimitiveTypePattern.forEachPrimitive(
+ primitive -> {
+ builder.put(primitive.getDescriptor(), new PrimitiveType(primitive));
+ });
+ return builder.build();
+ }
+
+ private final KeepPrimitiveTypePattern type;
+
+ private PrimitiveType(KeepPrimitiveTypePattern type) {
+ this.type = type;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return this == obj;
+ }
+
+ @Override
+ public int hashCode() {
+ return System.identityHashCode(this);
+ }
+
+ @Override
+ public String toString() {
+ return getDescriptor();
+ }
+
+ @Override
+ public <T> T match(
+ Supplier<T> onAny,
+ Function<KeepPrimitiveTypePattern, T> onPrimitive,
+ Function<KeepArrayTypePattern, T> onArray,
+ Function<KeepQualifiedClassNamePattern, T> onClass) {
+ return onPrimitive.apply(type);
+ }
+ }
+
+ private static class ClassType extends KeepTypePattern {
+ private final KeepQualifiedClassNamePattern type;
+
+ public ClassType(KeepQualifiedClassNamePattern type) {
+ this.type = type;
+ }
+
+ @Override
+ public <T> T match(
+ Supplier<T> onAny,
+ Function<KeepPrimitiveTypePattern, T> onPrimitive,
+ Function<KeepArrayTypePattern, T> onArray,
+ Function<KeepQualifiedClassNamePattern, T> onClass) {
+ return onClass.apply(type);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof ClassType)) {
+ return false;
+ }
+ ClassType classType = (ClassType) o;
+ return Objects.equals(type, classType.type);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type);
+ }
+ }
+
+ private static class ArrayType extends KeepTypePattern {
+ private final KeepArrayTypePattern type;
+
+ public ArrayType(KeepArrayTypePattern type) {
+ this.type = type;
+ }
+
+ @Override
+ public <T> T match(
+ Supplier<T> onAny,
+ Function<KeepPrimitiveTypePattern, T> onPrimitive,
+ Function<KeepArrayTypePattern, T> onArray,
+ Function<KeepQualifiedClassNamePattern, T> onClass) {
+ return onArray.apply(type);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof ArrayType)) {
+ return false;
+ }
+ ArrayType arrayType = (ArrayType) o;
+ return Objects.equals(type, arrayType.type);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type);
+ }
+ }
}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/ast/ParsingContext.java b/src/keepanno/java/com/android/tools/r8/keepanno/ast/ParsingContext.java
index d732c65..0d83a25 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/ast/ParsingContext.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/ast/ParsingContext.java
@@ -31,10 +31,33 @@
public abstract String getContextFrameAsString();
+ public boolean isSynthetic() {
+ return false;
+ }
+
+ private ParsingContext nonSyntheticParent() {
+ // We don't want to maintain nested property groups as they are "synthetic" and only the
+ // inner-most group is useful when diagnosing an error.
+ if (isSynthetic()) {
+ ParsingContext parent = getParentContext();
+ assert !parent.isSynthetic();
+ return parent;
+ }
+ return this;
+ }
+
public GroupParsingContext group(String propertyGroupDescription) {
return new GroupParsingContext(this, propertyGroupDescription);
}
+ public AnnotationParsingContext annotation(String annotationDescriptor) {
+ return new AnnotationParsingContext(this, annotationDescriptor);
+ }
+
+ public PropertyParsingContext property(String propertyName) {
+ return new PropertyParsingContext(this, propertyName);
+ }
+
public static class ClassParsingContext extends ParsingContext {
private final String className;
@@ -143,7 +166,7 @@
private final String annotationDescriptor;
public AnnotationParsingContext(ParsingContext parentContext, String annotationDescriptor) {
- this.parentContext = parentContext;
+ this.parentContext = parentContext.nonSyntheticParent();
this.annotationDescriptor = annotationDescriptor;
}
@@ -182,13 +205,7 @@
private final String propertyGroupDescription;
public GroupParsingContext(ParsingContext parentContext, String propertyGroupDescription) {
- // We don't want to maintain nested property groups as they are "synthetic" and only the
- // inner-most group useful for uses in diagnosing an error.
- if (parentContext instanceof GroupParsingContext) {
- parentContext = parentContext.getParentContext();
- }
- assert !(parentContext instanceof GroupParsingContext);
- this.parentContext = parentContext;
+ this.parentContext = parentContext.nonSyntheticParent();
this.propertyGroupDescription = propertyGroupDescription;
}
@@ -197,6 +214,13 @@
}
@Override
+ public boolean isSynthetic() {
+ // The property "groups" are not actual source info and should only be used in top-level
+ // reporting.
+ return true;
+ }
+
+ @Override
public String getHolderName() {
return parentContext.getHolderName();
}
@@ -216,4 +240,38 @@
return getPropertyGroupDescription();
}
}
+
+ public static class PropertyParsingContext extends ParsingContext {
+ private final ParsingContext parentContext;
+ private final String propertyName;
+
+ public PropertyParsingContext(ParsingContext parentContext, String propertyName) {
+ this.parentContext = parentContext.nonSyntheticParent();
+ this.propertyName = propertyName;
+ }
+
+ public String getPropertyName() {
+ return propertyName;
+ }
+
+ @Override
+ public String getHolderName() {
+ return parentContext.getHolderName();
+ }
+
+ @Override
+ public ParsingContext getParentContext() {
+ return parentContext;
+ }
+
+ @Override
+ public String getContextType() {
+ return "property";
+ }
+
+ @Override
+ public String getContextFrameAsString() {
+ return getPropertyName();
+ }
+ }
}
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/RulePrinter.java b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/RulePrinter.java
index 94694a2..518eeba 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/RulePrinter.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/RulePrinter.java
@@ -25,6 +25,7 @@
public RulePrinter append(String str) {
assert !str.contains("*");
assert !str.contains("(...)");
+ assert !str.contains("%");
return appendWithoutBackReferenceAssert(str);
}
@@ -45,6 +46,10 @@
return appendWithoutBackReferenceAssert("***");
}
+ public RulePrinter appendPercent() {
+ return appendWithoutBackReferenceAssert("%");
+ }
+
public RulePrinter appendAnyParameters() {
return appendWithoutBackReferenceAssert("(...)");
}
@@ -91,6 +96,11 @@
}
@Override
+ public RulePrinter appendPercent() {
+ return addBackRef("%");
+ }
+
+ @Override
public RulePrinter appendAnyParameters() {
// TODO(b/265892343): R8 does not yet support back reference to `...`.
return appendWithoutBackReferenceAssert("(...)");
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/RulePrintingUtils.java b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/RulePrintingUtils.java
index 5d5c357..20acbb7 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/RulePrintingUtils.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/RulePrintingUtils.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.keepanno.keeprules;
import com.android.tools.r8.keepanno.ast.AccessVisibility;
+import com.android.tools.r8.keepanno.ast.KeepArrayTypePattern;
import com.android.tools.r8.keepanno.ast.KeepClassItemPattern;
import com.android.tools.r8.keepanno.ast.KeepEdgeException;
import com.android.tools.r8.keepanno.ast.KeepEdgeMetaInfo;
@@ -21,6 +22,7 @@
import com.android.tools.r8.keepanno.ast.KeepOptions;
import com.android.tools.r8.keepanno.ast.KeepOptions.KeepOption;
import com.android.tools.r8.keepanno.ast.KeepPackagePattern;
+import com.android.tools.r8.keepanno.ast.KeepPrimitiveTypePattern;
import com.android.tools.r8.keepanno.ast.KeepQualifiedClassNamePattern;
import com.android.tools.r8.keepanno.ast.KeepTypePattern;
import com.android.tools.r8.keepanno.ast.KeepUnqualfiedClassNamePattern;
@@ -179,11 +181,34 @@
return printType(builder, returnTypePattern.asType());
}
- private static RulePrinter printType(RulePrinter builder, KeepTypePattern typePattern) {
- if (typePattern.isAny()) {
- return builder.appendTripleStar();
+ private static RulePrinter printType(RulePrinter printer, KeepTypePattern typePattern) {
+ return typePattern.match(
+ printer::appendTripleStar,
+ primitivePattern -> printPrimitiveType(printer, primitivePattern),
+ arrayTypePattern -> printArrayType(printer, arrayTypePattern),
+ classTypePattern -> printClassName(classTypePattern, printer));
+ }
+
+ private static RulePrinter printPrimitiveType(
+ RulePrinter printer, KeepPrimitiveTypePattern primitiveTypePattern) {
+ if (primitiveTypePattern.isAny()) {
+ // Matching any primitive type uses the wildcard syntax `%`
+ return printer.appendPercent();
}
- return builder.append(descriptorToJavaType(typePattern.getDescriptor()));
+ return printer.append(descriptorToJavaType(primitiveTypePattern.getDescriptor()));
+ }
+
+ private static RulePrinter printArrayType(
+ RulePrinter printer, KeepArrayTypePattern arrayTypePattern) {
+ // The "any" array is simply dimension one of any type. Just assert that to be true as the
+ // general case will emit the correct syntax: ***[]
+ assert !arrayTypePattern.isAny()
+ || (arrayTypePattern.getDimensions() == 1 && arrayTypePattern.getBaseType().isAny());
+ printType(printer, arrayTypePattern.getBaseType());
+ for (int i = 0; i < arrayTypePattern.getDimensions(); i++) {
+ printer.append("[]");
+ }
+ return printer;
}
public static RulePrinter printMemberAccess(
@@ -254,7 +279,7 @@
public static RulePrinter printClassName(
KeepQualifiedClassNamePattern classNamePattern, RulePrinter printer) {
if (classNamePattern.isAny()) {
- return printer.appendStar();
+ return printer.appendDoubleStar();
}
printPackagePrefix(classNamePattern.getPackagePattern(), printer);
return printSimpleClassName(classNamePattern.getNamePattern(), printer);
diff --git a/src/test/java/com/android/tools/r8/keepanno/ArrayPatternsTest.java b/src/test/java/com/android/tools/r8/keepanno/ArrayPatternsTest.java
new file mode 100644
index 0000000..a7cf54f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/ArrayPatternsTest.java
@@ -0,0 +1,136 @@
+// Copyright (c) 2024, 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 com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
+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.KeepItemKind;
+import com.android.tools.r8.keepanno.annotations.KeepTarget;
+import com.android.tools.r8.keepanno.annotations.TypePattern;
+import com.android.tools.r8.keepanno.annotations.UsedByReflection;
+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.Arrays;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ArrayPatternsTest extends TestBase {
+
+ static final String EXPECTED =
+ StringUtils.lines("int[] [1, 2, 3]", "int[][] [[42]]", "Integer[][][] [[[333]]]");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withDefaultRuntimes().withApiLevel(AndroidApiLevel.B).build();
+ }
+
+ public ArrayPatternsTest(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 testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .enableExperimentalKeepAnnotations()
+ .addProgramClasses(getInputClasses())
+ .setMinApi(parameters)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED)
+ .inspect(this::checkOutput);
+ }
+
+ public List<Class<?>> getInputClasses() {
+ return ImmutableList.of(TestClass.class, A.class, B.class);
+ }
+
+ private void checkOutput(CodeInspector inspector) {
+ assertThat(inspector.clazz(B.class), isPresentAndRenamed());
+ assertThat(inspector.clazz(B.class).method("void", "bar"), isAbsent());
+ assertThat(inspector.clazz(B.class).method("void", "bar", "int[]"), isPresent());
+ assertThat(inspector.clazz(B.class).method("void", "bar", "int[][]"), isPresent());
+ assertThat(inspector.clazz(B.class).method("void", "bar", "int[][][]"), isAbsent());
+ assertThat(
+ inspector.clazz(B.class).method("void", "bar", "java.lang.Integer[][][]"), isPresent());
+ }
+
+ static class A {
+
+ @UsesReflection({
+ @KeepTarget(
+ classConstant = B.class,
+ methodName = "bar",
+ methodParameterTypePatterns = {@TypePattern(constant = int[].class)}),
+ @KeepTarget(
+ classConstant = B.class,
+ methodName = "bar",
+ methodParameters = {"int[][]"}),
+ @KeepTarget(
+ classConstant = B.class,
+ methodName = "bar",
+ methodParameterTypePatterns = {@TypePattern(name = "java.lang.Integer[][][]")}),
+ })
+ public void foo() throws Exception {
+ // Invoke the first and second method.
+ B.class.getDeclaredMethod("bar", int[].class).invoke(null, (Object) new int[] {1, 2, 3});
+ B.class
+ .getDeclaredMethod("bar", int[][].class)
+ .invoke(null, (Object) new int[][] {new int[] {42}});
+ B.class
+ .getDeclaredMethod("bar", Integer[][][].class)
+ .invoke(null, (Object) new Integer[][][] {new Integer[][] {new Integer[] {333}}});
+ }
+ }
+
+ static class B {
+ public static void bar() {
+ throw new RuntimeException("UNUSED");
+ }
+
+ public static void bar(int[] value) {
+ System.out.println("int[] " + Arrays.toString(value));
+ }
+
+ public static void bar(int[][] value) {
+ System.out.println("int[][] " + Arrays.deepToString(value));
+ }
+
+ public static void bar(int[][][] value) {
+ throw new RuntimeException("UNUSED");
+ }
+
+ public static void bar(Integer[][][] value) {
+ System.out.println("Integer[][][] " + Arrays.deepToString(value));
+ }
+ }
+
+ static class TestClass {
+
+ @UsedByReflection(kind = KeepItemKind.CLASS_AND_METHODS)
+ public static void main(String[] args) throws Exception {
+ new A().foo();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/ast/KeepEdgeAstTest.java b/src/test/java/com/android/tools/r8/keepanno/ast/KeepEdgeAstTest.java
index f0ea424..8a83cf9 100644
--- a/src/test/java/com/android/tools/r8/keepanno/ast/KeepEdgeAstTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/ast/KeepEdgeAstTest.java
@@ -53,7 +53,7 @@
.build();
assertEquals(
StringUtils.unixLines(
- "-keep class * { void finalize(); }", "-keepclassmembers class * { *; }"),
+ "-keep class ** { void finalize(); }", "-keepclassmembers class ** { *; }"),
extract(edge));
}
@@ -83,8 +83,8 @@
// targeted members.
assertEquals(
StringUtils.unixLines(
- "-keep,allow" + allows + " class * { void finalize(); }",
- "-keepclassmembers,allow" + allows + " class * { *; }"),
+ "-keep,allow" + allows + " class ** { void finalize(); }",
+ "-keepclassmembers,allow" + allows + " class ** { *; }"),
extract(edge));
}
@@ -110,8 +110,8 @@
// Allow is just the ordered list of options.
assertEquals(
StringUtils.unixLines(
- "-keep,allowshrinking,allowobfuscation class * { void finalize(); }",
- "-keepclassmembers,allowshrinking,allowobfuscation class * { *; }"),
+ "-keep,allowshrinking,allowobfuscation class ** { void finalize(); }",
+ "-keepclassmembers,allowshrinking,allowobfuscation class ** { *; }"),
extract(edge));
}
diff --git a/src/test/java/com/android/tools/r8/keepanno/classpatterns/ClassNamePatternsTest.java b/src/test/java/com/android/tools/r8/keepanno/classpatterns/ClassNamePatternsTest.java
index 3468b4c..699ddd6 100644
--- a/src/test/java/com/android/tools/r8/keepanno/classpatterns/ClassNamePatternsTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/classpatterns/ClassNamePatternsTest.java
@@ -30,7 +30,19 @@
static final Class<?> A2 = com.android.tools.r8.keepanno.classpatterns.pkg2.A.class;
static final Class<?> B2 = com.android.tools.r8.keepanno.classpatterns.pkg2.B.class;
- static final String EXPECTED_ALL = StringUtils.lines("pkg1.A", "pkg1.B", "pkg2.A", "pkg2.B");
+ static final String EXPECTED_ALL =
+ StringUtils.lines(
+ "pkg1.A",
+ "pkg1.A: pkg1.A",
+ "pkg1.B",
+ "pkg1.B: pkg1.B",
+ "pkg2.A",
+ "pkg2.A: pkg2.A",
+ "pkg2.B",
+ "pkg2.B: pkg2.B");
+
+ static final String EXPECTED_ALL_NON_VOID =
+ StringUtils.lines("pkg1.A", "pkg1.B", "pkg2.A", "pkg2.B");
static final String EXPECTED_PKG = StringUtils.lines("pkg1.A", "pkg1.B");
static final String EXPECTED_NAME = StringUtils.lines("pkg1.B", "pkg2.B");
static final String EXPECTED_SINGLE = StringUtils.lines("pkg2.A");
@@ -71,6 +83,11 @@
}
@Test
+ public void testAllNoVoidR8() throws Exception {
+ runTestR8(TestAllNoVoid.class, EXPECTED_ALL_NON_VOID);
+ }
+
+ @Test
public void testPkgR8() throws Exception {
runTestR8(TestPkg.class, EXPECTED_PKG);
}
@@ -85,6 +102,11 @@
runTestR8(TestSingle.class, EXPECTED_SINGLE);
}
+ @Test
+ public void testSingleNonExactR8() throws Exception {
+ runTestR8(TestSingleWithNonExactReturnTypeClassPattern.class, EXPECTED_SINGLE);
+ }
+
public List<Class<?>> getBaseInputClasses() {
return ImmutableList.of(Util.class, A1, B1, A2, B2);
}
@@ -97,6 +119,7 @@
try {
Class<?> clazz = Class.forName(type);
System.out.println(clazz.getDeclaredMethod("foo").invoke(null));
+ clazz.getDeclaredMethod("foo", String.class).invoke(null, pkg + "." + name);
} catch (ClassNotFoundException ignored) {
} catch (IllegalAccessException ignored) {
} catch (InvocationTargetException ignored) {
@@ -115,7 +138,8 @@
// The empty class pattern is equivalent to "any class".
classNamePattern = @ClassNamePattern(),
methodName = "foo",
- methodReturnTypeConstant = String.class)
+ // The empty type pattern used in a return-type context will match 'void'.
+ methodReturnTypePattern = @TypePattern())
})
public void foo() throws Exception {
Util.lookupClassesAndInvokeMethods();
@@ -127,6 +151,25 @@
}
}
+ static class TestAllNoVoid {
+
+ @UsesReflection({
+ @KeepTarget(
+ kind = KeepItemKind.CLASS_AND_METHODS,
+ methodName = "foo",
+ // Matching any class does not include 'void'.
+ methodReturnTypePattern = @TypePattern(classNamePattern = @ClassNamePattern()))
+ })
+ public void foo() throws Exception {
+ Util.lookupClassesAndInvokeMethods();
+ }
+
+ @UsedByReflection(kind = KeepItemKind.CLASS_AND_METHODS)
+ public static void main(String[] args) throws Exception {
+ new TestAllNoVoid().foo();
+ }
+ }
+
static class TestPkg {
@UsesReflection({
@@ -189,4 +232,26 @@
new TestSingle().foo();
}
}
+
+ static class TestSingleWithNonExactReturnTypeClassPattern {
+
+ @UsesReflection(
+ @KeepTarget(
+ kind = KeepItemKind.CLASS_AND_METHODS,
+ classNamePattern =
+ @ClassNamePattern(
+ simpleName = "A",
+ packageName = "com.android.tools.r8.keepanno.classpatterns.pkg2"),
+ methodName = "foo",
+ methodReturnTypePattern =
+ @TypePattern(classNamePattern = @ClassNamePattern(simpleName = "String"))))
+ public void foo() throws Exception {
+ Util.lookupClassesAndInvokeMethods();
+ }
+
+ @UsedByReflection(kind = KeepItemKind.CLASS_AND_METHODS)
+ public static void main(String[] args) throws Exception {
+ new TestSingleWithNonExactReturnTypeClassPattern().foo();
+ }
+ }
}
diff --git a/src/test/java/com/android/tools/r8/keepanno/classpatterns/pkg1/A.java b/src/test/java/com/android/tools/r8/keepanno/classpatterns/pkg1/A.java
index eab0e5f..377601c 100644
--- a/src/test/java/com/android/tools/r8/keepanno/classpatterns/pkg1/A.java
+++ b/src/test/java/com/android/tools/r8/keepanno/classpatterns/pkg1/A.java
@@ -9,4 +9,8 @@
public static String foo() {
return "pkg1.A";
}
+
+ public static void foo(String arg) {
+ System.out.println("pkg1.A: " + arg);
+ }
}
diff --git a/src/test/java/com/android/tools/r8/keepanno/classpatterns/pkg1/B.java b/src/test/java/com/android/tools/r8/keepanno/classpatterns/pkg1/B.java
index edeb6b8..b37f208 100644
--- a/src/test/java/com/android/tools/r8/keepanno/classpatterns/pkg1/B.java
+++ b/src/test/java/com/android/tools/r8/keepanno/classpatterns/pkg1/B.java
@@ -9,4 +9,8 @@
public static String foo() {
return "pkg1.B";
}
+
+ public static void foo(String arg) {
+ System.out.println("pkg1.B: " + arg);
+ }
}
diff --git a/src/test/java/com/android/tools/r8/keepanno/classpatterns/pkg2/A.java b/src/test/java/com/android/tools/r8/keepanno/classpatterns/pkg2/A.java
index ca0667e..1670103 100644
--- a/src/test/java/com/android/tools/r8/keepanno/classpatterns/pkg2/A.java
+++ b/src/test/java/com/android/tools/r8/keepanno/classpatterns/pkg2/A.java
@@ -9,4 +9,8 @@
public static String foo() {
return "pkg2.A";
}
+
+ public static void foo(String arg) {
+ System.out.println("pkg2.A: " + arg);
+ }
}
diff --git a/src/test/java/com/android/tools/r8/keepanno/classpatterns/pkg2/B.java b/src/test/java/com/android/tools/r8/keepanno/classpatterns/pkg2/B.java
index c98e746..ab18f98 100644
--- a/src/test/java/com/android/tools/r8/keepanno/classpatterns/pkg2/B.java
+++ b/src/test/java/com/android/tools/r8/keepanno/classpatterns/pkg2/B.java
@@ -9,4 +9,8 @@
public static String foo() {
return "pkg2.B";
}
+
+ public static void foo(String arg) {
+ System.out.println("pkg2.B: " + arg);
+ }
}