blob: 40809f264f9956e87e3bde1377ed11e7db806fe8 [file]
// Copyright (c) 2026, 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 com.android.tools.r8.ApiDatabaseGeneratorException;
import com.android.tools.r8.references.ClassReference;
import com.android.tools.r8.utils.internal.collections.DisjointSets;
import com.android.tools.r8.utils.internal.collections.Pair;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class ParsedApiClassVerifier {
public static void verify(Collection<ParsedApiClass> classes)
throws ApiDatabaseGeneratorException {
Map<ClassReference, ParsedApiClass> classMap = new HashMap<>();
for (ParsedApiClass clazz : classes) {
ClassReference classReference = clazz.getClassReference();
if (classMap.containsKey(classReference)) {
throw new ApiDatabaseGeneratorException("Duplicate API classes for: " + classReference);
}
classMap.put(classReference, clazz);
}
verify(classMap);
}
private static void verify(Map<ClassReference, ParsedApiClass> classMap)
throws ApiDatabaseGeneratorException {
verifyClassHierarchy(classMap);
verifyApiRanges(classMap);
verifyClassOrInterface(classMap);
}
private static void verifyClassHierarchy(Map<ClassReference, ParsedApiClass> classMap)
throws ApiDatabaseGeneratorException {
for (ParsedApiClass clazz : classMap.values()) {
clazz.forEachSupertypeThrowing(
(supertype, range) -> {
if (!classMap.containsKey(supertype)) {
throw new ApiDatabaseGeneratorException(
"Missing supertype " + supertype + " for " + clazz.getClassReference());
}
});
clazz.forEachInterfaceThrowing(
(iface, range) -> {
if (!classMap.containsKey(iface)) {
throw new ApiDatabaseGeneratorException(
"Missing interface " + iface + " for " + clazz.getClassReference());
}
});
}
}
private static void verifyApiRanges(Map<ClassReference, ParsedApiClass> classMap)
throws ApiDatabaseGeneratorException {
for (ParsedApiClass clazz : classMap.values()) {
clazz.forEachSupertypeThrowing(
(supertype, relationRange) -> {
if (!relationRange.isWithin(clazz.getRange())) {
throw new ApiDatabaseGeneratorException(
"Supertype relation range "
+ relationRange
+ " for "
+ supertype
+ " is not within class range "
+ clazz.getRange()
+ " of "
+ clazz.getClassReference());
}
ParsedApiClass superclass = classMap.get(supertype);
if (!relationRange.isWithin(superclass.getRange())) {
throw new ApiDatabaseGeneratorException(
"Supertype relation range "
+ relationRange
+ " for "
+ supertype
+ " is not within superclass range "
+ superclass.getRange()
+ " of "
+ supertype);
}
});
clazz.forEachInterfaceThrowing(
(iface, relationRange) -> {
if (!relationRange.isWithin(clazz.getRange())) {
throw new ApiDatabaseGeneratorException(
"Interface relation range "
+ relationRange
+ " for "
+ iface
+ " is not within class range "
+ clazz.getRange()
+ " of "
+ clazz.getClassReference());
}
ParsedApiClass interfaceClass = classMap.get(iface);
if (!relationRange.isWithin(interfaceClass.getRange())) {
throw new ApiDatabaseGeneratorException(
"Interface relation range "
+ relationRange
+ " for "
+ iface
+ " is not within interface range "
+ interfaceClass.getRange()
+ " of "
+ iface);
}
});
clazz.forEachMethodThrowing(
(method, methodRange) -> {
if (!methodRange.isWithin(clazz.getRange())) {
throw new ApiDatabaseGeneratorException(
"Method range "
+ methodRange
+ " for "
+ method
+ " is not within class range "
+ clazz.getRange()
+ " of "
+ clazz.getClassReference());
}
});
clazz.forEachFieldThrowing(
(field, fieldRange) -> {
if (!fieldRange.isWithin(clazz.getRange())) {
throw new ApiDatabaseGeneratorException(
"Field range "
+ fieldRange
+ " for "
+ field
+ " is not within class range "
+ clazz.getRange()
+ " of "
+ clazz.getClassReference());
}
});
}
}
private static void verifyClassOrInterface(Map<ClassReference, ParsedApiClass> classMap)
throws ApiDatabaseGeneratorException {
ClassInterfaceUnification unifier = new ClassInterfaceUnification();
var classes = classMap.values();
for (ParsedApiClass clazz : classes) {
if (clazz.hasConstructor()) {
unifier.markAsClass(clazz.getClassReference());
}
}
for (ParsedApiClass clazz : classes) {
clazz.forEachInterfaceThrowing((iface, range) -> unifier.markAsInterface(iface));
}
for (ParsedApiClass clazz : classes) {
clazz.forEachSupertypeThrowing(
(supertype, range) -> {
if (supertype.getDescriptor().equals("Ljava/lang/Object;")) {
// Both kinds can extend Object.
return;
}
unifier.unify(clazz.getClassReference(), supertype);
});
}
for (ParsedApiClass clazz : classes) {
List<Pair<ClassReference, ApiRange>> supertypes = new ArrayList<>();
clazz.forEachSupertype(
(supertype, range) -> {
if (!supertype.getDescriptor().equals("Ljava/lang/Object;")) {
supertypes.add(Pair.create(supertype, range));
}
});
for (int i = 0; i < supertypes.size(); i++) {
for (int j = i + 1; j < supertypes.size(); j++) {
if (supertypes.get(i).getSecond().isOverlappingWith(supertypes.get(j).getSecond())) {
unifier.markAsInterface(clazz.getClassReference());
}
}
}
}
}
private static class ClassInterfaceUnification {
private final DisjointSets<Object> unifier = new DisjointSets<>();
private enum ClassKind {
UNKNOWN,
CLASS,
INTERFACE
}
ClassInterfaceUnification() {
unifier.makeSet(ClassKind.CLASS);
unifier.makeSet(ClassKind.INTERFACE);
}
public void unify(ClassReference ref1, ClassReference ref2)
throws ApiDatabaseGeneratorException {
unionInternal(ref1, ref2);
}
public void markAsClass(ClassReference ref) throws ApiDatabaseGeneratorException {
unionInternal(ref, ClassKind.CLASS);
}
public void markAsInterface(ClassReference ref) throws ApiDatabaseGeneratorException {
unionInternal(ref, ClassKind.INTERFACE);
}
private void unionInternal(Object ref1, Object ref2) throws ApiDatabaseGeneratorException {
Object root1 = unifier.findOrMakeSet(ref1);
Object root2 = unifier.findOrMakeSet(ref2);
if (root1 != root2) {
unifier.union(root1, root2);
Object classRoot = unifier.findSet(ClassKind.CLASS);
assert classRoot != null;
Object interfaceRoot = unifier.findSet(ClassKind.INTERFACE);
assert interfaceRoot != null;
if (classRoot == interfaceRoot) {
throw new ApiDatabaseGeneratorException(
"Inconsistent class/interface usage involving " + ref1);
}
}
}
}
}