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:
1. Attaching a Byte Buddy agent
Section titled “1. Attaching a Byte Buddy agent”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.
2. Redefining the java.base
module
Section titled “2. Redefining the java.base module”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.
3. Patching and redefining classes
Section titled “3. Patching and redefining classes”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.
4. Injecting the PatchedClassLoader
Section titled “4. Injecting the PatchedClassLoader”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.
5. Reverting the Reflection patch
Section titled “5. Reverting the Reflection patch”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.
6. Cleaning class loader reflection cache
Section titled “6. Cleaning class loader reflection cache”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.