From ac3ce2bf759735042480b846f3c1cf37a0843b8d Mon Sep 17 00:00:00 2001 From: Chen Liang Date: Thu, 8 Jun 2023 07:29:56 +0000 Subject: [PATCH] 8304425: ClassHierarchyResolver from Reflection Reviewed-by: asotona --- .../classfile/ClassHierarchyResolver.java | 165 +++++++++++++--- .../classfile/impl/ClassHierarchyImpl.java | 179 ++++++++++++------ .../jdk/internal/classfile/impl/Options.java | 4 +- .../jdk/internal/classfile/impl/Util.java | 25 ++- .../classfile/impl/verifier/VerifierImpl.java | 2 +- .../snippet-files/PackageSnippets.java | 13 +- .../AdvancedTransformationsTest.java | 2 +- .../jdk/classfile/ClassHierarchyInfoTest.java | 56 +++++- test/jdk/jdk/classfile/UtilTest.java | 23 ++- test/jdk/jdk/classfile/VerifierSelfTest.java | 2 +- 10 files changed, 373 insertions(+), 98 deletions(-) diff --git a/src/java.base/share/classes/jdk/internal/classfile/ClassHierarchyResolver.java b/src/java.base/share/classes/jdk/internal/classfile/ClassHierarchyResolver.java index 1e74393dd9e..161d15a7152 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/ClassHierarchyResolver.java +++ b/src/java.base/share/classes/jdk/internal/classfile/ClassHierarchyResolver.java @@ -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. * * 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.lang.constant.ClassDesc; +import java.lang.invoke.MethodHandles; import java.util.Collection; +import java.util.HashMap; import java.util.Map; 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.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 @@ -41,18 +48,14 @@ import jdk.internal.classfile.impl.ClassHierarchyImpl; public interface ClassHierarchyResolver { /** - * Default singleton instance of {@linkplain ClassHierarchyResolver} - * using {@link ClassLoader#getSystemResourceAsStream(String)} - * as the {@code ClassStreamResolver} + * Returns a default instance of {@linkplain ClassHierarchyResolver} + * that reads from system class loader with + * {@link ClassLoader#getSystemResourceAsStream(String)} and falls + * back to reflection if a class is not found. */ - ClassHierarchyResolver DEFAULT_CLASS_HIERARCHY_RESOLVER - = new ClassHierarchyImpl.CachedClassHierarchyResolver( - new Function() { - @Override - public InputStream apply(ClassDesc classDesc) { - return ClassLoader.getSystemResourceAsStream(Util.toInternalName(classDesc) + ".class"); - } - }); + static ClassHierarchyResolver defaultResolver() { + return ClassHierarchyImpl.DEFAULT_RESOLVER; + } /** * {@return the {@link ClassHierarchyInfo} for a given class name, or null @@ -61,6 +64,31 @@ public interface ClassHierarchyResolver { */ 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 * consulted if this resolver does not know about the specified class. @@ -74,30 +102,73 @@ public interface ClassHierarchyResolver { public ClassHierarchyInfo getClassInfo(ClassDesc classDesc) { var chi = ClassHierarchyResolver.this.getClassInfo(classDesc); if (chi == null) - chi = other.getClassInfo(classDesc); + return other.getClassInfo(classDesc); return chi; } }; } /** - * Information about a resolved class. - * @param thisClass descriptor of this class - * @param isInterface whether this class is an interface - * @param superClass descriptor of the superclass (not relevant for interfaces) + * Returns a ClassHierarchyResolver that caches class hierarchy information from this + * resolver. The returned resolver will not update if delegate resolver returns differently. + * The thread safety of the returned resolver depends on the thread safety of the map + * 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> cacheFactory) { + return new ClassHierarchyImpl.CachedClassHierarchyResolver(this, cacheFactory.get()); } /** - * Returns a {@linkplain ClassHierarchyResolver} that extracts class hierarchy - * information from classfiles located by a mapping function + * 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> { + // uses a record as we cannot declare a local class static + static final Factory INSTANCE = new Factory(); + + @Override + public Map get() { + return new HashMap<>(); + } + } + return cached(Factory.INSTANCE); + } + + /** + * Returns a {@linkplain ClassHierarchyResolver} that extracts class hierarchy + * 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 * @return the {@linkplain ClassHierarchyResolver} */ - public static ClassHierarchyResolver ofCached(Function classStreamResolver) { - return new ClassHierarchyImpl.CachedClassHierarchyResolver(classStreamResolver); + static ClassHierarchyResolver ofResourceParsing(Function 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 * @return the {@linkplain ClassHierarchyResolver} */ - public static ClassHierarchyResolver of(Collection interfaces, + static ClassHierarchyResolver of(Collection interfaces, Map 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; + } + } + }); } } diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/ClassHierarchyImpl.java b/src/java.base/share/classes/jdk/internal/classfile/impl/ClassHierarchyImpl.java index 898d6ddaac2..5069f8bbc08 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/ClassHierarchyImpl.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/ClassHierarchyImpl.java @@ -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. * * 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.DataInputStream; +import java.io.IOException; import java.io.InputStream; +import java.io.UncheckedIOException; import java.lang.constant.ClassDesc; -import java.lang.constant.ConstantDescs; import java.util.Collection; -import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; +import java.util.function.Supplier; 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. * 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 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 get() { + return new ConcurrentHashMap<>(); + } + }); + private final ClassHierarchyResolver resolver; /** @@ -53,9 +72,9 @@ public final class ClassHierarchyImpl { this.resolver = classHierarchyResolver; } - private ClassHierarchyResolver.ClassHierarchyInfo resolve(ClassDesc classDesc) { + private ClassHierarchyInfoImpl resolve(ClassDesc 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()); } @@ -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 //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 - 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 s2 = symbol2; s2 != null; s2 = resolve(s2).superClass()) { if (s1.equals(s2)) return s1; @@ -101,83 +120,94 @@ public final class ClassHierarchyImpl { } public static final class CachedClassHierarchyResolver implements ClassHierarchyResolver { - - //this instance should never appear in the cache nor leak out + //this instance should leak out, appears only in cache in order to utilize Map.computeIfAbsent private static final ClassHierarchyResolver.ClassHierarchyInfo NOPE = - new ClassHierarchyResolver.ClassHierarchyInfo(null, false, null); + new ClassHierarchyInfoImpl(null, true); - private final Function streamProvider; - private final Map resolvedCache; + private final Map resolvedCache; + private final Function delegateFunction; - public CachedClassHierarchyResolver(Function classStreamProvider) { - this.streamProvider = classStreamProvider; - this.resolvedCache = Collections.synchronizedMap(new HashMap<>()); + public CachedClassHierarchyResolver(ClassHierarchyResolver delegate, Map resolvedCache) { + this.resolvedCache = resolvedCache; + 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 SYSTEM_STREAM_PROVIDER = new Function<>() { + @Override + public InputStream apply(ClassDesc cd) { + return ClassLoader.getSystemClassLoader().getResourceAsStream(Util.toInternalName(cd) + ".class"); + } + }; + private final Function streamProvider; + + public ResourceParsingClassHierarchyResolver(Function classStreamProvider) { + this.streamProvider = classStreamProvider; + } // resolve method looks for the class file using ClassStreamResolver 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) // empty ClInfo is stored in case of an exception to avoid repeated scanning failures @Override 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); - if (ci != null) { - try (var in = new DataInputStream(new BufferedInputStream(ci))) { - in.skipBytes(8); - int cpLength = in.readUnsignedShort(); - String[] cpStrings = new String[cpLength]; - int[] cpClasses = new int[cpLength]; - for (int i=1; i cpStrings[i] = in.readUTF(); - case 7 -> cpClasses[i] = in.readUnsignedShort(); - case 8, 16, 19, 20 -> in.skipBytes(2); - case 15 -> in.skipBytes(3); - case 3, 4, 9, 10, 11, 12, 17, 18 -> in.skipBytes(4); - case 5, 6 -> {in.skipBytes(8); i++;} - } + var ci = streamProvider.apply(classDesc); + if (ci == null) return null; + try (var in = new DataInputStream(new BufferedInputStream(ci))) { + in.skipBytes(8); + int cpLength = in.readUnsignedShort(); + String[] cpStrings = new String[cpLength]; + int[] cpClasses = new int[cpLength]; + for (int i = 1; i < cpLength; i++) { + int tag; + switch (tag = in.readUnsignedByte()) { + case TAG_UTF8 -> cpStrings[i] = in.readUTF(); + case TAG_CLASS -> cpClasses[i] = in.readUnsignedShort(); + case TAG_STRING, TAG_METHODTYPE, TAG_MODULE, TAG_PACKAGE -> in.skipBytes(2); + case TAG_METHODHANDLE -> in.skipBytes(3); + 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++; } - boolean isInterface = (in.readUnsignedShort() & 0x0200) != 0; - in.skipBytes(2); - int superIndex = in.readUnsignedShort(); - var superClass = superIndex > 0 ? ClassDesc.ofInternalName(cpStrings[cpClasses[superIndex]]) : null; - res = new ClassHierarchyInfo(classDesc, isInterface, superClass); - int interfCount = in.readUnsignedShort(); - for (int i=0; i throw new IllegalStateException("Bad tag (" + tag + ") at index (" + i + ")"); } } - //null ClassHierarchyInfo value is also cached to avoid repeated resolution attempts - resolvedCache.put(classDesc, res); + boolean isInterface = (in.readUnsignedShort() & ACC_INTERFACE) != 0; + in.skipBytes(2); + int superIndex = in.readUnsignedShort(); + var superClass = superIndex > 0 ? ClassDesc.ofInternalName(cpStrings[cpClasses[superIndex]]) : null; + return new ClassHierarchyInfoImpl(superClass, isInterface); + } catch (IOException ioe) { + throw new UncheckedIOException(ioe); } - return res; } } public static final class StaticClassHierarchyResolver implements ClassHierarchyResolver { - private static final ClassHierarchyInfo CHI_Object = - new ClassHierarchyInfo(ConstantDescs.CD_Object, false, null); - private final Map map; public StaticClassHierarchyResolver(Collection interfaceNames, Map classToSuperClass) { 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()) - map.put(e.getKey(), new ClassHierarchyInfo(e.getKey(), false, e.getValue())); + map.put(e.getKey(), ClassHierarchyInfo.ofClass(e.getValue())); for (var i : interfaceNames) - map.put(i, new ClassHierarchyInfo(i, true, null)); + map.put(i, ClassHierarchyInfo.ofInterface()); } @Override @@ -185,4 +215,39 @@ public final class ClassHierarchyImpl { return map.get(classDesc); } } + + public static final class ClassLoadingClassHierarchyResolver implements ClassHierarchyResolver { + public static final Function> 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> classProvider; + + public ClassLoadingClassHierarchyResolver(Function> 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()); + } + } } diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/Options.java b/src/java.base/share/classes/jdk/internal/classfile/impl/Options.java index d9bf3fd05c7..1f017625f2d 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/Options.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/Options.java @@ -32,7 +32,7 @@ import jdk.internal.classfile.ClassHierarchyResolver; import jdk.internal.classfile.Classfile; 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 { @@ -52,7 +52,7 @@ public class Options { public Boolean fixJumps = true; public Boolean patchCode = true; public Boolean filterDeadLabels = false; - public ClassHierarchyResolver classHierarchyResolver = DEFAULT_CLASS_HIERARCHY_RESOLVER; + public ClassHierarchyResolver classHierarchyResolver = defaultResolver(); public Function> attributeMapper = new Function<>() { @Override public AttributeMapper apply(Utf8Entry k) { diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/Util.java b/src/java.base/share/classes/jdk/internal/classfile/impl/Util.java index c66b097cfca..d4ee49aca6e 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/Util.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/Util.java @@ -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. * * This code is free software; you can redistribute it and/or modify it @@ -84,9 +84,26 @@ public class Util { return count; } - public static String toClassString(String desc) { - //TODO: this doesn't look right L ... ; - return desc.replace('/', '.'); + /** + * Convert a descriptor of classes or interfaces or arrays, or an internal + * 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 parameterTypes(String s) { diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/VerifierImpl.java b/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/VerifierImpl.java index 14be6c3e675..47b438ca0b4 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/VerifierImpl.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/verifier/VerifierImpl.java @@ -105,7 +105,7 @@ public final class VerifierImpl { static final int MAX_CODE_SIZE = 65535; public static List verify(ClassModel classModel, Consumer logger) { - return verify(classModel, ClassHierarchyResolver.DEFAULT_CLASS_HIERARCHY_RESOLVER, logger); + return verify(classModel, ClassHierarchyResolver.defaultResolver(), logger); } public static List verify(ClassModel classModel, ClassHierarchyResolver classHierarchyResolver, Consumer logger) { diff --git a/src/java.base/share/classes/jdk/internal/classfile/snippet-files/PackageSnippets.java b/src/java.base/share/classes/jdk/internal/classfile/snippet-files/PackageSnippets.java index 1814412556c..931fe5e6c7b 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/snippet-files/PackageSnippets.java +++ b/src/java.base/share/classes/jdk/internal/classfile/snippet-files/PackageSnippets.java @@ -25,6 +25,7 @@ import java.lang.constant.ClassDesc; import java.lang.constant.ConstantDescs; import java.lang.constant.MethodTypeDesc; +import java.lang.invoke.MethodHandles; import java.util.HashSet; import java.util.Set; @@ -33,7 +34,10 @@ import java.util.LinkedList; import java.util.Map; import java.util.function.Predicate; import java.util.stream.Collectors; +import java.util.stream.Stream; + import jdk.internal.classfile.ClassElement; +import jdk.internal.classfile.ClassHierarchyResolver; import jdk.internal.classfile.ClassModel; import jdk.internal.classfile.ClassTransform; import jdk.internal.classfile.Classfile; @@ -107,7 +111,7 @@ class PackageSnippets { ClassModel cm = Classfile.parse(bytes); Set dependencies = 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()) .mapMulti((xe, c) -> { switch (xe) { @@ -303,4 +307,11 @@ class PackageSnippets { .andThen(instrumentorClassRemapper))))); } // @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 + } } diff --git a/test/jdk/jdk/classfile/AdvancedTransformationsTest.java b/test/jdk/jdk/classfile/AdvancedTransformationsTest.java index e809d40933c..e9403b75ac0 100644 --- a/test/jdk/jdk/classfile/AdvancedTransformationsTest.java +++ b/test/jdk/jdk/classfile/AdvancedTransformationsTest.java @@ -119,7 +119,7 @@ class AdvancedTransformationsTest { ClassHierarchyResolver.of(Set.of(ClassDesc.of("remapped.List")), Map.of( ClassDesc.of("remapped.RemappedBytecode"), ConstantDescs.CD_Object, ClassDesc.ofDescriptor(RawBytecodeHelper.class.descriptorString()), ClassDesc.of("remapped.RemappedBytecode"))) - .orElse(ClassHierarchyResolver.DEFAULT_CLASS_HIERARCHY_RESOLVER) + .orElse(ClassHierarchyResolver.defaultResolver()) , null)); //System.out::print)); remapped.fields().forEach(f -> f.findAttribute(Attributes.SIGNATURE).ifPresent(sa -> verifySignature(f.fieldTypeSymbol(), sa.asTypeSignature()))); diff --git a/test/jdk/jdk/classfile/ClassHierarchyInfoTest.java b/test/jdk/jdk/classfile/ClassHierarchyInfoTest.java index 30acf359064..1cd69ccc541 100644 --- a/test/jdk/jdk/classfile/ClassHierarchyInfoTest.java +++ b/test/jdk/jdk/classfile/ClassHierarchyInfoTest.java @@ -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. * * This code is free software; you can redistribute it and/or modify it @@ -25,16 +25,28 @@ /* * @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. * @run junit ClassHierarchyInfoTest */ import java.io.IOException; import java.lang.constant.ClassDesc; import java.lang.constant.ConstantDescs; +import java.lang.invoke.MethodHandles; import java.net.URI; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; +import java.util.HashMap; import java.util.Map; import java.util.Set; import jdk.internal.classfile.ClassHierarchyResolver; @@ -65,18 +77,18 @@ class ClassHierarchyInfoTest { } @Test - void testBreakDefaulClassHierarchy() throws Exception { + void testBreakDefaultClassHierarchy() throws Exception { assertThrows(VerifyError.class, () -> transformAndVerify(ClassHierarchyResolver.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 void testProvideCustomClassStreamResolver() throws Exception { var fs = FileSystems.getFileSystem(URI.create("jrt:/")); - transformAndVerify(ClassHierarchyResolver.ofCached(classDesc -> { + transformAndVerify(ClassHierarchyResolver.ofResourceParsing(classDesc -> { try { return Files.newInputStream(fs.getPath("modules/java.base/" + Util.toInternalName(classDesc) + ".class")); } 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 { + 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"); var classModel = Classfile.parse(path, Classfile.Option.classHierarchyResolver(res)); byte[] newBytes = classModel.transform( @@ -103,6 +144,11 @@ class ClassHierarchyInfoTest { clb.with(cle); }); 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; + } } } diff --git a/test/jdk/jdk/classfile/UtilTest.java b/test/jdk/jdk/classfile/UtilTest.java index 1ce8c27dd30..2790213acfa 100644 --- a/test/jdk/jdk/classfile/UtilTest.java +++ b/test/jdk/jdk/classfile/UtilTest.java @@ -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. * * This code is free software; you can redistribute it and/or modify it @@ -31,6 +31,8 @@ import java.lang.constant.MethodTypeDesc; import jdk.internal.classfile.impl.Util; 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.*; @@ -38,6 +40,25 @@ import static org.junit.jupiter.api.Assertions.*; * 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 void testParameterSlots() { diff --git a/test/jdk/jdk/classfile/VerifierSelfTest.java b/test/jdk/jdk/classfile/VerifierSelfTest.java index 67b08c8cf3e..2bfaae3c99a 100644 --- a/test/jdk/jdk/classfile/VerifierSelfTest.java +++ b/test/jdk/jdk/classfile/VerifierSelfTest.java @@ -64,7 +64,7 @@ class VerifierSelfTest { void testFailedDump() throws IOException { Path path = FileSystems.getFileSystem(URI.create("jrt:/")).getPath("modules/java.base/java/util/HashMap.class"); var classModel = Classfile.parse(path, Classfile.Option.classHierarchyResolver( - className -> new ClassHierarchyResolver.ClassHierarchyInfo(className, false, null))); + className -> ClassHierarchyResolver.ClassHierarchyInfo.ofClass(null))); byte[] brokenClassBytes = classModel.transform( (clb, cle) -> { if (cle instanceof MethodModel mm) {