[ApiModel] Extend visitors to recurse up in the hierarchy
Bug: 193414761
Bug: 188388130
Change-Id: Idabf2c232de527fb3c1a48da86447c4136fd3074
diff --git a/src/main/java/com/android/tools/r8/androidapi/AndroidApiClass.java b/src/main/java/com/android/tools/r8/androidapi/AndroidApiClass.java
index ee0ffb2..ed6767a 100644
--- a/src/main/java/com/android/tools/r8/androidapi/AndroidApiClass.java
+++ b/src/main/java/com/android/tools/r8/androidapi/AndroidApiClass.java
@@ -4,6 +4,8 @@
package com.android.tools.r8.androidapi;
+import static com.android.tools.r8.utils.AndroidApiLevel.getAndroidApiLevel;
+
import com.android.tools.r8.references.ClassReference;
import com.android.tools.r8.references.FieldReference;
import com.android.tools.r8.references.MethodReference;
@@ -28,38 +30,56 @@
public abstract int getMemberCount();
- public abstract TraversalContinuation visitFields(
- BiFunction<FieldReference, AndroidApiLevel, TraversalContinuation> visitor);
+ public TraversalContinuation visitFields(
+ BiFunction<FieldReference, AndroidApiLevel, TraversalContinuation> visitor) {
+ return visitFields(visitor, classReference, 1);
+ }
- public abstract TraversalContinuation visitMethods(
- BiFunction<MethodReference, AndroidApiLevel, TraversalContinuation> visitor);
+ public TraversalContinuation visitMethods(
+ BiFunction<MethodReference, AndroidApiLevel, TraversalContinuation> visitor) {
+ return visitMethods(visitor, classReference, 1);
+ }
+
+ protected abstract TraversalContinuation visitFields(
+ BiFunction<FieldReference, AndroidApiLevel, TraversalContinuation> visitor,
+ ClassReference holder,
+ int minApiClass);
+
+ protected abstract TraversalContinuation visitMethods(
+ BiFunction<MethodReference, AndroidApiLevel, TraversalContinuation> visitor,
+ ClassReference holder,
+ int minApiClass);
protected TraversalContinuation visitField(
+ BiFunction<FieldReference, AndroidApiLevel, TraversalContinuation> visitor,
+ ClassReference holder,
+ int minApiClass,
+ int minApiField,
String name,
- String typeDescriptor,
- int apiLevel,
- BiFunction<FieldReference, AndroidApiLevel, TraversalContinuation> visitor) {
+ String typeDescriptor) {
return visitor.apply(
- Reference.field(classReference, name, Reference.typeFromDescriptor(typeDescriptor)),
- AndroidApiLevel.getAndroidApiLevel(apiLevel));
+ Reference.field(holder, name, Reference.typeFromDescriptor(typeDescriptor)),
+ getAndroidApiLevel(Integer.max(minApiClass, minApiField)));
}
protected TraversalContinuation visitMethod(
+ BiFunction<MethodReference, AndroidApiLevel, TraversalContinuation> visitor,
+ ClassReference holder,
+ int minApiClass,
+ int minApiMethod,
String name,
String[] formalTypeDescriptors,
- String returnType,
- int apiLevel,
- BiFunction<MethodReference, AndroidApiLevel, TraversalContinuation> visitor) {
+ String returnType) {
List<TypeReference> typeReferenceList = new ArrayList<>(formalTypeDescriptors.length);
for (String formalTypeDescriptor : formalTypeDescriptors) {
typeReferenceList.add(Reference.typeFromDescriptor(formalTypeDescriptor));
}
return visitor.apply(
Reference.method(
- classReference,
+ holder,
name,
typeReferenceList,
returnType == null ? null : Reference.returnTypeFromDescriptor(returnType)),
- AndroidApiLevel.getAndroidApiLevel(apiLevel));
+ getAndroidApiLevel(Integer.max(minApiClass, minApiMethod)));
}
}
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index c9e6675..312aaf4 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1406,6 +1406,22 @@
}
return TraversalContinuation.CONTINUE;
}
+
+ @Override
+ protected TraversalContinuation visitFields(
+ BiFunction<FieldReference, AndroidApiLevel, TraversalContinuation> visitor,
+ ClassReference holder,
+ int minApi) {
+ return null;
+ }
+
+ @Override
+ protected TraversalContinuation visitMethods(
+ BiFunction<MethodReference, AndroidApiLevel, TraversalContinuation> visitor,
+ ClassReference holder,
+ int minApi) {
+ return null;
+ }
});
});
}
diff --git a/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseBuilderGenerator.java b/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseBuilderGenerator.java
index dfaecd1..8c139fe 100644
--- a/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseBuilderGenerator.java
+++ b/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseBuilderGenerator.java
@@ -13,6 +13,7 @@
import static org.objectweb.asm.Opcodes.DUP;
import static org.objectweb.asm.Opcodes.F_SAME1;
import static org.objectweb.asm.Opcodes.IFEQ;
+import static org.objectweb.asm.Opcodes.ILOAD;
import static org.objectweb.asm.Opcodes.INVOKEINTERFACE;
import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
import static org.objectweb.asm.Opcodes.INVOKESTATIC;
@@ -27,7 +28,9 @@
import com.android.tools.r8.references.TypeReference;
import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate;
import com.android.tools.r8.transformers.MethodTransformer;
+import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.IntBox;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
@@ -89,10 +92,11 @@
.collect(Collectors.toList());
for (ParsedApiClass apiClass : apiClasses) {
+ String apiClassDescriptor = getApiClassReference(apiClass).getDescriptor();
consumer.accept(
- getApiClassDescriptor(apiClass),
+ apiClassDescriptor,
transformer(AndroidApiDatabaseClassTemplate.class)
- .setClassDescriptor(getApiClassDescriptor(apiClass))
+ .setClassDescriptor(apiClassDescriptor)
.addMethodTransformer(getInitTransformer(apiClass))
.addMethodTransformer(getApiLevelTransformer(apiClass))
.addMethodTransformer(getGetMemberCountTransformer(apiClass))
@@ -103,6 +107,8 @@
.removeMethods(MethodPredicate.onName("placeHolderForGetMemberCount"))
.removeMethods(MethodPredicate.onName("placeHolderForVisitFields"))
.removeMethods(MethodPredicate.onName("placeHolderForVisitMethods"))
+ .setSourceFile(
+ DescriptorUtils.getSimpleClassNameFromDescriptor(apiClassDescriptor) + ".java")
.computeMaxs()
.transform(ClassWriter.COMPUTE_MAXS));
}
@@ -137,13 +143,17 @@
.replace("Template", "ForPackage_" + pkg.replace(".", "_")));
}
- private static String getApiClassDescriptor(ParsedApiClass apiClass) {
- return DescriptorUtils.javaTypeToDescriptor(
- AndroidApiDatabaseClassTemplate.class
- .getTypeName()
- .replace(
- "Template",
- "ForClass_" + apiClass.getClassReference().getTypeName().replace(".", "_")));
+ private static ClassReference getApiClassReference(ParsedApiClass apiClass) {
+ return fromTemplate(apiClass.getClassReference());
+ }
+
+ private static ClassReference fromTemplate(ClassReference classReference) {
+ String descriptor =
+ DescriptorUtils.javaTypeToDescriptor(
+ AndroidApiDatabaseClassTemplate.class
+ .getTypeName()
+ .replace("Template", "ForClass_" + classReference.getTypeName().replace(".", "_")));
+ return Reference.classFromDescriptor(descriptor);
}
// The transformer below changes AndroidApiDatabaseClassTemplate.<init> from:
@@ -190,17 +200,24 @@
// placeHolder();
// return TraversalContinuation.CONTINUE;
// into
- // TraversalContinuation s1 = visitField("field1", "descriptor1", apiLevel1, visitor)
+ // TraversalContinuation s1 = visitField(visitor, holder, minApiClass, apiLevel1, "field1",
+ // "descriptor1")
// if (s1.shouldBreak()) {
// return s1;
// }
- // TraversalContinuation s2 = visitField("field2", "descriptor2", apiLevel2, visitor)
+ // TraversalContinuation s2 = visitField(visitor, holder, minApiClass, apiLevel2, "field2",
+ // // "descriptor2")
// if (s2.shouldBreak()) {
// return s2;
// }
// ...
+ // AndroidApiClassForClass_class_name1() super1 = new AndroidApiClassForClass_class_name1();
+ // TraversalContinuation sN = super1.visitFields(
+ // visitor, holder, max(minApiClass, minApiForLink));
+ // ...
// return TraversalContinuation.CONTINUE;
private static MethodTransformer getVisitFieldsTransformer(ParsedApiClass apiClass) {
+ IntBox lineNumberBox = new IntBox(0);
return replaceCode(
"placeHolderForVisitFields",
transformer -> {
@@ -208,19 +225,26 @@
(apiLevel, references) -> {
references.forEach(
reference -> {
+ Label labelStart = new Label();
+ transformer.visitLabel(labelStart);
+ transformer.visitLineNumber(lineNumberBox.getAndIncrement(), labelStart);
transformer.visitVarInsn(ALOAD, 0);
+ transformer.visitVarInsn(ALOAD, 1);
+ transformer.visitVarInsn(ALOAD, 2);
+ transformer.visitVarInsn(ILOAD, 3);
+ transformer.visitLdcInsn(apiLevel.getLevel());
transformer.visitLdcInsn(reference.getFieldName());
transformer.visitLdcInsn(reference.getFieldType().getDescriptor());
- transformer.visitLdcInsn(apiLevel.getLevel());
- transformer.visitVarInsn(ALOAD, 1);
transformer.visitMethodInsn(
INVOKEVIRTUAL,
ANDROID_API_CLASS.getBinaryName(),
"visitField",
- "(Ljava/lang/String;"
- + "Ljava/lang/String;"
+ "(Ljava/util/function/BiFunction;"
+ + descriptor(ClassReference.class)
+ "I"
- + "Ljava/util/function/BiFunction;)"
+ + "I"
+ + "Ljava/lang/String;"
+ + "Ljava/lang/String;)"
+ TRAVERSAL_CONTINUATION.getDescriptor(),
false);
// Note that instead of storing the result here, we dup it on the stack.
@@ -246,6 +270,19 @@
transformer.visitInsn(POP);
});
});
+ if (!apiClass.isInterface()) {
+ apiClass.visitSuperType(
+ (superType, apiLevel) -> {
+ addMembersForParent(
+ transformer,
+ superType,
+ "visitFields",
+ apiLevel,
+ lineNumberBox.getAndIncrement());
+ });
+ }
+ // No need to visit fields on interfaces since they have to be static and should not be
+ // called on a super instance.
});
}
@@ -253,19 +290,24 @@
// placeHolderForVisitMethods();
// return TraversalContinuation.CONTINUE;
// into
- // TraversalContinuation s1 = visitMethod(
- // "method1", new String[] { "param11", ... , "param1X" }, null/return1, apiLevel1, visitor)
+ // TraversalContinuation s1 = visitMethod(visitor, holder, minApiClass, apiLevel1,
+ // "method1", new String[] { "param11", ... , "param1X" }, null/return1)
// if (s1.shouldBreak()) {
// return s1;
// }
- // TraversalContinuation s1 = visitMethod(
- // "method2", new String[] { "param21", ... , "param2X" }, null/return2, apiLevel2, visitor)
+ // TraversalContinuation s1 = visitMethod(visitor, holder, minApiClass, apiLevel2,
+ // "method2", new String[] { "param21", ... , "param2X" }, null/return2)
// if (s2.shouldBreak()) {
// return s2;
// }
// ...
+ // AndroidApiClassForClass_class_name1() super1 = new AndroidApiClassForClass_class_name1();
+ // TraversalContinuation sN = super1.visitMethods(
+ // visitor, holder, max(minApiClass, minApiForLink));
+ // ...
// return TraversalContinuation.CONTINUE;
private static MethodTransformer getVisitMethodsTransformer(ParsedApiClass apiClass) {
+ IntBox lineNumberBox = new IntBox(0);
return replaceCode(
"placeHolderForVisitMethods",
transformer -> {
@@ -273,7 +315,14 @@
(apiLevel, references) -> {
references.forEach(
reference -> {
+ Label labelStart = new Label();
+ transformer.visitLabel(labelStart);
+ transformer.visitLineNumber(lineNumberBox.getAndIncrement(), labelStart);
transformer.visitVarInsn(ALOAD, 0);
+ transformer.visitVarInsn(ALOAD, 1);
+ transformer.visitVarInsn(ALOAD, 2);
+ transformer.visitVarInsn(ILOAD, 3);
+ transformer.visitLdcInsn(apiLevel.getLevel());
transformer.visitLdcInsn(reference.getMethodName());
List<TypeReference> formalTypes = reference.getFormalTypes();
transformer.visitLdcInsn(formalTypes.size());
@@ -289,16 +338,17 @@
} else {
transformer.visitInsn(ACONST_NULL);
}
- transformer.visitLdcInsn(apiLevel.getLevel());
- transformer.visitVarInsn(ALOAD, 1);
transformer.visitMethodInsn(
INVOKEVIRTUAL,
ANDROID_API_CLASS.getBinaryName(),
"visitMethod",
- "(Ljava/lang/String;"
- + "[Ljava/lang/String;Ljava/lang/String;"
+ "(Ljava/util/function/BiFunction;"
+ + descriptor(ClassReference.class)
+ "I"
- + "Ljava/util/function/BiFunction;)"
+ + "I"
+ + "Ljava/lang/String;"
+ + "[Ljava/lang/String;"
+ + "Ljava/lang/String;)"
+ TRAVERSAL_CONTINUATION.getDescriptor(),
false);
// Note that instead of storing the result here, we dup it on the stack.
@@ -324,9 +374,72 @@
transformer.visitInsn(POP);
});
});
+ if (!apiClass.isInterface()) {
+ // Visit super types before interfaces emulating a poor man's resolutions.
+ apiClass.visitSuperType(
+ (superType, apiLevel) -> {
+ addMembersForParent(
+ transformer,
+ superType,
+ "visitMethods",
+ apiLevel,
+ lineNumberBox.getAndIncrement());
+ });
+ }
+ apiClass.visitInterface(
+ (classReference, apiLevel) -> {
+ addMembersForParent(
+ transformer,
+ classReference,
+ "visitMethods",
+ apiLevel,
+ lineNumberBox.getAndIncrement());
+ });
});
}
+ private static void addMembersForParent(
+ MethodTransformer transformer,
+ ClassReference classReference,
+ String methodName,
+ AndroidApiLevel minApiLevel,
+ int lineNumber) {
+ String binaryName = fromTemplate(classReference).getBinaryName();
+ Label labelStart = new Label();
+ transformer.visitLabel(labelStart);
+ transformer.visitLineNumber(lineNumber, labelStart);
+ transformer.visitTypeInsn(NEW, binaryName);
+ transformer.visitInsn(DUP);
+ transformer.visitMethodInsn(INVOKESPECIAL, binaryName, "<init>", "()V", false);
+ transformer.visitVarInsn(ALOAD, 1);
+ transformer.visitVarInsn(ALOAD, 2);
+ // Compute the max api level compared to what is passed in.
+ transformer.visitLdcInsn(minApiLevel.getLevel());
+ transformer.visitVarInsn(ILOAD, 3);
+ transformer.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "max", "(II)I", false);
+ transformer.visitMethodInsn(
+ INVOKEVIRTUAL,
+ binaryName,
+ methodName,
+ "(Ljava/util/function/BiFunction;"
+ + descriptor(ClassReference.class)
+ + "I)"
+ + TRAVERSAL_CONTINUATION.getDescriptor(),
+ false);
+ // Note that instead of storing the result here, we dup it on the stack.
+ transformer.visitInsn(DUP);
+ transformer.visitMethodInsn(
+ INVOKEVIRTUAL, TRAVERSAL_CONTINUATION.getBinaryName(), "shouldBreak", "()Z", false);
+ Label label = new Label();
+ transformer.visitJumpInsn(IFEQ, label);
+ transformer.visitInsn(ARETURN);
+ transformer.visitLabel(label);
+ transformer.visitFrame(
+ F_SAME1, 0, new Object[] {}, 1, new Object[] {TRAVERSAL_CONTINUATION.getBinaryName()});
+ // The pop here is needed to remove the dupped value in the case we do not return.
+ transformer.visitInsn(POP);
+ }
+
// The transformer below changes AndroidApiDatabasePackageTemplate.buildClass from:
// placeHolder();
// return null;
@@ -359,8 +472,7 @@
false);
Label label = new Label();
transformer.visitJumpInsn(IFEQ, label);
- String binaryName =
- DescriptorUtils.getBinaryNameFromDescriptor(getApiClassDescriptor(apiClass));
+ String binaryName = getApiClassReference(apiClass).getBinaryName();
transformer.visitTypeInsn(NEW, binaryName);
transformer.visitInsn(DUP);
transformer.visitMethodInsn(INVOKESPECIAL, binaryName, "<init>", "()V", false);
diff --git a/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseBuilderGeneratorTest.java b/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseBuilderGeneratorTest.java
index 2ad1e72..8b3571a 100644
--- a/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseBuilderGeneratorTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseBuilderGeneratorTest.java
@@ -19,6 +19,7 @@
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.apimodel.AndroidApiVersionsXmlParser.ParsedApiClass;
import com.android.tools.r8.cf.bootstrap.BootstrapCurrentEqualityTest;
+import com.android.tools.r8.references.ClassReference;
import com.android.tools.r8.references.Reference;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.BooleanBox;
@@ -36,7 +37,9 @@
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.function.BiFunction;
import org.junit.Test;
@@ -121,6 +124,8 @@
* were introduced. Running main will generate a new jar and run tests on it to ensure it is
* compatible with R8 sources and works as expected.
*
+ * <p>The generated jar depends on r8NoManifestWithoutDeps.
+ *
* <p>If the generated jar passes tests it will be moved to third_party/android_jar/api-database/
* and override the current file in there.
*/
@@ -141,8 +146,12 @@
AndroidApiDatabaseBuilderGeneratorTest::testNoPlaceHolder);
tests.forEach(
test -> {
- if (!test.apply(generated, apiClasses)) {
- throw new RuntimeException("Generated jar did not pass tests");
+ try {
+ if (!test.apply(generated, apiClasses)) {
+ throw new RuntimeException("Generated jar did not pass tests");
+ }
+ } catch (Exception e) {
+ throw new RuntimeException("Generated jar did not pass tests", e);
}
});
}
@@ -232,44 +241,116 @@
}
private static String getExpected(List<ParsedApiClass> parsedApiClasses, boolean abort) {
+ Map<ClassReference, ParsedApiClass> parsedApiClassMap = new HashMap<>(parsedApiClasses.size());
+ parsedApiClasses.forEach(
+ parsedClass -> parsedApiClassMap.put(parsedClass.getClassReference(), parsedClass));
List<String> expected = new ArrayList<>();
parsedApiClasses.forEach(
apiClass -> {
- expected.add(apiClass.getClassReference().getDescriptor());
+ expected.add("CLASS: " + apiClass.getClassReference().getDescriptor());
expected.add(apiClass.getApiLevel().getName());
expected.add(apiClass.getTotalMemberCount() + "");
- BooleanBox added = new BooleanBox(false);
- apiClass.visitFieldReferences(
- (apiLevel, fieldReferences) -> {
- fieldReferences.forEach(
- fieldReference -> {
- if (added.isTrue() && abort) {
- return;
- }
- added.set();
- expected.add(fieldReference.getFieldType().getDescriptor());
- expected.add(fieldReference.getFieldName());
- expected.add(apiLevel.getName());
- });
- });
- added.set(false);
- apiClass.visitMethodReferences(
- (apiLevel, methodReferences) -> {
- methodReferences.forEach(
- methodReference -> {
- if (added.isTrue() && abort) {
- return;
- }
- added.set();
- expected.add(methodReference.getMethodDescriptor());
- expected.add(methodReference.getMethodName());
- expected.add(apiLevel.getName());
- });
- });
+ visitApiClass(expected, parsedApiClassMap, apiClass, apiClass.getApiLevel(), true, abort);
+ visitApiClass(
+ expected, parsedApiClassMap, apiClass, apiClass.getApiLevel(), false, abort);
});
return StringUtils.lines(expected);
}
+ private static boolean visitApiClass(
+ List<String> expected,
+ Map<ClassReference, ParsedApiClass> parsedApiClassMap,
+ ParsedApiClass apiClass,
+ AndroidApiLevel apiLevel,
+ boolean visitFields,
+ boolean abort) {
+ BooleanBox added = new BooleanBox(false);
+ if (visitFields) {
+ added.set(visitFields(expected, apiClass, apiLevel, abort));
+ } else {
+ added.set(visitMethods(expected, apiClass, apiLevel, abort));
+ }
+ if (added.isTrue() && abort) {
+ return true;
+ }
+ // Go through super type methods if not interface.
+ if (!apiClass.isInterface()) {
+ apiClass.visitSuperType(
+ (classReference, linkApiLevel) -> {
+ if (added.isTrue() && abort) {
+ return;
+ }
+ ParsedApiClass superApiClass = parsedApiClassMap.get(classReference);
+ assert superApiClass != null;
+ added.set(
+ visitApiClass(
+ expected,
+ parsedApiClassMap,
+ superApiClass,
+ linkApiLevel.max(apiLevel),
+ visitFields,
+ abort));
+ });
+ }
+ if (!visitFields) {
+ apiClass.visitInterface(
+ (classReference, linkApiLevel) -> {
+ if (added.isTrue() && abort) {
+ return;
+ }
+ ParsedApiClass ifaceApiClass = parsedApiClassMap.get(classReference);
+ assert ifaceApiClass != null;
+ added.set(
+ visitApiClass(
+ expected,
+ parsedApiClassMap,
+ ifaceApiClass,
+ linkApiLevel.max(apiLevel),
+ visitFields,
+ abort));
+ });
+ }
+ return added.get();
+ }
+
+ private static boolean visitFields(
+ List<String> expected, ParsedApiClass apiClass, AndroidApiLevel minApiLevel, boolean abort) {
+ BooleanBox added = new BooleanBox(false);
+ apiClass.visitFieldReferences(
+ (apiLevel, fieldReferences) -> {
+ fieldReferences.forEach(
+ fieldReference -> {
+ if (added.isTrue() && abort) {
+ return;
+ }
+ added.set();
+ expected.add(fieldReference.getFieldType().getDescriptor());
+ expected.add(fieldReference.getFieldName());
+ expected.add(apiLevel.max(minApiLevel).getName());
+ });
+ });
+ return added.get();
+ }
+
+ private static boolean visitMethods(
+ List<String> expected, ParsedApiClass apiClass, AndroidApiLevel minApiLevel, boolean abort) {
+ BooleanBox added = new BooleanBox(false);
+ apiClass.visitMethodReferences(
+ (apiLevel, methodReferences) -> {
+ methodReferences.forEach(
+ methodReference -> {
+ if (added.isTrue() && abort) {
+ return;
+ }
+ added.set();
+ expected.add(methodReference.getMethodDescriptor());
+ expected.add(methodReference.getMethodName());
+ expected.add(apiLevel.max(minApiLevel).getName());
+ });
+ });
+ return added.get();
+ }
+
public static class TestGeneratedMainVisitClasses {
public static void main(String[] args) {
@@ -286,7 +367,7 @@
AndroidApiDatabaseBuilderTemplate.buildClass(
Reference.classFromDescriptor(descriptor));
if (apiClass != null) {
- System.out.println(descriptor);
+ System.out.println("CLASS: " + descriptor);
System.out.println(apiClass.getApiLevel().getName());
System.out.println(apiClass.getMemberCount());
apiClass.visitFields(
@@ -317,7 +398,7 @@
AndroidApiDatabaseBuilderTemplate.buildClass(
Reference.classFromDescriptor(descriptor));
if (apiClass != null) {
- System.out.println(descriptor);
+ System.out.println("CLASS: " + descriptor);
System.out.println(apiClass.getApiLevel().getName());
System.out.println(apiClass.getMemberCount());
apiClass.visitFields(
diff --git a/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseClassTemplate.java b/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseClassTemplate.java
index faefd62..1a8b7e3 100644
--- a/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseClassTemplate.java
+++ b/src/test/java/com/android/tools/r8/apimodel/AndroidApiDatabaseClassTemplate.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.apimodel;
import com.android.tools.r8.androidapi.AndroidApiClass;
+import com.android.tools.r8.references.ClassReference;
import com.android.tools.r8.references.FieldReference;
import com.android.tools.r8.references.MethodReference;
import com.android.tools.r8.references.Reference;
@@ -34,7 +35,9 @@
@Override
public TraversalContinuation visitFields(
- BiFunction<FieldReference, AndroidApiLevel, TraversalContinuation> visitor) {
+ BiFunction<FieldReference, AndroidApiLevel, TraversalContinuation> visitor,
+ ClassReference holder,
+ int minApi) {
// Code added dynamically in AndroidApiDatabaseBuilderGenerator.
placeHolderForVisitFields();
return TraversalContinuation.CONTINUE;
@@ -42,7 +45,9 @@
@Override
public TraversalContinuation visitMethods(
- BiFunction<MethodReference, AndroidApiLevel, TraversalContinuation> visitor) {
+ BiFunction<MethodReference, AndroidApiLevel, TraversalContinuation> visitor,
+ ClassReference holder,
+ int minApi) {
// Code added dynamically in AndroidApiDatabaseBuilderGenerator.
placeHolderForVisitMethods();
return TraversalContinuation.CONTINUE;
diff --git a/src/test/java/com/android/tools/r8/apimodel/AndroidApiVersionsXmlParser.java b/src/test/java/com/android/tools/r8/apimodel/AndroidApiVersionsXmlParser.java
index 4002bf9..24b9613 100644
--- a/src/test/java/com/android/tools/r8/apimodel/AndroidApiVersionsXmlParser.java
+++ b/src/test/java/com/android/tools/r8/apimodel/AndroidApiVersionsXmlParser.java
@@ -19,6 +19,7 @@
import java.io.File;
import java.util.ArrayList;
import java.util.Comparator;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
@@ -40,8 +41,9 @@
this.maxApiLevel = maxApiLevel;
}
- private ParsedApiClass register(ClassReference reference, AndroidApiLevel apiLevel) {
- ParsedApiClass parsedApiClass = new ParsedApiClass(reference, apiLevel);
+ private ParsedApiClass register(
+ ClassReference reference, AndroidApiLevel apiLevel, boolean isInterface) {
+ ParsedApiClass parsedApiClass = new ParsedApiClass(reference, apiLevel, isInterface);
classes.add(parsedApiClass);
return parsedApiClass;
}
@@ -62,11 +64,19 @@
continue;
}
ClassReference originalReference = clazz.getOriginalReference();
- ParsedApiClass parsedApiClass = register(originalReference, apiLevel);
+ ParsedApiClass parsedApiClass = register(originalReference, apiLevel, clazz.isInterface());
NodeList members = node.getChildNodes();
for (int j = 0; j < members.getLength(); j++) {
Node memberNode = members.item(j);
- if (isMethod(memberNode)) {
+ if (isExtends(memberNode)) {
+ parsedApiClass.registerSuperType(
+ Reference.classFromBinaryName(getName(memberNode)),
+ hasSince(memberNode) ? getSince(memberNode) : apiLevel);
+ } else if (isImplements(memberNode)) {
+ parsedApiClass.registerInterface(
+ Reference.classFromBinaryName(getName(memberNode)),
+ hasSince(memberNode) ? getSince(memberNode) : apiLevel);
+ } else if (isMethod(memberNode)) {
// TODO(b/190326408): Check for existence.
parsedApiClass.register(
getMethodReference(originalReference, memberNode),
@@ -109,16 +119,29 @@
return node.getNodeName().equals("field");
}
- private AndroidApiLevel getMaxAndroidApiLevelFromNode(Node node, AndroidApiLevel defaultValue) {
- if (node == null) {
- return defaultValue;
- }
+ private boolean isExtends(Node node) {
+ return node.getNodeName().equals("extends");
+ }
+
+ private boolean isImplements(Node node) {
+ return node.getNodeName().equals("implements");
+ }
+
+ private boolean hasSince(Node node) {
+ return node.getAttributes().getNamedItem("since") != null;
+ }
+
+ private AndroidApiLevel getSince(Node node) {
+ assert hasSince(node);
Node since = node.getAttributes().getNamedItem("since");
- if (since == null) {
+ return AndroidApiLevel.getAndroidApiLevel(Integer.parseInt(since.getNodeValue()));
+ }
+
+ private AndroidApiLevel getMaxAndroidApiLevelFromNode(Node node, AndroidApiLevel defaultValue) {
+ if (node == null || !hasSince(node)) {
return defaultValue;
}
- return defaultValue.max(
- AndroidApiLevel.getAndroidApiLevel(Integer.parseInt(since.getNodeValue())));
+ return defaultValue.max(getSince(node));
}
public static List<ParsedApiClass> getParsedApiClasses(
@@ -132,12 +155,17 @@
private final ClassReference classReference;
private final AndroidApiLevel apiLevel;
+ private final boolean isInterface;
+ private final Map<ClassReference, AndroidApiLevel> superTypes = new LinkedHashMap<>();
+ private final Map<ClassReference, AndroidApiLevel> interfaces = new LinkedHashMap<>();
private final TreeMap<AndroidApiLevel, List<FieldReference>> fieldReferences = new TreeMap<>();
private final Map<AndroidApiLevel, List<MethodReference>> methodReferences = new TreeMap<>();
- private ParsedApiClass(ClassReference classReference, AndroidApiLevel apiLevel) {
+ private ParsedApiClass(
+ ClassReference classReference, AndroidApiLevel apiLevel, boolean isInterface) {
this.classReference = classReference;
this.apiLevel = apiLevel;
+ this.isInterface = isInterface;
}
public ClassReference getClassReference() {
@@ -171,6 +199,16 @@
methodReferences.computeIfAbsent(apiLevel, ignoreArgument(ArrayList::new)).add(reference);
}
+ private void registerSuperType(ClassReference superType, AndroidApiLevel apiLevel) {
+ AndroidApiLevel existing = superTypes.put(superType, apiLevel);
+ assert existing == null;
+ }
+
+ private void registerInterface(ClassReference iface, AndroidApiLevel apiLevel) {
+ AndroidApiLevel existing = interfaces.put(iface, apiLevel);
+ assert existing == null;
+ }
+
public void visitFieldReferences(BiConsumer<AndroidApiLevel, List<FieldReference>> consumer) {
fieldReferences.forEach(
(apiLevel, references) -> {
@@ -186,5 +224,17 @@
consumer.accept(apiLevel, references);
});
}
+
+ public void visitSuperType(BiConsumer<ClassReference, AndroidApiLevel> consumer) {
+ superTypes.forEach(consumer);
+ }
+
+ public void visitInterface(BiConsumer<ClassReference, AndroidApiLevel> consumer) {
+ interfaces.forEach(consumer);
+ }
+
+ public boolean isInterface() {
+ return isInterface;
+ }
}
}
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelFieldSuperTypeTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelFieldSuperTypeTest.java
index 32b111e..5b47c79 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelFieldSuperTypeTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelFieldSuperTypeTest.java
@@ -5,10 +5,8 @@
package com.android.tools.r8.apimodel;
import static com.android.tools.r8.apimodel.ApiModelingTestHelper.addTracedApiReferenceLevelCallBack;
-import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertEquals;
-import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
@@ -35,7 +33,7 @@
this.parameters = parameters;
}
- @Test(expected = CompilationFailedException.class)
+ @Test
public void testR8() throws Exception {
Method main = Main.class.getDeclaredMethod("main", String[].class);
testForR8(parameters.getBackend())
@@ -55,16 +53,14 @@
addTracedApiReferenceLevelCallBack(
(method, apiLevel) -> {
if (Reference.methodFromMethod(main).equals(method)) {
- // TODO(b/193414761): Should not be UNKNOWN.
- assertEquals(AndroidApiLevel.UNKNOWN, apiLevel);
+ assertEquals(
+ parameters.isCfRuntime()
+ ? AndroidApiLevel.E
+ : parameters.getApiLevel().max(AndroidApiLevel.E),
+ apiLevel);
}
}))
- .compileWithExpectedDiagnostics(
- diagnostics -> {
- // TODO(b/193414761): We should analyze all members.
- diagnostics.assertErrorMessageThatMatches(
- containsString("Every member should have been analyzed"));
- });
+ .compile();
}
/* Only here to get the test to compile */
@@ -77,7 +73,7 @@
public static void main(String[] args) {
// START_CONTINUATION_MASK is inherited from android/app/Service which was introduced at
- // AndroidApiLevel.B.
+ // AndroidApiLevel.E.
System.out.println(
new /* android.accessibilityservice */ AccessibilityService().START_CONTINUATION_MASK);
}
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelVirtualDispatchInterfaceTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelVirtualDispatchInterfaceTest.java
index 22c0702..892f9d8 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelVirtualDispatchInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelVirtualDispatchInterfaceTest.java
@@ -5,15 +5,13 @@
package com.android.tools.r8.apimodel;
import static com.android.tools.r8.apimodel.ApiModelingTestHelper.addTracedApiReferenceLevelCallBack;
-import static com.android.tools.r8.utils.AndroidApiLevel.UNKNOWN;
-import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertEquals;
-import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.AndroidApiLevel;
import java.lang.reflect.Method;
import java.util.ArrayList;
import org.junit.Test;
@@ -35,7 +33,7 @@
this.parameters = parameters;
}
- @Test(expected = CompilationFailedException.class)
+ @Test
public void testR8() throws Exception {
Method main = Main.class.getDeclaredMethod("main", String[].class);
testForR8(parameters.getBackend())
@@ -48,16 +46,14 @@
addTracedApiReferenceLevelCallBack(
(method, apiLevel) -> {
if (Reference.methodFromMethod(main).equals(method)) {
- // TODO(b/193414761): Should not be UNKNOWN.
- assertEquals(UNKNOWN, apiLevel);
+ assertEquals(
+ parameters.isCfRuntime()
+ ? AndroidApiLevel.B
+ : parameters.getApiLevel().max(AndroidApiLevel.B),
+ apiLevel);
}
}))
- .compileWithExpectedDiagnostics(
- diagnostics -> {
- // TODO(b/193414761): We should analyze all members.
- diagnostics.assertErrorMessageThatMatches(
- containsString("Every member should have been analyzed"));
- });
+ .compile();
}
public static class Main {
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelVirtualDispatchLinkInterfaceTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelVirtualDispatchLinkInterfaceTest.java
index 3f8bed1..04337ec 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelVirtualDispatchLinkInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelVirtualDispatchLinkInterfaceTest.java
@@ -6,11 +6,9 @@
import static com.android.tools.r8.apimodel.ApiModelingTestHelper.addTracedApiReferenceLevelCallBack;
import static com.android.tools.r8.utils.AndroidApiLevel.LATEST;
-import static com.android.tools.r8.utils.AndroidApiLevel.UNKNOWN;
-import static org.hamcrest.CoreMatchers.containsString;
+import static com.android.tools.r8.utils.AndroidApiLevel.R;
import static org.junit.Assert.assertEquals;
-import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
@@ -36,7 +34,7 @@
this.parameters = parameters;
}
- @Test(expected = CompilationFailedException.class)
+ @Test
public void testR8() throws Exception {
// Landroid/view/accessibility/AccessibilityNodeInfo$AccessibilityAction; is introduced at api
// level 21 and on api level 30 it implements android.os.Parcelable.
@@ -58,16 +56,10 @@
addTracedApiReferenceLevelCallBack(
(method, apiLevel) -> {
if (Reference.methodFromMethod(main).equals(method)) {
- // TODO(b/193414761): Should be 30.
- assertEquals(UNKNOWN, apiLevel);
+ assertEquals(R, apiLevel);
}
}))
- .compileWithExpectedDiagnostics(
- diagnostics -> {
- // TODO(b/193414761): We should analyze all members.
- diagnostics.assertErrorMessageThatMatches(
- containsString("Every member should have been analyzed"));
- });
+ .compile();
}
public static class AccessibilityNodeInfo$AccessibilityAction {
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelVirtualDispatchSuperTypeTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelVirtualDispatchSuperTypeTest.java
index 2c5868e..c79c69b 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelVirtualDispatchSuperTypeTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelVirtualDispatchSuperTypeTest.java
@@ -5,10 +5,8 @@
package com.android.tools.r8.apimodel;
import static com.android.tools.r8.apimodel.ApiModelingTestHelper.addTracedApiReferenceLevelCallBack;
-import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertEquals;
-import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
@@ -35,7 +33,7 @@
this.parameters = parameters;
}
- @Test(expected = CompilationFailedException.class)
+ @Test
public void testR8() throws Exception {
Method main = Main.class.getDeclaredMethod("main", String[].class);
testForR8(parameters.getBackend())
@@ -55,16 +53,17 @@
addTracedApiReferenceLevelCallBack(
(method, apiLevel) -> {
if (Reference.methodFromMethod(main).equals(method)) {
- // TODO(b/193414761): Should not be UNKNOWN.
- assertEquals(AndroidApiLevel.UNKNOWN, apiLevel);
+ // android.app.Service.stopSelf() was introduced at AndroidApiLevel.B but
+ // android/accessibilityservice/AccessibilityService was introduced at D
+ // so the minimum api level is D.
+ assertEquals(
+ parameters.isCfRuntime()
+ ? AndroidApiLevel.D
+ : parameters.getApiLevel().max(AndroidApiLevel.D),
+ apiLevel);
}
}))
- .compileWithExpectedDiagnostics(
- diagnostics -> {
- // TODO(b/193414761): We should analyze all members.
- diagnostics.assertErrorMessageThatMatches(
- containsString("Every member should have been analyzed"));
- });
+ .compile();
}
/* Only here to get the test to compile */
@@ -76,7 +75,8 @@
public static class Main {
public static void main(String[] args) {
- // stopSelf() is inherited from android/app/Service which was introduced at AndroidApiLevel.B.
+ // AccessibilityService.stopSelf() is inherited from android/app/Service which was introduced
+ // at AndroidApiLevel.B.
new /* android.accessibilityservice */ AccessibilityService().stopSelf();
}
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
index 402d6b3..4ea9a82 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
@@ -120,6 +120,11 @@
}
@Override
+ public boolean isInterface() {
+ throw new Unreachable("Cannot determine if an absent class is an interface");
+ }
+
+ @Override
public String getOriginalName() {
return reference.getTypeName();
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
index 8beec8a..f552a25 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
@@ -171,6 +171,8 @@
@Override
public abstract ClassAccessFlags getAccessFlags();
+ public abstract boolean isInterface();
+
public abstract boolean isAbstract();
public abstract boolean isAnnotation();
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
index 1114bce..318f0d9 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
@@ -294,6 +294,11 @@
}
@Override
+ public boolean isInterface() {
+ return dexClass.isInterface();
+ }
+
+ @Override
public boolean isImplementing(ClassSubject subject) {
assertTrue(subject.isPresent());
for (DexType itf : getDexProgramClass().interfaces) {
diff --git a/third_party/android_jar/api-database.tar.gz.sha1 b/third_party/android_jar/api-database.tar.gz.sha1
index 97d5f12..2bbe555 100644
--- a/third_party/android_jar/api-database.tar.gz.sha1
+++ b/third_party/android_jar/api-database.tar.gz.sha1
@@ -1 +1 @@
-a3e0351d71082eb74073576e11c0632191fd8530
\ No newline at end of file
+829d7f32a482c16a2008a8878c33c58637c21a56
\ No newline at end of file