Skip to content

The patching system


Reflection is a powerful tool. It can be used to inject entities into nearly any registry and, even modify some behavior. But it can’t change code.

That’s where patching comes in. While you can’t add new methods or fields, you can modify existing bytecode, including methods and constructors.

Below is a simplified overview of what Bestium does, and why:

Byte Buddy is a runtime code generation library for Java. One of its most useful features is the ability to attach a Java agent in runtime. This lets us obtain an Instrumentation instance, which allows for redefining loaded classes.

For more on Java agents and instrumentation, see the java.instrument Javadoc.

With the Instrumentation instance, the jdk.internal.reflect package is opened from the java.base module to Bestium’s module. This is necessary in order to patch jdk.internal.reflect.Reflection later.

Here all patch implementations are collected and executed. The bytecode of modified classes is then redefined via the instrumentation API.

Patching jdk.internal.reflect.Reflection#filterFields
Section titled “Patching jdk.internal.reflect.Reflection#filterFields”

One of the very important patches is the patch to jdk.internal.reflect.Reflection#filterFields.

Since Java 7, the JDK restricts reflection access to certain fields. One of these filtered-out exceptions is all fields inside the ClassLoader class.

That creates a big issue, because to inject the PatchedClassLoader, we need to replace the ClassLoader.parent field of an existing class loader.

To allow this, the filterFields method is patched to return early and not filter any fields.

Now that Reflection is patched, the class loader can be injected to the hierarchy.

By default, the new loader is placed directly above Paper’s class loader. If Nova plugin is present, the class loader is placed above Nova’s own patched loader to keep compatibility.

Now that the patching is complete, the patch on jdk.internal.reflect.Reflection can be reverted.

This is important because some plugins (like Denizen supposedly) do not expect access to filtered-out fields. The original bytecode of Reflection is saved before patching and is restored and redefined.

Even after reverting the Reflection patch, access to ClassLoader.parent remains cached. The Class class internally uses a SoftReference field called reflectionData to cache reflection results.

To prevent this inconsistency, Bestium accesses Class.reflectionData using reflection and clears the reference.