Skip to content

TransformerTypes

Lenni0451 edited this page Mar 17, 2024 · 4 revisions

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:

IBytecodeTransformer

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;
    }

}

IRawTransformer

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;
    }

}

CTransformer

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
    }

}

IPostTransformer

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
        }
    }

}

IAnnotationHandlerPreprocessor

The IAnnotationHandlerPreprocessor is a special transformer type that is used to transform CTransformers 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 CTransformers 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;
    }

}

IAnnotationCoprocessor

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 MethodInsnNodes 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.