-
-
Notifications
You must be signed in to change notification settings - Fork 5
TransformerTypes
ClassTransform has multiple types of transformers that can be used to inject code into classes.
The following transformer types are available:
ClassTransform also offers a special transformer type which can be used to transform CTransformer
before they are applied:
The IBytecodeTransformer
is the most basic transformer type. It implements the byte[] transform(final String className, final byte[] bytecode, final boolean calculateStackMapFrames)
method which is called for every class that is passed to the TransformerManager
.
The first parameter (String
) is the name of the class (e.g. net.lenni0451.example.ExampleClass
).
The second parameter (byte[]
) is the bytecode of the class.
The third parameter (boolean
) is true
if the stack map frames should be calculated by the transformer. Ignoring this parameter can lead to a stack overflow!
The return value is the transformed bytecode of the class. If the bytecode should not be changed null
should be returned.
Example:
public class ExampleTransformer implements IRawTransformer {
@Override
public byte[] transform(final String className, final byte[] bytecode, final boolean calculateStackMapFrames) {
if (className.equals("net.lenni0451.example.ExampleClass")) {
//Do something with the bytecode
return bytecode;
}
return null;
}
}
The IRawTransformer
only receives the ClassNode
of the target class chosen when registering the transformer. It allows for more powerful transformations but requires knowledge of Java bytecode and the ASM library.
The ClassNode transform(final TransformerManager transformerManager, final ClassNode transformedClass)
method is implemented by this transformer type.
The first parameter (TransformerManager
) is the TransformerManager
that is currently applying the transformers.
The second parameter (ClassNode
) is the ClassNode
of the target class.
The return value is the transformed ClassNode
of the target class. If the ClassNode
should not be changed it should be returned.
Example:
public class ExampleTransformer implements IRawTransformer {
@Override
public ClassNode transform(final TransformerManager transformerManager, final ClassNode transformedClass) {
//Do something with the ClassNode
return transformedClass;
}
}
The CTransformer
is the most used transformer type and does not require an interface to be implemented.
It is a class that is annotated with the @CTransformer
annotation and has as many methods as required to achieve the desired result.
This transformer type is the easiest to use but also the most limited one.
The methods can be annotated with any supported annotation (see Annotations).
The method arguments and return type are dependent on the annotation that is used.
Example:
@CTransformer(ExampleClass.class)
public class ExampleTransformer {
@Inject(method = "exampleMethod", target = @CTarget("HEAD")) //Example annotation
public void injectTop(String arg1, int arg2) {
//Do something at the top of the method
}
}
The IPostTransformer
is less a transformer and more a callback that is called after all other transformers have been applied. It only receives the bytecode if at least one transformer has changed the bytecode of the class.
This transformer type is useful for dumping and analyzing the bytecode of a class after all transformations have been applied.
The void transform(final String className, final byte[] bytecode)
method is implemented by this transformer type.
The first parameter (String
) is the name of the class (e.g. net.lenni0451.example.ExampleClass
).
The second parameter (byte[]
) is the bytecode of the class.
Example:
public class ExampleTransformer implements IPostTransformer {
@Override
public void transform(final String className, final byte[] bytecode) {
if (className.equals("net.lenni0451.example.ExampleClass")) {
//Do something with the bytecode
}
}
}
The IAnnotationHandlerPreprocessor
is a special transformer type that is used to transform CTransformer
s before they are applied.
It is used in the MixinsTranslator
submodule to translate Mixins annotations to ClassTransform annotations.
This transformer type can be used to implement custom annotation handlers or to modify the CTransformer
class before it is parsed by the annotation handlers.
The void process(final ClassNode node)
method is implemented by this transformer type.
The parameter (ClassNode
) is the ClassNode
of the CTransformer
class.
There is also an optional ClassNode replace(final ClassNode node)
method that can be implemented to replace the ClassNode
of the CTransformer
s class.
Example:
public class ExampleTransformer implements IAnnotationHandlerPreprocessor {
@Override
public void process(final ClassNode node) {
//Do something with the ClassNode
}
//The replace method is optional
//It was added in ClassTransform 1.10.1 and made the process method redundant
//The process method is still required to be implemented for backwards compatibility and still works as expected
@Override
public ClassNode replace(final ClassNode node) {
//Do something with the ClassNode
return node;
}
}
The IAnnotationCoprocessor
is a special transformer type that is used to transform CTransformer
methods before and after they are applied.
It can be used to modify the transformer method before the annotation handler parses it and after the annotation handler applies it.
It is used for the CLocalVariable
and CShared
annotations to add the required local variables to the target method.
The IAnnotationCoprocessor
is the only transformer type that has a new instance created for every transformer run because it is required to store data about the transformer method in the instance.
Three methods have to be implemented by this transformer type:
MethodNode preprocess(final TransformerManager transformerManager, final ClassNode transformedClass, final MethodNode transformedMethod, final ClassNode transformer, final MethodNode transformerMethod)
MethodNode transform(final TransformerManager transformerManager, final ClassNode transformedClass, final MethodNode transformedMethod, final ClassNode transformer, final MethodNode transformerMethod)
void postprocess(final TransformerManager transformerManager, final ClassNode transformedClass, final MethodNode transformedMethod, final List<MethodInsnNode> transformerMethodCalls, final ClassNode transformer, final MethodNode transformerMethod)
The preprocess
method is called before the annotation handler parses the transformer method. In the CSharedCoprocessor
this method is used to merge all parameters with the @CShared
annotation into one parameter Object[]
.
The transform
method is called after all annotation coprocessors have been applied in reverse order. In the CSharedCoprocessor
this method is used to remove the Object[]
parameter to allow the annotation handler to parse the method.
The postprocess
method is called after the annotation handler has applied the transformer. It also receives the list of MethodInsnNode
s that were added to the transformed method. In the CSharedCoprocessor
this method is used to add the Object[]
parameter back to the method and to create and pass the array of parameters to the MethodInsnNode
that were added to the target method.
Example:
public class ExampleTransformer implements IAnnotationCoprocessor {
@Override
public MethodNode preprocess(final TransformerManager transformerManager, final ClassNode transformedClass, final MethodNode transformedMethod, final ClassNode transformer, final MethodNode transformerMethod) {
//Do something with the transformerMethod
return transformerMethod;
}
@Override
public MethodNode transform(final TransformerManager transformerManager, final ClassNode transformedClass, final MethodNode transformedMethod, final ClassNode transformer, final MethodNode transformerMethod) {
//Do something with the transformerMethod
return transformerMethod;
}
@Override
public void postprocess(final TransformerManager transformerManager, final ClassNode transformedClass, final MethodNode transformedMethod, final List<MethodInsnNode> transformerMethodCalls, final ClassNode transformer, final MethodNode transformerMethod) {
//Do something with the transformerMethod
}
}
For better examples check out the CLocalVariableCoprocessor
and the CSharedCoprocessor
in the net.lenni0451.classtransform.transformer.coprocessor.impl
package here.