/*
* Copyright 2014 - 2020 Rafael Winterhalter
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.bytebuddy.dynamic;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import net.bytebuddy.build.HashCodeAndEqualsPlugin;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.loading.ClassInjector;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import net.bytebuddy.implementation.Implementation;
import net.bytebuddy.implementation.LoadedTypeInitializer;
import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
import net.bytebuddy.implementation.bytecode.Removal;
import net.bytebuddy.implementation.bytecode.StackManipulation;
import net.bytebuddy.implementation.bytecode.collection.ArrayFactory;
import net.bytebuddy.implementation.bytecode.constant.ClassConstant;
import net.bytebuddy.implementation.bytecode.constant.IntegerConstant;
import net.bytebuddy.implementation.bytecode.constant.NullConstant;
import net.bytebuddy.implementation.bytecode.constant.TextConstant;
import net.bytebuddy.implementation.bytecode.member.MethodInvocation;
import org.objectweb.asm.MethodVisitor;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Arrays;
import java.util.Collections;
The Nexus accessor is creating a VM-global singleton Nexus
such that it can be seen by all class loaders of a virtual machine. Furthermore, it provides an API to access this global instance. /**
* The Nexus accessor is creating a VM-global singleton {@link Nexus} such that it can be seen by all class loaders of
* a virtual machine. Furthermore, it provides an API to access this global instance.
*/
@HashCodeAndEqualsPlugin.Enhance
public class NexusAccessor {
The dispatcher to use.
/**
* The dispatcher to use.
*/
private static final Dispatcher DISPATCHER = AccessController.doPrivileged(Dispatcher.CreationAction.INSTANCE);
An type-safe constant for a non-operational reference queue.
/**
* An type-safe constant for a non-operational reference queue.
*/
private static final ReferenceQueue<ClassLoader> NO_QUEUE = null;
The reference queue that is notified upon a GC eligible Nexus
entry or null
if no such queue should be notified. /**
* The reference queue that is notified upon a GC eligible {@link Nexus} entry or {@code null} if no such queue should be notified.
*/
@HashCodeAndEqualsPlugin.ValueHandling(HashCodeAndEqualsPlugin.ValueHandling.Sort.REVERSE_NULLABILITY)
private final ReferenceQueue<? super ClassLoader> referenceQueue;
Creates a new accessor for the Nexus
without any active management of stale references within a nexus. /**
* Creates a new accessor for the {@link Nexus} without any active management of stale references within a nexus.
*/
public NexusAccessor() {
this(NO_QUEUE);
}
Creates a new accessor for a Nexus
where any GC eligible are enqueued to the supplied reference queue. Any such enqueued reference can be explicitly removed from the nexus via the clean(Reference<? extends ClassLoader>)
method. Nexus entries can become stale if a class loader is garbage collected after a class was loaded but before a class was initialized. Params: - referenceQueue – The reference queue onto which stale references should be enqueued or
null
if no reference queue should be notified.
/**
* Creates a new accessor for a {@link Nexus} where any GC eligible are enqueued to the supplied reference queue. Any such enqueued
* reference can be explicitly removed from the nexus via the {@link NexusAccessor#clean(Reference)} method. Nexus entries can
* become stale if a class loader is garbage collected after a class was loaded but before a class was initialized.
*
* @param referenceQueue The reference queue onto which stale references should be enqueued or {@code null} if no reference queue
* should be notified.
*/
public NexusAccessor(ReferenceQueue<? super ClassLoader> referenceQueue) {
this.referenceQueue = referenceQueue;
}
Checks if this NexusAccessor
is capable of registering loaded type initializers. Returns: true
if this accessor is alive.
/**
* Checks if this {@link NexusAccessor} is capable of registering loaded type initializers.
*
* @return {@code true} if this accessor is alive.
*/
public static boolean isAlive() {
return DISPATCHER.isAlive();
}
Removes a stale entries that are registered in the Nexus
. Entries can become stale if a class is loaded but never initialized prior to its garbage collection. As all class loaders within a nexus are only referenced weakly, such class loaders are always garbage collected. However, the initialization data stored by Byte Buddy does not become eligible which is why it needs to be cleaned explicitly. Params: - reference – The reference to remove. References are collected via a reference queue that is supplied to the
NexusAccessor
.
/**
* Removes a stale entries that are registered in the {@link Nexus}. Entries can become stale if a class is loaded but never initialized
* prior to its garbage collection. As all class loaders within a nexus are only referenced weakly, such class loaders are always garbage
* collected. However, the initialization data stored by Byte Buddy does not become eligible which is why it needs to be cleaned explicitly.
*
* @param reference The reference to remove. References are collected via a reference queue that is supplied to the {@link NexusAccessor}.
*/
public static void clean(Reference<? extends ClassLoader> reference) {
DISPATCHER.clean(reference);
}
Registers a loaded type initializer in Byte Buddy's Nexus
which is injected into the system class loader. Params:
/**
* Registers a loaded type initializer in Byte Buddy's {@link Nexus} which is injected into the system class loader.
*
* @param name The binary name of the class.
* @param classLoader The class's class loader.
* @param identification The id used for identifying the loaded type initializer that was added to the {@link Nexus}.
* @param loadedTypeInitializer The loaded type initializer to make available via the {@link Nexus}.
*/
public void register(String name, ClassLoader classLoader, int identification, LoadedTypeInitializer loadedTypeInitializer) {
if (loadedTypeInitializer.isAlive()) {
DISPATCHER.register(name, classLoader, referenceQueue, identification, loadedTypeInitializer);
}
}
An initialization appender that looks up a loaded type initializer from Byte Buddy's Nexus
. /**
* An initialization appender that looks up a loaded type initializer from Byte Buddy's {@link Nexus}.
*/
@HashCodeAndEqualsPlugin.Enhance
public static class InitializationAppender implements ByteCodeAppender {
The id used for identifying the loaded type initializer that was added to the Nexus
. /**
* The id used for identifying the loaded type initializer that was added to the {@link Nexus}.
*/
private final int identification;
Creates a new initialization appender.
Params: - identification – The id used for identifying the loaded type initializer that was added to the
Nexus
.
/**
* Creates a new initialization appender.
*
* @param identification The id used for identifying the loaded type initializer that was added to the {@link Nexus}.
*/
public InitializationAppender(int identification) {
this.identification = identification;
}
{@inheritDoc}
/**
* {@inheritDoc}
*/
public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext, MethodDescription instrumentedMethod) {
try {
return new ByteCodeAppender.Simple(new StackManipulation.Compound(
MethodInvocation.invoke(new MethodDescription.ForLoadedMethod(ClassLoader.class.getMethod("getSystemClassLoader"))),
new TextConstant(Nexus.class.getName()),
MethodInvocation.invoke(new MethodDescription.ForLoadedMethod(ClassLoader.class.getMethod("loadClass", String.class))),
new TextConstant("initialize"),
ArrayFactory.forType(TypeDescription.Generic.CLASS)
.withValues(Arrays.asList(
ClassConstant.of(TypeDescription.CLASS),
ClassConstant.of(TypeDescription.ForLoadedType.of(int.class)))),
MethodInvocation.invoke(new MethodDescription.ForLoadedMethod(Class.class.getMethod("getMethod", String.class, Class[].class))),
NullConstant.INSTANCE,
ArrayFactory.forType(TypeDescription.Generic.OBJECT)
.withValues(Arrays.asList(
ClassConstant.of(instrumentedMethod.getDeclaringType().asErasure()),
new StackManipulation.Compound(
IntegerConstant.forValue(identification),
MethodInvocation.invoke(new MethodDescription.ForLoadedMethod(Integer.class.getMethod("valueOf", int.class)))))),
MethodInvocation.invoke(new MethodDescription.ForLoadedMethod(Method.class.getMethod("invoke", Object.class, Object[].class))),
Removal.SINGLE
)).apply(methodVisitor, implementationContext, instrumentedMethod);
} catch (NoSuchMethodException exception) {
throw new IllegalStateException("Cannot locate method", exception);
}
}
}
A dispatcher for registering type initializers in the Nexus
. /**
* A dispatcher for registering type initializers in the {@link Nexus}.
*/
protected interface Dispatcher {
Returns true
if this dispatcher is alive. Returns: true
if this dispatcher is alive.
/**
* Returns {@code true} if this dispatcher is alive.
*
* @return {@code true} if this dispatcher is alive.
*/
boolean isAlive();
Cleans any dead entries of the system class loader's Nexus
. Params: - reference – The reference to remove.
/**
* Cleans any dead entries of the system class loader's {@link Nexus}.
*
* @param reference The reference to remove.
*/
void clean(Reference<? extends ClassLoader> reference);
Registers a type initializer with the system class loader's nexus.
Params: - name – The name of a type for which a loaded type initializer is registered.
- classLoader – The class loader for which a loaded type initializer is registered.
- referenceQueue – A reference queue to notify about stale nexus entries or
null
if no queue should be referenced. - identification – An identification for the initializer to run.
- loadedTypeInitializer – The loaded type initializer to be registered.
/**
* Registers a type initializer with the system class loader's nexus.
*
* @param name The name of a type for which a loaded type initializer is registered.
* @param classLoader The class loader for which a loaded type initializer is registered.
* @param referenceQueue A reference queue to notify about stale nexus entries or {@code null} if no queue should be referenced.
* @param identification An identification for the initializer to run.
* @param loadedTypeInitializer The loaded type initializer to be registered.
*/
void register(String name,
ClassLoader classLoader,
ReferenceQueue<? super ClassLoader> referenceQueue,
int identification,
LoadedTypeInitializer loadedTypeInitializer);
Creates a new dispatcher for accessing a Nexus
. /**
* Creates a new dispatcher for accessing a {@link Nexus}.
*/
enum CreationAction implements PrivilegedAction<Dispatcher> {
The singleton instance.
/**
* The singleton instance.
*/
INSTANCE;
{@inheritDoc}
/**
* {@inheritDoc}
*/
@SuppressFBWarnings(value = "REC_CATCH_EXCEPTION", justification = "Exception should not be rethrown but trigger a fallback")
public Dispatcher run() {
if (Boolean.getBoolean(Nexus.PROPERTY)) {
return new Unavailable("Nexus injection was explicitly disabled");
} else {
try {
Class<?> nexusType = new ClassInjector.UsingReflection(ClassLoader.getSystemClassLoader(), ClassLoadingStrategy.NO_PROTECTION_DOMAIN)
.inject(Collections.singletonMap(TypeDescription.ForLoadedType.of(Nexus.class), ClassFileLocator.ForClassLoader.read(Nexus.class)))
.get(TypeDescription.ForLoadedType.of(Nexus.class));
return new Dispatcher.Available(nexusType.getMethod("register", String.class, ClassLoader.class, ReferenceQueue.class, int.class, Object.class),
nexusType.getMethod("clean", Reference.class));
} catch (Exception exception) {
try {
Class<?> nexusType = ClassLoader.getSystemClassLoader().loadClass(Nexus.class.getName());
return new Dispatcher.Available(nexusType.getMethod("register", String.class, ClassLoader.class, ReferenceQueue.class, int.class, Object.class),
nexusType.getMethod("clean", Reference.class));
} catch (Exception ignored) {
return new Dispatcher.Unavailable(exception.toString());
}
}
}
}
}
An enabled dispatcher for registering a type initializer in a Nexus
. /**
* An enabled dispatcher for registering a type initializer in a {@link Nexus}.
*/
@HashCodeAndEqualsPlugin.Enhance
class Available implements Dispatcher {
Indicates that a static method is invoked by reflection.
/**
* Indicates that a static method is invoked by reflection.
*/
private static final Object STATIC_METHOD = null;
/**
* The {@link Nexus#register(String, ClassLoader, ReferenceQueue, int, Object)} method.
*/
private final Method register;
The Nexus.clean(Reference<? super ClassLoader>)
method. /**
* The {@link Nexus#clean(Reference)} method.
*/
private final Method clean;
Creates a new dispatcher.
Params: - register – The
Nexus.register(String, ClassLoader, ReferenceQueue<? super ClassLoader>, int, Object)
method. - clean – The
Nexus.clean(Reference<? super ClassLoader>)
method.
/**
* Creates a new dispatcher.
*
* @param register The {@link Nexus#register(String, ClassLoader, ReferenceQueue, int, Object)} method.
* @param clean The {@link Nexus#clean(Reference)} method.
*/
protected Available(Method register, Method clean) {
this.register = register;
this.clean = clean;
}
{@inheritDoc}
/**
* {@inheritDoc}
*/
public boolean isAlive() {
return true;
}
{@inheritDoc}
/**
* {@inheritDoc}
*/
public void clean(Reference<? extends ClassLoader> reference) {
try {
clean.invoke(STATIC_METHOD, reference);
} catch (IllegalAccessException exception) {
throw new IllegalStateException("Cannot access: " + clean, exception);
} catch (InvocationTargetException exception) {
throw new IllegalStateException("Cannot invoke: " + clean, exception.getCause());
}
}
{@inheritDoc}
/**
* {@inheritDoc}
*/
public void register(String name,
ClassLoader classLoader,
ReferenceQueue<? super ClassLoader> referenceQueue,
int identification,
LoadedTypeInitializer loadedTypeInitializer) {
try {
register.invoke(STATIC_METHOD, name, classLoader, referenceQueue, identification, loadedTypeInitializer);
} catch (IllegalAccessException exception) {
throw new IllegalStateException("Cannot access: " + register, exception);
} catch (InvocationTargetException exception) {
throw new IllegalStateException("Cannot invoke: " + register, exception.getCause());
}
}
}
A disabled dispatcher where a Nexus
is not available. /**
* A disabled dispatcher where a {@link Nexus} is not available.
*/
@HashCodeAndEqualsPlugin.Enhance
class Unavailable implements Dispatcher {
The reason for the dispatcher being unavailable.
/**
* The reason for the dispatcher being unavailable.
*/
private final String message;
Creates a new unavailable dispatcher.
Params: - message – The reason for the dispatcher being unavailable.
/**
* Creates a new unavailable dispatcher.
*
* @param message The reason for the dispatcher being unavailable.
*/
protected Unavailable(String message) {
this.message = message;
}
{@inheritDoc}
/**
* {@inheritDoc}
*/
public boolean isAlive() {
return false;
}
{@inheritDoc}
/**
* {@inheritDoc}
*/
public void clean(Reference<? extends ClassLoader> reference) {
throw new UnsupportedOperationException("Could not initialize Nexus accessor: " + message);
}
{@inheritDoc}
/**
* {@inheritDoc}
*/
public void register(String name,
ClassLoader classLoader,
ReferenceQueue<? super ClassLoader> referenceQueue,
int identification,
LoadedTypeInitializer loadedTypeInitializer) {
throw new UnsupportedOperationException("Could not initialize Nexus accessor: " + message);
}
}
}
}