8304425: ClassHierarchyResolver from Reflection

Reviewed-by: asotona
This commit is contained in:
Chen Liang 2023-06-08 07:29:56 +00:00 committed by Adam Sotona
parent 79a4ac791c
commit ac3ce2bf75
10 changed files with 373 additions and 98 deletions

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -26,12 +26,19 @@ package jdk.internal.classfile;
import java.io.InputStream; import java.io.InputStream;
import java.lang.constant.ClassDesc; import java.lang.constant.ClassDesc;
import java.lang.invoke.MethodHandles;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.function.Function; import java.util.function.Function;
import jdk.internal.classfile.impl.Util; import java.util.function.Supplier;
import jdk.internal.classfile.impl.ClassHierarchyImpl; import jdk.internal.classfile.impl.ClassHierarchyImpl;
import jdk.internal.classfile.impl.ClassHierarchyImpl.ClassLoadingClassHierarchyResolver;
import jdk.internal.classfile.impl.ClassHierarchyImpl.StaticClassHierarchyResolver;
import jdk.internal.classfile.impl.Util;
import static java.lang.constant.ConstantDescs.CD_Object;
/** /**
* Provides class hierarchy information for generating correct stack maps * Provides class hierarchy information for generating correct stack maps
@ -41,18 +48,14 @@ import jdk.internal.classfile.impl.ClassHierarchyImpl;
public interface ClassHierarchyResolver { public interface ClassHierarchyResolver {
/** /**
* Default singleton instance of {@linkplain ClassHierarchyResolver} * Returns a default instance of {@linkplain ClassHierarchyResolver}
* using {@link ClassLoader#getSystemResourceAsStream(String)} * that reads from system class loader with
* as the {@code ClassStreamResolver} * {@link ClassLoader#getSystemResourceAsStream(String)} and falls
* back to reflection if a class is not found.
*/ */
ClassHierarchyResolver DEFAULT_CLASS_HIERARCHY_RESOLVER static ClassHierarchyResolver defaultResolver() {
= new ClassHierarchyImpl.CachedClassHierarchyResolver( return ClassHierarchyImpl.DEFAULT_RESOLVER;
new Function<ClassDesc, InputStream>() {
@Override
public InputStream apply(ClassDesc classDesc) {
return ClassLoader.getSystemResourceAsStream(Util.toInternalName(classDesc) + ".class");
} }
});
/** /**
* {@return the {@link ClassHierarchyInfo} for a given class name, or null * {@return the {@link ClassHierarchyInfo} for a given class name, or null
@ -61,6 +64,31 @@ public interface ClassHierarchyResolver {
*/ */
ClassHierarchyInfo getClassInfo(ClassDesc classDesc); ClassHierarchyInfo getClassInfo(ClassDesc classDesc);
/**
* Information about a resolved class.
*/
sealed interface ClassHierarchyInfo permits ClassHierarchyImpl.ClassHierarchyInfoImpl {
/**
* Indicates that a class is a declared class, with the specific given super class.
*
* @param superClass descriptor of the super class, may be {@code null}
* @return the info indicating the super class
*/
static ClassHierarchyInfo ofClass(ClassDesc superClass) {
return new ClassHierarchyImpl.ClassHierarchyInfoImpl(superClass, false);
}
/**
* Indicates that a class is an interface.
*
* @return the info indicating an interface
*/
static ClassHierarchyInfo ofInterface() {
return new ClassHierarchyImpl.ClassHierarchyInfoImpl(CD_Object, true);
}
}
/** /**
* Chains this {@linkplain ClassHierarchyResolver} with another to be * Chains this {@linkplain ClassHierarchyResolver} with another to be
* consulted if this resolver does not know about the specified class. * consulted if this resolver does not know about the specified class.
@ -74,30 +102,73 @@ public interface ClassHierarchyResolver {
public ClassHierarchyInfo getClassInfo(ClassDesc classDesc) { public ClassHierarchyInfo getClassInfo(ClassDesc classDesc) {
var chi = ClassHierarchyResolver.this.getClassInfo(classDesc); var chi = ClassHierarchyResolver.this.getClassInfo(classDesc);
if (chi == null) if (chi == null)
chi = other.getClassInfo(classDesc); return other.getClassInfo(classDesc);
return chi; return chi;
} }
}; };
} }
/** /**
* Information about a resolved class. * Returns a ClassHierarchyResolver that caches class hierarchy information from this
* @param thisClass descriptor of this class * resolver. The returned resolver will not update if delegate resolver returns differently.
* @param isInterface whether this class is an interface * The thread safety of the returned resolver depends on the thread safety of the map
* @param superClass descriptor of the superclass (not relevant for interfaces) * returned by the {@code cacheFactory}.
*
* @param cacheFactory the factory for the cache
* @return the ClassHierarchyResolver with caching
*/ */
public record ClassHierarchyInfo(ClassDesc thisClass, boolean isInterface, ClassDesc superClass) { default ClassHierarchyResolver cached(Supplier<Map<ClassDesc, ClassHierarchyInfo>> cacheFactory) {
return new ClassHierarchyImpl.CachedClassHierarchyResolver(this, cacheFactory.get());
}
/**
* Returns a ClassHierarchyResolver that caches class hierarchy information from this
* resolver. The returned resolver will not update if delegate resolver returns differently.
* The returned resolver is not thread-safe.
* {@snippet file="PackageSnippets.java" region="lookup-class-hierarchy-resolver"}
*
* @return the ClassHierarchyResolver
*/
default ClassHierarchyResolver cached() {
record Factory() implements Supplier<Map<ClassDesc, ClassHierarchyInfo>> {
// uses a record as we cannot declare a local class static
static final Factory INSTANCE = new Factory();
@Override
public Map<ClassDesc, ClassHierarchyInfo> get() {
return new HashMap<>();
}
}
return cached(Factory.INSTANCE);
} }
/** /**
* Returns a {@linkplain ClassHierarchyResolver} that extracts class hierarchy * Returns a {@linkplain ClassHierarchyResolver} that extracts class hierarchy
* information from classfiles located by a mapping function * information from classfiles located by a mapping function. The mapping function
* should return null if it cannot provide a mapping for a classfile. Any IOException
* from the provided input stream is rethrown as an UncheckedIOException.
* *
* @param classStreamResolver maps class descriptors to classfile input streams * @param classStreamResolver maps class descriptors to classfile input streams
* @return the {@linkplain ClassHierarchyResolver} * @return the {@linkplain ClassHierarchyResolver}
*/ */
public static ClassHierarchyResolver ofCached(Function<ClassDesc, InputStream> classStreamResolver) { static ClassHierarchyResolver ofResourceParsing(Function<ClassDesc, InputStream> classStreamResolver) {
return new ClassHierarchyImpl.CachedClassHierarchyResolver(classStreamResolver); return new ClassHierarchyImpl.ResourceParsingClassHierarchyResolver(classStreamResolver);
}
/**
* Returns a {@linkplain ClassHierarchyResolver} that extracts class hierarchy
* information from classfiles located by a class loader.
*
* @param loader the class loader, to find class files
* @return the {@linkplain ClassHierarchyResolver}
*/
static ClassHierarchyResolver ofResourceParsing(ClassLoader loader) {
return ofResourceParsing(new Function<>() {
@Override
public InputStream apply(ClassDesc classDesc) {
return loader.getResourceAsStream(Util.toInternalName(classDesc) + ".class");
}
});
} }
/** /**
@ -108,8 +179,52 @@ public interface ClassHierarchyResolver {
* @param classToSuperClass a map from classes to their super classes * @param classToSuperClass a map from classes to their super classes
* @return the {@linkplain ClassHierarchyResolver} * @return the {@linkplain ClassHierarchyResolver}
*/ */
public static ClassHierarchyResolver of(Collection<ClassDesc> interfaces, static ClassHierarchyResolver of(Collection<ClassDesc> interfaces,
Map<ClassDesc, ClassDesc> classToSuperClass) { Map<ClassDesc, ClassDesc> classToSuperClass) {
return new ClassHierarchyImpl.StaticClassHierarchyResolver(interfaces, classToSuperClass); return new StaticClassHierarchyResolver(interfaces, classToSuperClass);
}
/**
* Returns a ClassHierarchyResolver that extracts class hierarchy information via
* the Reflection API with a {@linkplain ClassLoader}.
*
* @param loader the class loader
* @return the class hierarchy resolver
*/
static ClassHierarchyResolver ofClassLoading(ClassLoader loader) {
return new ClassLoadingClassHierarchyResolver(new Function<>() {
@Override
public Class<?> apply(ClassDesc cd) {
try {
return Class.forName(Util.toBinaryName(cd.descriptorString()), false, loader);
} catch (ClassNotFoundException ex) {
return null;
}
}
});
}
/**
* Returns a ClassHierarchyResolver that extracts class hierarchy information via
* the Reflection API with a {@linkplain MethodHandles.Lookup Lookup}. If the class
* resolved is inaccessible to the given lookup, it throws {@link
* IllegalArgumentException} instead of returning {@code null}.
*
* @param lookup the lookup, must be able to access classes to resolve
* @return the class hierarchy resolver
*/
static ClassHierarchyResolver ofClassLoading(MethodHandles.Lookup lookup) {
return new ClassLoadingClassHierarchyResolver(new Function<>() {
@Override
public Class<?> apply(ClassDesc cd) {
try {
return cd.resolveConstantDesc(lookup);
} catch (IllegalAccessException ex) {
throw new IllegalArgumentException(ex);
} catch (ReflectiveOperationException ex) {
return null;
}
}
});
} }
} }

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -25,17 +25,22 @@ package jdk.internal.classfile.impl;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.io.DataInputStream; import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.UncheckedIOException;
import java.lang.constant.ClassDesc; import java.lang.constant.ClassDesc;
import java.lang.constant.ConstantDescs;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Supplier;
import jdk.internal.classfile.ClassHierarchyResolver; import jdk.internal.classfile.ClassHierarchyResolver;
import static java.lang.constant.ConstantDescs.CD_Object;
import static jdk.internal.classfile.Classfile.*;
/** /**
* Class hierarchy resolution framework is answering questions about classes assignability, common classes ancestor and whether the class represents an interface. * Class hierarchy resolution framework is answering questions about classes assignability, common classes ancestor and whether the class represents an interface.
* All the requests are handled without class loading nor full verification, optionally with incomplete dependencies and with focus on maximum performance. * All the requests are handled without class loading nor full verification, optionally with incomplete dependencies and with focus on maximum performance.
@ -43,6 +48,20 @@ import jdk.internal.classfile.ClassHierarchyResolver;
*/ */
public final class ClassHierarchyImpl { public final class ClassHierarchyImpl {
public record ClassHierarchyInfoImpl(ClassDesc superClass, boolean isInterface) implements ClassHierarchyResolver.ClassHierarchyInfo {
static final ClassHierarchyResolver.ClassHierarchyInfo OBJECT_INFO = new ClassHierarchyInfoImpl(null, false);
}
public static final ClassHierarchyResolver DEFAULT_RESOLVER = ClassHierarchyResolver
.ofResourceParsing(ResourceParsingClassHierarchyResolver.SYSTEM_STREAM_PROVIDER)
.orElse(new ClassLoadingClassHierarchyResolver(ClassLoadingClassHierarchyResolver.SYSTEM_CLASS_PROVIDER))
.cached(new Supplier<>() {
@Override
public Map<ClassDesc, ClassHierarchyResolver.ClassHierarchyInfo> get() {
return new ConcurrentHashMap<>();
}
});
private final ClassHierarchyResolver resolver; private final ClassHierarchyResolver resolver;
/** /**
@ -53,9 +72,9 @@ public final class ClassHierarchyImpl {
this.resolver = classHierarchyResolver; this.resolver = classHierarchyResolver;
} }
private ClassHierarchyResolver.ClassHierarchyInfo resolve(ClassDesc classDesc) { private ClassHierarchyInfoImpl resolve(ClassDesc classDesc) {
var res = resolver.getClassInfo(classDesc); var res = resolver.getClassInfo(classDesc);
if (res != null) return res; if (res != null) return (ClassHierarchyInfoImpl) res;
throw new IllegalArgumentException("Could not resolve class " + classDesc.displayName()); throw new IllegalArgumentException("Could not resolve class " + classDesc.displayName());
} }
@ -79,7 +98,7 @@ public final class ClassHierarchyImpl {
//calculation of common ancestor is a robust (yet fast) way to decide about assignability in incompletely resolved class hierarchy //calculation of common ancestor is a robust (yet fast) way to decide about assignability in incompletely resolved class hierarchy
//exact order of symbol loops is critical for performance of the above isAssignableFrom method, so standard situations are resolved in linear time //exact order of symbol loops is critical for performance of the above isAssignableFrom method, so standard situations are resolved in linear time
//this method returns null if common ancestor could not be identified //this method returns null if common ancestor could not be identified
if (isInterface(symbol1) || isInterface(symbol2)) return ConstantDescs.CD_Object; if (isInterface(symbol1) || isInterface(symbol2)) return CD_Object;
for (var s1 = symbol1; s1 != null; s1 = resolve(s1).superClass()) { for (var s1 = symbol1; s1 != null; s1 = resolve(s1).superClass()) {
for (var s2 = symbol2; s2 != null; s2 = resolve(s2).superClass()) { for (var s2 = symbol2; s2 != null; s2 = resolve(s2).superClass()) {
if (s1.equals(s2)) return s1; if (s1.equals(s2)) return s1;
@ -101,83 +120,94 @@ public final class ClassHierarchyImpl {
} }
public static final class CachedClassHierarchyResolver implements ClassHierarchyResolver { public static final class CachedClassHierarchyResolver implements ClassHierarchyResolver {
//this instance should leak out, appears only in cache in order to utilize Map.computeIfAbsent
//this instance should never appear in the cache nor leak out
private static final ClassHierarchyResolver.ClassHierarchyInfo NOPE = private static final ClassHierarchyResolver.ClassHierarchyInfo NOPE =
new ClassHierarchyResolver.ClassHierarchyInfo(null, false, null); new ClassHierarchyInfoImpl(null, true);
private final Function<ClassDesc, InputStream> streamProvider; private final Map<ClassDesc, ClassHierarchyInfo> resolvedCache;
private final Map<ClassDesc, ClassHierarchyResolver.ClassHierarchyInfo> resolvedCache; private final Function<ClassDesc, ClassHierarchyInfo> delegateFunction;
public CachedClassHierarchyResolver(Function<ClassDesc, InputStream> classStreamProvider) { public CachedClassHierarchyResolver(ClassHierarchyResolver delegate, Map<ClassDesc, ClassHierarchyInfo> resolvedCache) {
this.streamProvider = classStreamProvider; this.resolvedCache = resolvedCache;
this.resolvedCache = Collections.synchronizedMap(new HashMap<>()); this.delegateFunction = new Function<>() {
@Override
public ClassHierarchyInfo apply(ClassDesc classDesc) {
var ret = delegate.getClassInfo(classDesc);
return ret == null ? NOPE : ret;
}
};
} }
@Override
public ClassHierarchyInfo getClassInfo(ClassDesc classDesc) {
var ret = resolvedCache.computeIfAbsent(classDesc, delegateFunction);
return ret == NOPE ? null : ret;
}
}
public static final class ResourceParsingClassHierarchyResolver implements ClassHierarchyResolver {
public static final Function<ClassDesc, InputStream> SYSTEM_STREAM_PROVIDER = new Function<>() {
@Override
public InputStream apply(ClassDesc cd) {
return ClassLoader.getSystemClassLoader().getResourceAsStream(Util.toInternalName(cd) + ".class");
}
};
private final Function<ClassDesc, InputStream> streamProvider;
public ResourceParsingClassHierarchyResolver(Function<ClassDesc, InputStream> classStreamProvider) {
this.streamProvider = classStreamProvider;
}
// resolve method looks for the class file using <code>ClassStreamResolver</code> instance and tries to briefly scan it just for minimal information necessary // resolve method looks for the class file using <code>ClassStreamResolver</code> instance and tries to briefly scan it just for minimal information necessary
// minimal information includes: identification of the class as interface, obtaining its superclass name and identification of all potential interfaces (to avoid unnecessary future resolutions of them) // minimal information includes: identification of the class as interface, obtaining its superclass name and identification of all potential interfaces (to avoid unnecessary future resolutions of them)
// empty ClInfo is stored in case of an exception to avoid repeated scanning failures // empty ClInfo is stored in case of an exception to avoid repeated scanning failures
@Override @Override
public ClassHierarchyResolver.ClassHierarchyInfo getClassInfo(ClassDesc classDesc) { public ClassHierarchyResolver.ClassHierarchyInfo getClassInfo(ClassDesc classDesc) {
//using NOPE to distinguish between null value and non-existent record in the cache
//this code is on JDK bootstrap critical path, so cannot use lambdas here
var res = resolvedCache.getOrDefault(classDesc, NOPE);
if (res == NOPE) {
res = null;
var ci = streamProvider.apply(classDesc); var ci = streamProvider.apply(classDesc);
if (ci != null) { if (ci == null) return null;
try (var in = new DataInputStream(new BufferedInputStream(ci))) { try (var in = new DataInputStream(new BufferedInputStream(ci))) {
in.skipBytes(8); in.skipBytes(8);
int cpLength = in.readUnsignedShort(); int cpLength = in.readUnsignedShort();
String[] cpStrings = new String[cpLength]; String[] cpStrings = new String[cpLength];
int[] cpClasses = new int[cpLength]; int[] cpClasses = new int[cpLength];
for (int i = 1; i < cpLength; i++) { for (int i = 1; i < cpLength; i++) {
switch (in.readUnsignedByte()) { int tag;
case 1 -> cpStrings[i] = in.readUTF(); switch (tag = in.readUnsignedByte()) {
case 7 -> cpClasses[i] = in.readUnsignedShort(); case TAG_UTF8 -> cpStrings[i] = in.readUTF();
case 8, 16, 19, 20 -> in.skipBytes(2); case TAG_CLASS -> cpClasses[i] = in.readUnsignedShort();
case 15 -> in.skipBytes(3); case TAG_STRING, TAG_METHODTYPE, TAG_MODULE, TAG_PACKAGE -> in.skipBytes(2);
case 3, 4, 9, 10, 11, 12, 17, 18 -> in.skipBytes(4); case TAG_METHODHANDLE -> in.skipBytes(3);
case 5, 6 -> {in.skipBytes(8); i++;} case TAG_INTEGER, TAG_FLOAT, TAG_FIELDREF, TAG_METHODREF, TAG_INTERFACEMETHODREF,
TAG_NAMEANDTYPE, TAG_CONSTANTDYNAMIC, TAG_INVOKEDYNAMIC -> in.skipBytes(4);
case TAG_LONG, TAG_DOUBLE -> {
in.skipBytes(8);
i++;
}
default -> throw new IllegalStateException("Bad tag (" + tag + ") at index (" + i + ")");
} }
} }
boolean isInterface = (in.readUnsignedShort() & 0x0200) != 0; boolean isInterface = (in.readUnsignedShort() & ACC_INTERFACE) != 0;
in.skipBytes(2); in.skipBytes(2);
int superIndex = in.readUnsignedShort(); int superIndex = in.readUnsignedShort();
var superClass = superIndex > 0 ? ClassDesc.ofInternalName(cpStrings[cpClasses[superIndex]]) : null; var superClass = superIndex > 0 ? ClassDesc.ofInternalName(cpStrings[cpClasses[superIndex]]) : null;
res = new ClassHierarchyInfo(classDesc, isInterface, superClass); return new ClassHierarchyInfoImpl(superClass, isInterface);
int interfCount = in.readUnsignedShort(); } catch (IOException ioe) {
for (int i=0; i<interfCount; i++) { throw new UncheckedIOException(ioe);
//all listed interfaces are cached without resolution
var intDesc = ClassDesc.ofInternalName(cpStrings[cpClasses[in.readUnsignedShort()]]);
resolvedCache.put(intDesc, new ClassHierarchyResolver.ClassHierarchyInfo(intDesc, true, null));
} }
} catch (Exception ignore) {
//ignore
}
}
//null ClassHierarchyInfo value is also cached to avoid repeated resolution attempts
resolvedCache.put(classDesc, res);
}
return res;
} }
} }
public static final class StaticClassHierarchyResolver implements ClassHierarchyResolver { public static final class StaticClassHierarchyResolver implements ClassHierarchyResolver {
private static final ClassHierarchyInfo CHI_Object =
new ClassHierarchyInfo(ConstantDescs.CD_Object, false, null);
private final Map<ClassDesc, ClassHierarchyInfo> map; private final Map<ClassDesc, ClassHierarchyInfo> map;
public StaticClassHierarchyResolver(Collection<ClassDesc> interfaceNames, Map<ClassDesc, ClassDesc> classToSuperClass) { public StaticClassHierarchyResolver(Collection<ClassDesc> interfaceNames, Map<ClassDesc, ClassDesc> classToSuperClass) {
map = HashMap.newHashMap(interfaceNames.size() + classToSuperClass.size() + 1); map = HashMap.newHashMap(interfaceNames.size() + classToSuperClass.size() + 1);
map.put(ConstantDescs.CD_Object, CHI_Object); map.put(CD_Object, ClassHierarchyInfoImpl.OBJECT_INFO);
for (var e : classToSuperClass.entrySet()) for (var e : classToSuperClass.entrySet())
map.put(e.getKey(), new ClassHierarchyInfo(e.getKey(), false, e.getValue())); map.put(e.getKey(), ClassHierarchyInfo.ofClass(e.getValue()));
for (var i : interfaceNames) for (var i : interfaceNames)
map.put(i, new ClassHierarchyInfo(i, true, null)); map.put(i, ClassHierarchyInfo.ofInterface());
} }
@Override @Override
@ -185,4 +215,39 @@ public final class ClassHierarchyImpl {
return map.get(classDesc); return map.get(classDesc);
} }
} }
public static final class ClassLoadingClassHierarchyResolver implements ClassHierarchyResolver {
public static final Function<ClassDesc, Class<?>> SYSTEM_CLASS_PROVIDER = new Function<>() {
@Override
public Class<?> apply(ClassDesc cd) {
try {
return Class.forName(Util.toBinaryName(cd.descriptorString()), false, ClassLoader.getSystemClassLoader());
} catch (ClassNotFoundException ex) {
return null;
}
}
};
private final Function<ClassDesc, Class<?>> classProvider;
public ClassLoadingClassHierarchyResolver(Function<ClassDesc, Class<?>> classProvider) {
this.classProvider = classProvider;
}
@Override
public ClassHierarchyInfo getClassInfo(ClassDesc cd) {
if (!cd.isClassOrInterface())
return null;
if (cd.equals(CD_Object))
return ClassHierarchyInfo.ofClass(null);
var cl = classProvider.apply(cd);
if (cl == null) {
return null;
}
return cl.isInterface() ? ClassHierarchyInfo.ofInterface()
: ClassHierarchyInfo.ofClass(cl.getSuperclass().describeConstable().orElseThrow());
}
}
} }

View file

@ -32,7 +32,7 @@ import jdk.internal.classfile.ClassHierarchyResolver;
import jdk.internal.classfile.Classfile; import jdk.internal.classfile.Classfile;
import jdk.internal.classfile.constantpool.Utf8Entry; import jdk.internal.classfile.constantpool.Utf8Entry;
import static jdk.internal.classfile.ClassHierarchyResolver.DEFAULT_CLASS_HIERARCHY_RESOLVER; import static jdk.internal.classfile.ClassHierarchyResolver.defaultResolver;
public class Options { public class Options {
@ -52,7 +52,7 @@ public class Options {
public Boolean fixJumps = true; public Boolean fixJumps = true;
public Boolean patchCode = true; public Boolean patchCode = true;
public Boolean filterDeadLabels = false; public Boolean filterDeadLabels = false;
public ClassHierarchyResolver classHierarchyResolver = DEFAULT_CLASS_HIERARCHY_RESOLVER; public ClassHierarchyResolver classHierarchyResolver = defaultResolver();
public Function<Utf8Entry, AttributeMapper<?>> attributeMapper = new Function<>() { public Function<Utf8Entry, AttributeMapper<?>> attributeMapper = new Function<>() {
@Override @Override
public AttributeMapper<?> apply(Utf8Entry k) { public AttributeMapper<?> apply(Utf8Entry k) {

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -84,9 +84,26 @@ public class Util {
return count; return count;
} }
public static String toClassString(String desc) { /**
//TODO: this doesn't look right L ... ; * Convert a descriptor of classes or interfaces or arrays, or an internal
return desc.replace('/', '.'); * name of a class or interface, into a fully qualified binary name, that can
* be resolved by {@link Class#forName(String) Class::forName}. Primitive type
* descriptors should never be passed into this method.
*
* @param descOrInternalName a descriptor or internal name
* @return the fully qualified binary name
*/
public static String toBinaryName(String descOrInternalName) {
if (descOrInternalName.startsWith("L")) {
// descriptors of classes or interfaces
if (descOrInternalName.length() <= 2 || !descOrInternalName.endsWith(";")) {
throw new IllegalArgumentException(descOrInternalName);
}
return descOrInternalName.substring(1, descOrInternalName.length() - 1).replace('/', '.');
} else {
// arrays, classes or interfaces' internal names
return descOrInternalName.replace('/', '.');
}
} }
public static Iterator<String> parameterTypes(String s) { public static Iterator<String> parameterTypes(String s) {

View file

@ -105,7 +105,7 @@ public final class VerifierImpl {
static final int MAX_CODE_SIZE = 65535; static final int MAX_CODE_SIZE = 65535;
public static List<VerifyError> verify(ClassModel classModel, Consumer<String> logger) { public static List<VerifyError> verify(ClassModel classModel, Consumer<String> logger) {
return verify(classModel, ClassHierarchyResolver.DEFAULT_CLASS_HIERARCHY_RESOLVER, logger); return verify(classModel, ClassHierarchyResolver.defaultResolver(), logger);
} }
public static List<VerifyError> verify(ClassModel classModel, ClassHierarchyResolver classHierarchyResolver, Consumer<String> logger) { public static List<VerifyError> verify(ClassModel classModel, ClassHierarchyResolver classHierarchyResolver, Consumer<String> logger) {

View file

@ -25,6 +25,7 @@
import java.lang.constant.ClassDesc; import java.lang.constant.ClassDesc;
import java.lang.constant.ConstantDescs; import java.lang.constant.ConstantDescs;
import java.lang.constant.MethodTypeDesc; import java.lang.constant.MethodTypeDesc;
import java.lang.invoke.MethodHandles;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
@ -33,7 +34,10 @@ import java.util.LinkedList;
import java.util.Map; import java.util.Map;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream;
import jdk.internal.classfile.ClassElement; import jdk.internal.classfile.ClassElement;
import jdk.internal.classfile.ClassHierarchyResolver;
import jdk.internal.classfile.ClassModel; import jdk.internal.classfile.ClassModel;
import jdk.internal.classfile.ClassTransform; import jdk.internal.classfile.ClassTransform;
import jdk.internal.classfile.Classfile; import jdk.internal.classfile.Classfile;
@ -107,7 +111,7 @@ class PackageSnippets {
ClassModel cm = Classfile.parse(bytes); ClassModel cm = Classfile.parse(bytes);
Set<ClassDesc> dependencies = Set<ClassDesc> dependencies =
cm.elementStream() cm.elementStream()
.flatMap(ce -> ce instanceof MethodMethod mm ? mm.elementStream() : Stream.empty()) .flatMap(ce -> ce instanceof MethodModel mm ? mm.elementStream() : Stream.empty())
.flatMap(me -> me instanceof CodeModel com ? com.elementStream() : Stream.empty()) .flatMap(me -> me instanceof CodeModel com ? com.elementStream() : Stream.empty())
.<ClassDesc>mapMulti((xe, c) -> { .<ClassDesc>mapMulti((xe, c) -> {
switch (xe) { switch (xe) {
@ -303,4 +307,11 @@ class PackageSnippets {
.andThen(instrumentorClassRemapper))))); .andThen(instrumentorClassRemapper)))));
} }
// @end // @end
void resolverExample() {
// @start region="lookup-class-hierarchy-resolver"
MethodHandles.Lookup lookup = MethodHandles.lookup(); // @replace regex="MethodHandles\.lookup\(\)" replacement="..."
ClassHierarchyResolver resolver = ClassHierarchyResolver.ofClassLoading(lookup).cached();
// @end
}
} }

View file

@ -119,7 +119,7 @@ class AdvancedTransformationsTest {
ClassHierarchyResolver.of(Set.of(ClassDesc.of("remapped.List")), Map.of( ClassHierarchyResolver.of(Set.of(ClassDesc.of("remapped.List")), Map.of(
ClassDesc.of("remapped.RemappedBytecode"), ConstantDescs.CD_Object, ClassDesc.of("remapped.RemappedBytecode"), ConstantDescs.CD_Object,
ClassDesc.ofDescriptor(RawBytecodeHelper.class.descriptorString()), ClassDesc.of("remapped.RemappedBytecode"))) ClassDesc.ofDescriptor(RawBytecodeHelper.class.descriptorString()), ClassDesc.of("remapped.RemappedBytecode")))
.orElse(ClassHierarchyResolver.DEFAULT_CLASS_HIERARCHY_RESOLVER) .orElse(ClassHierarchyResolver.defaultResolver())
, null)); //System.out::print)); , null)); //System.out::print));
remapped.fields().forEach(f -> f.findAttribute(Attributes.SIGNATURE).ifPresent(sa -> remapped.fields().forEach(f -> f.findAttribute(Attributes.SIGNATURE).ifPresent(sa ->
verifySignature(f.fieldTypeSymbol(), sa.asTypeSignature()))); verifySignature(f.fieldTypeSymbol(), sa.asTypeSignature())));

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -25,16 +25,28 @@
/* /*
* @test * @test
* @modules java.base/jdk.internal.classfile
* java.base/jdk.internal.classfile.attribute
* java.base/jdk.internal.classfile.constantpool
* java.base/jdk.internal.classfile.instruction
* java.base/jdk.internal.classfile.impl
* java.base/jdk.internal.classfile.impl.verifier
* java.base/jdk.internal.classfile.components
* java.base/java.util:open
* @comment Opens java.util so HashMap bytecode generation can access its nested
* classes with a proper Lookup object
* @summary Testing Classfile class hierarchy resolution SPI. * @summary Testing Classfile class hierarchy resolution SPI.
* @run junit ClassHierarchyInfoTest * @run junit ClassHierarchyInfoTest
*/ */
import java.io.IOException; import java.io.IOException;
import java.lang.constant.ClassDesc; import java.lang.constant.ClassDesc;
import java.lang.constant.ConstantDescs; import java.lang.constant.ConstantDescs;
import java.lang.invoke.MethodHandles;
import java.net.URI; import java.net.URI;
import java.nio.file.FileSystems; import java.nio.file.FileSystems;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import jdk.internal.classfile.ClassHierarchyResolver; import jdk.internal.classfile.ClassHierarchyResolver;
@ -65,18 +77,18 @@ class ClassHierarchyInfoTest {
} }
@Test @Test
void testBreakDefaulClassHierarchy() throws Exception { void testBreakDefaultClassHierarchy() throws Exception {
assertThrows(VerifyError.class, () -> assertThrows(VerifyError.class, () ->
transformAndVerify(ClassHierarchyResolver.of( transformAndVerify(ClassHierarchyResolver.of(
Set.of(), Set.of(),
Map.of(ClassDesc.of("java.util.HashMap$Node"), ClassDesc.of("java.util.HashMap$TreeNode"))).orElse(ClassHierarchyResolver.DEFAULT_CLASS_HIERARCHY_RESOLVER)) Map.of(ClassDesc.of("java.util.HashMap$Node"), ClassDesc.of("java.util.HashMap$TreeNode"))).orElse(ClassHierarchyResolver.defaultResolver()))
); );
} }
@Test @Test
void testProvideCustomClassStreamResolver() throws Exception { void testProvideCustomClassStreamResolver() throws Exception {
var fs = FileSystems.getFileSystem(URI.create("jrt:/")); var fs = FileSystems.getFileSystem(URI.create("jrt:/"));
transformAndVerify(ClassHierarchyResolver.ofCached(classDesc -> { transformAndVerify(ClassHierarchyResolver.ofResourceParsing(classDesc -> {
try { try {
return Files.newInputStream(fs.getPath("modules/java.base/" + Util.toInternalName(classDesc) + ".class")); return Files.newInputStream(fs.getPath("modules/java.base/" + Util.toInternalName(classDesc) + ".class"));
} catch (IOException ioe) { } catch (IOException ioe) {
@ -85,7 +97,36 @@ class ClassHierarchyInfoTest {
})); }));
} }
@Test
void testClassLoaderParsingResolver() throws Exception {
transformAndVerify(ClassHierarchyResolver.ofResourceParsing(ClassLoader.getSystemClassLoader()));
}
@Test
void testClassLoaderReflectionResolver() throws Exception {
transformAndVerify(ClassHierarchyResolver.ofClassLoading(ClassLoader.getSystemClassLoader()));
}
@Test
void testLookupResolver() throws Exception {
// A lookup must be able to access all the classes involved in the class file generation
var privilegedLookup = MethodHandles.privateLookupIn(HashMap.class, MethodHandles.lookup());
transformAndVerify(ClassHierarchyResolver.ofClassLoading(privilegedLookup));
}
@Test
void testLookupResolver_IllegalAccess() throws Exception {
// A lookup from this test class, cannot access nested classes in HashMap
var lookup = MethodHandles.lookup();
assertThrows(IllegalArgumentException.class, () -> transformAndVerify(ClassHierarchyResolver.ofClassLoading(lookup)));
}
void transformAndVerify(ClassHierarchyResolver res) throws Exception { void transformAndVerify(ClassHierarchyResolver res) throws Exception {
transformAndVerifySingle(res);
transformAndVerifySingle(res.cached());
}
void transformAndVerifySingle(ClassHierarchyResolver res) throws Exception {
Path path = FileSystems.getFileSystem(URI.create("jrt:/")).getPath("modules/java.base/java/util/HashMap.class"); Path path = FileSystems.getFileSystem(URI.create("jrt:/")).getPath("modules/java.base/java/util/HashMap.class");
var classModel = Classfile.parse(path, Classfile.Option.classHierarchyResolver(res)); var classModel = Classfile.parse(path, Classfile.Option.classHierarchyResolver(res));
byte[] newBytes = classModel.transform( byte[] newBytes = classModel.transform(
@ -103,6 +144,11 @@ class ClassHierarchyInfoTest {
clb.with(cle); clb.with(cle);
}); });
var errors = Classfile.parse(newBytes).verify(null); var errors = Classfile.parse(newBytes).verify(null);
if (!errors.isEmpty()) throw errors.iterator().next(); if (!errors.isEmpty()) {
var itr = errors.iterator();
var thrown = itr.next();
itr.forEachRemaining(thrown::addSuppressed);
throw thrown;
}
} }
} }

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -31,6 +31,8 @@
import java.lang.constant.MethodTypeDesc; import java.lang.constant.MethodTypeDesc;
import jdk.internal.classfile.impl.Util; import jdk.internal.classfile.impl.Util;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*;
@ -38,6 +40,25 @@ import static org.junit.jupiter.api.Assertions.*;
* UtilTest * UtilTest
*/ */
class UtilTest { class UtilTest {
@ParameterizedTest
@ValueSource(classes = {
Long.class,
Object.class,
Util.class,
Test.class,
int[][].class,
Object[].class,
})
void testDescToBinaryName(Class<?> type) throws ReflectiveOperationException {
if (!type.isArray()) {
// Test internal name
var internal = type.getName().replace('.', '/');
assertEquals(type, Class.forName(Util.toBinaryName(internal)));
}
// Test descriptor
assertEquals(type, Class.forName(Util.toBinaryName(type.descriptorString())));
}
@Test @Test
void testParameterSlots() { void testParameterSlots() {

View file

@ -64,7 +64,7 @@ class VerifierSelfTest {
void testFailedDump() throws IOException { void testFailedDump() throws IOException {
Path path = FileSystems.getFileSystem(URI.create("jrt:/")).getPath("modules/java.base/java/util/HashMap.class"); Path path = FileSystems.getFileSystem(URI.create("jrt:/")).getPath("modules/java.base/java/util/HashMap.class");
var classModel = Classfile.parse(path, Classfile.Option.classHierarchyResolver( var classModel = Classfile.parse(path, Classfile.Option.classHierarchyResolver(
className -> new ClassHierarchyResolver.ClassHierarchyInfo(className, false, null))); className -> ClassHierarchyResolver.ClassHierarchyInfo.ofClass(null)));
byte[] brokenClassBytes = classModel.transform( byte[] brokenClassBytes = classModel.transform(
(clb, cle) -> { (clb, cle) -> {
if (cle instanceof MethodModel mm) { if (cle instanceof MethodModel mm) {