mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-28 07:14:30 +02:00
8304425: ClassHierarchyResolver from Reflection
Reviewed-by: asotona
This commit is contained in:
parent
79a4ac791c
commit
ac3ce2bf75
10 changed files with 373 additions and 98 deletions
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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())));
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue