Skip to content

Commit

Permalink
Support dynamic proxy classes with (and as) predefined classes.
Browse files Browse the repository at this point in the history
  • Loading branch information
peter-hofer committed Jul 16, 2021
1 parent b36b3b2 commit d72e67b
Show file tree
Hide file tree
Showing 12 changed files with 201 additions and 75 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@

import com.oracle.svm.agent.stackaccess.InterceptedState;
import com.oracle.svm.agent.tracing.core.Tracer;
import com.oracle.svm.configure.trace.AccessAdvisor;
import com.oracle.svm.core.c.function.CEntryPointOptions;
import com.oracle.svm.core.util.VMError;
import com.oracle.svm.jni.JNIObjectHandles;
Expand Down Expand Up @@ -997,6 +998,11 @@ private static void onClassFileLoadHook(@SuppressWarnings("unused") JvmtiEnv jvm
if (loader.equal(nullHandle())) { // boot class loader
return;
}
String className = fromCString(name);
if (className != null && AccessAdvisor.PROXY_CLASS_NAME_PATTERN.matcher(className).matches()) {
// Proxy classes are handled using a different mechanism, so we ignore them here
return;
}
for (JNIObjectHandle builtinLoader : builtinClassLoaders) {
if (jniFunctions().getIsSameObject().invoke(jni, loader, builtinLoader)) {
return;
Expand All @@ -1007,7 +1013,7 @@ private static void onClassFileLoadHook(@SuppressWarnings("unused") JvmtiEnv jvm
}
byte[] data = new byte[classDataLen];
CTypeConversion.asByteBuffer(classData, classDataLen).get(data);
tracer.traceCall("classloading", "onClassFileLoadHook", null, null, null, null, state.getFullStackTraceOrNull(), fromCString(name), data);
tracer.traceCall("classloading", "onClassFileLoadHook", null, null, null, null, state.getFullStackTraceOrNull(), className, data);
}

private static final CEntryPointLiteral<CFunctionPointer> onBreakpointLiteral = CEntryPointLiteral.create(BreakpointInterceptor.class, "onBreakpoint",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,10 @@ private static void printArray(JsonWriter json, Object[] array) throws IOExcepti
}

private static void printValue(JsonWriter json, Object value) throws IOException {
String s;
String s = null;
if (value instanceof byte[]) {
s = Base64.getEncoder().encodeToString((byte[]) value);
} else {
} else if (value != null) {
s = value.toString();
}
json.quote(s);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
*/
package com.oracle.svm.configure.trace;

import java.util.regex.Pattern;

import org.graalvm.compiler.phases.common.LazyValue;

import com.oracle.svm.configure.filters.RuleNode;
Expand All @@ -33,6 +35,12 @@
* {@code AccessVerifier} classes which accesses to ignore when the agent is in restriction mode.
*/
public final class AccessAdvisor {
/**
* {@link java.lang.reflect.Proxy} generated classes can be put in arbitrary packages depending
* on the visibility and module of the interfaces they implement, so we can only match against
* their class name using this pattern (which we hope isn't used by anything else).
*/
public static final Pattern PROXY_CLASS_NAME_PATTERN = Pattern.compile("^(.+[/.])?\\$Proxy[0-9]+$");

/** Filter to ignore accesses that <em>originate in</em> methods of these internal classes. */
private static final RuleNode internalCallerFilter;
Expand Down Expand Up @@ -154,7 +162,10 @@ public boolean shouldIgnore(LazyValue<String> queriedClass, LazyValue<String> ca
if (callerClass.get() == null && queriedClass.get() != null && !accessWithoutCallerFilter.treeIncludes(queriedClass.get())) {
return true;
}
return accessFilter != null && queriedClass.get() != null && !accessFilter.treeIncludes(queriedClass.get());
if (accessFilter != null && queriedClass.get() != null && !accessFilter.treeIncludes(queriedClass.get())) {
return true;
}
return heuristicsEnabled && queriedClass.get() != null && PROXY_CLASS_NAME_PATTERN.matcher(queriedClass.get()).matches();
}

public boolean shouldIgnoreJniMethodLookup(LazyValue<String> queriedClass, LazyValue<String> name, LazyValue<String> signature, LazyValue<String> callerClass) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ void processEntry(Map<String, ?> entry) {
if (!advisor.shouldIgnore(lazyValue(forNameString), callerClassLazyValue)) {
if (function.equals("FindClass")) {
configuration.getOrCreateType(forNameString);
} else if (!lookupName.startsWith("com/sun/proxy/$Proxy")) { // DefineClass
} else if (!AccessAdvisor.PROXY_CLASS_NAME_PATTERN.matcher(lookupName).matches()) { // DefineClass
logWarning("Unsupported JNI function DefineClass used to load class " + forNameString);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,8 @@
public final class DynamicHub implements JavaKind.FormatWithToString, AnnotatedElement, java.lang.reflect.Type, GenericDeclaration, Serializable,
Target_java_lang_invoke_TypeDescriptor_OfField<DynamicHub>, Target_java_lang_constant_Constable {

/** Marker that a value must be obtained from the companion object. */
private static final Object FROM_COMPANION = new Object();
/** Marker value for {@link #classLoader}. */
static final Object NO_CLASS_LOADER = new Object();

@Substitute //
@TargetElement(onlyWith = JDK11OrLater.class) //
Expand Down Expand Up @@ -362,7 +362,7 @@ public DynamicHub(Class<?> hostedJavaClass, String name, HubType hubType, Refere
this.componentType = componentHub;
this.sourceFileName = sourceFileName;
this.modifiers = modifiers;
this.classLoader = PredefinedClassesSupport.isPredefined(hostedJavaClass) ? FROM_COMPANION : classLoader;
this.classLoader = PredefinedClassesSupport.isPredefined(hostedJavaClass) ? NO_CLASS_LOADER : classLoader;
setFlag(IS_HIDDED_FLAG_BIT, isHidden);
setFlag(IS_RECORD_FLAG_BIT, isRecord);
this.nestHost = nestHost;
Expand Down Expand Up @@ -741,14 +741,14 @@ public InputStream getResourceAsStream(String resourceName) {

@Substitute
private ClassLoader getClassLoader0() {
if (classLoader == FROM_COMPANION) {
if (classLoader == NO_CLASS_LOADER) {
return companion.get().getClassLoader();
}
return (ClassLoader) classLoader;
}

public boolean isLoaded() {
return classLoader != FROM_COMPANION || (companion.isPresent() && companion.get().hasClassLoader());
return classLoader != NO_CLASS_LOADER || (companion.isPresent() && companion.get().hasClassLoader());
}

void setClassLoaderAtRuntime(ClassLoader loader) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
*/
package com.oracle.svm.core.hub;

import static com.oracle.svm.core.hub.DynamicHub.NO_CLASS_LOADER;

import java.security.ProtectionDomain;

import com.oracle.svm.core.util.VMError;
Expand All @@ -33,7 +35,7 @@ public final class DynamicHubCompanion {
private final DynamicHub hub;

private String packageName;
private ClassLoader classLoader;
private Object classLoader = NO_CLASS_LOADER;
private ProtectionDomain protectionDomain;

public DynamicHubCompanion(DynamicHub hub) {
Expand All @@ -48,17 +50,17 @@ public String getPackageName() {
}

boolean hasClassLoader() {
return classLoader != null;
return classLoader != NO_CLASS_LOADER;
}

public ClassLoader getClassLoader() {
ClassLoader loader = classLoader;
VMError.guarantee(loader != null);
return loader;
Object loader = classLoader;
VMError.guarantee(loader != NO_CLASS_LOADER);
return (ClassLoader) loader;
}

public void setClassLoader(ClassLoader loader) {
VMError.guarantee(classLoader == null && loader != null);
VMError.guarantee(classLoader == NO_CLASS_LOADER && loader != NO_CLASS_LOADER);
classLoader = loader;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
import java.util.concurrent.locks.ReentrantLock;

import org.graalvm.collections.EconomicMap;
import org.graalvm.collections.UnmodifiableEconomicMap;
import org.graalvm.compiler.api.replacements.Fold;
import org.graalvm.compiler.options.Option;
import org.graalvm.nativeimage.ImageSingletons;
Expand All @@ -45,6 +44,7 @@
import com.oracle.svm.core.option.SubstrateOptionsParser;
import com.oracle.svm.core.util.ImageHeapMap;
import com.oracle.svm.core.util.VMError;
import com.oracle.svm.util.ClassUtil;

public final class PredefinedClassesSupport {
public static final class Options {
Expand Down Expand Up @@ -104,6 +104,15 @@ public static void registerClass(String hash, Class<?> clazz) {
}
}

/**
* Register a class that cannot be loaded via a byte array of its class data, only with
* {@link #loadClass(ClassLoader, ProtectionDomain, Class)} or {@link #loadClassIfNotLoaded}.
*/
@Platforms(Platform.HOSTED_ONLY.class)
public static void registerClass(Class<?> clazz) {
singleton().predefinedClasses.add(clazz);
}

@Platforms(Platform.HOSTED_ONLY.class)
public static boolean isPredefined(Class<?> clazz) {
return singleton().predefinedClasses.contains(clazz);
Expand All @@ -120,21 +129,37 @@ public static Class<?> loadClass(ClassLoader classLoader, String expectedName, b
throw VMError.unsupportedFeature("Defining a class from new bytecodes at run time is not supported. Class " + name +
" with hash " + hash + " was not provided during the image build. Please see BuildConfiguration.md.");
}
return singleton().load(classLoader, protectionDomain, clazz);
loadClass(classLoader, protectionDomain, clazz);
return clazz;
}

public static void loadClass(ClassLoader classLoader, ProtectionDomain protectionDomain, Class<?> clazz) {
boolean loaded = loadClassIfNotLoaded(classLoader, protectionDomain, clazz);
if (!loaded) {
if (classLoader == clazz.getClassLoader()) {
throw new LinkageError("loader " + classLoader + " attempted duplicate class definition for " + clazz.getName() + " defined by " + clazz.getClassLoader());
} else {
throw VMError.unsupportedFeature("A predefined class can be loaded (defined) at runtime only once by a single class loader. " +
"Hierarchies of class loaders and distinct sets of classes are not supported. Class " + clazz.getName() + " has already " +
"been loaded by class loader: " + clazz.getClassLoader());
}
}
}

/**
* Load the class if it has not already been loaded. Returns {@code true} if the class has been
* loaded as a result of this call, {@code false} otherwise. Throws if an error occurred.
*/
public static boolean loadClassIfNotLoaded(ClassLoader classLoader, ProtectionDomain protectionDomain, Class<?> clazz) {
return singleton().loadClass0(classLoader, protectionDomain, clazz);
}

private Class<?> load(ClassLoader classLoader, ProtectionDomain protectionDomain, Class<?> clazz) {
private boolean loadClass0(ClassLoader classLoader, ProtectionDomain protectionDomain, Class<?> clazz) {
lock.lock();
try {
boolean alreadyLoaded = (loadedClassesByName.get(clazz.getName()) == clazz);
if (alreadyLoaded) {
if (classLoader == clazz.getClassLoader()) {
throw new LinkageError("loader " + classLoader + " attempted duplicate class definition for " + clazz.getName() + " defined by " + clazz.getClassLoader());
} else {
throw VMError.unsupportedFeature("A predefined class can be loaded (defined) at runtime only once by a single class loader. " +
"Hierarchies of class loaders and distinct sets of classes are not supported. Class " + clazz.getName() + " has already " +
"been loaded by class loader: " + clazz.getClassLoader());
}
return false;
}

throwIfUnresolvable(clazz.getSuperclass(), classLoader);
Expand All @@ -152,7 +177,7 @@ private Class<?> load(ClassLoader classLoader, ProtectionDomain protectionDomain
hub.setProtectionDomainAtRuntime(protectionDomain);
}
loadedClassesByName.put(clazz.getName(), clazz);
return clazz;
return true;
} finally {
lock.unlock();
}
Expand All @@ -163,24 +188,18 @@ public static void throwIfUnresolvable(Class<?> clazz, ClassLoader classLoader)
return;
}
DynamicHub hub = DynamicHub.fromClass(clazz);
if (!hub.isLoaded()) {
throwResolutionError(clazz.getName());
if (!hub.isLoaded() || !ClassUtil.isSameOrParentLoader(clazz.getClassLoader(), classLoader)) {
String name = clazz.getName();
// NoClassDefFoundError with ClassNotFoundException required by Java VM spec, 5.3
NoClassDefFoundError error = new NoClassDefFoundError(name.replace('.', '/'));
error.initCause(new ClassNotFoundException(name));
throw error;
}
if (!isSameOrParent(clazz.getClassLoader(), classLoader)) { // common case: same loader
throwResolutionError(clazz.getName());
}
}

private static void throwResolutionError(String name) {
// NoClassDefFoundError with ClassNotFoundException required by Java VM specification, 5.3
NoClassDefFoundError error = new NoClassDefFoundError(name.replace('.', '/'));
error.initCause(new ClassNotFoundException(name));
throw error;
}

static Class<?> getLoadedForNameOrNull(String name, ClassLoader classLoader) {
Class<?> clazz = singleton().getLoaded(name);
if (clazz == null || !isSameOrParent(clazz.getClassLoader(), classLoader)) {
if (clazz == null || !ClassUtil.isSameOrParentLoader(clazz.getClassLoader(), classLoader)) {
return null;
}
return clazz;
Expand All @@ -195,23 +214,14 @@ private Class<?> getLoaded(String name) {
}
}

private static boolean isSameOrParent(ClassLoader parent, ClassLoader child) {
if (parent == null) {
return true; // boot loader: any loader's parent
}
ClassLoader c = child;
do {
if (c == parent) { // common case
return true;
}
c = c.getParent();
} while (c != null);
return false;
}

@Platforms(Platform.HOSTED_ONLY.class)
public static class TestingBackdoor {
public static UnmodifiableEconomicMap<String, Class<?>> getPredefinedClasses() {
return singleton().predefinedClassesByHash;
public static Set<Class<?>> getConfigurationPredefinedClasses() {
Set<Class<?>> set = new HashSet<>();
for (Class<?> clazz : singleton().predefinedClassesByHash.getValues()) {
set.add(clazz);
}
return set; // excludes internal classes such as proxy classes
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,8 @@ public interface DynamicProxyRegistry {
@Platforms(Platform.HOSTED_ONLY.class)
void addProxyClass(Class<?>... interfaces);

Class<?> getProxyClass(Class<?>... interfaces);
Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces);

boolean isProxyClass(Class<?> clazz);

@SuppressWarnings("deprecation")
@Platforms(Platform.HOSTED_ONLY.class)
static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces) {
return java.lang.reflect.Proxy.getProxyClass(loader, interfaces);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@
import com.oracle.svm.core.annotate.Substitute;
import com.oracle.svm.core.annotate.TargetClass;
import com.oracle.svm.core.annotate.TargetElement;
import com.oracle.svm.core.jdk.JDK8OrEarlier;
import com.oracle.svm.core.jdk.JDK11OrLater;
import com.oracle.svm.core.jdk.JDK8OrEarlier;

@TargetClass(java.lang.reflect.Proxy.class)
final class Target_java_lang_reflect_Proxy {
Expand All @@ -51,7 +51,7 @@ final class Target_java_lang_reflect_Proxy {
@Substitute
@TargetElement(onlyWith = JDK8OrEarlier.class) //
private static Class<?> getProxyClass0(@SuppressWarnings("unused") ClassLoader loader, Class<?>... interfaces) {
return ImageSingletons.lookup(DynamicProxyRegistry.class).getProxyClass(interfaces);
return ImageSingletons.lookup(DynamicProxyRegistry.class).getProxyClass(loader, interfaces);
}

/** We have our own proxy cache so mark the original one as deleted. */
Expand All @@ -63,7 +63,7 @@ private static Class<?> getProxyClass0(@SuppressWarnings("unused") ClassLoader l
@TargetElement(onlyWith = JDK11OrLater.class)
@SuppressWarnings("unused")
private static Constructor<?> getProxyConstructor(Class<?> caller, ClassLoader loader, Class<?>... interfaces) {
final Class<?> cl = ImageSingletons.lookup(DynamicProxyRegistry.class).getProxyClass(interfaces);
final Class<?> cl = ImageSingletons.lookup(DynamicProxyRegistry.class).getProxyClass(loader, interfaces);
try {
final Constructor<?> cons = cl.getConstructor(InvocationHandler.class);
if (!Modifier.isPublic(cl.getModifiers())) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ public class ClassPredefinitionFeature implements Feature {
public void afterRegistration(AfterRegistrationAccess arg) {
ImageSingletons.add(PredefinedClassesSupport.class, new PredefinedClassesSupport());

/*
* NOTE: loading the class predefinition configuration should be done as early as possible
* so that their classes are already known for other configuration (reflection, proxies).
*/
AfterRegistrationAccessImpl access = (AfterRegistrationAccessImpl) arg;
PredefinedClassesRegistry registry = new PredefinedClassesRegistryImpl();
ImageSingletons.add(PredefinedClassesRegistry.class, registry);
Expand Down
Loading

0 comments on commit d72e67b

Please sign in to comment.