Skip to content

CInject

Lenni0451 edited this page Mar 17, 2024 · 1 revision

The CInject annotation can be used to inject code at a specific position in a method.
The original code flow can be cancelled and other values can be returned.

Method signature

The method signature of the transformer method depends on the target method.
When injecting into a static method, the transformer method also needs to be static and vice versa.
The parameters of the transformer method can be the same as the target method, but can also be left out.
An optional InjectCallback can be added as the last parameter to cancel the original code flow and return a value.
The return type always has to be void.

//Injecting into a static method
@CInject(method = "method", ...)
public static void transform(final String arg1, final int arg2, final InjectionCallback ic)

//Injecting into a non-static method
@CInject(method = "method", ...)
public void transform(final int arg1, final InjectionCallback ic)

//Leaving out the parameters
//Example method: public void method(String arg1, int arg2) {
@CInject(method = "method", ...)
public void transform(final InjectionCallback ic)

//Leaving out the parameters and the callback
//Example method: public static void method(String arg1, int arg2) {
@CInject(method = "method", ...)
public static void transform()

When adding the parameters to the transformer method, the types of the parameters need to match the types of the target method.
Object can be used as a wildcard for any non-primitive type. This can be useful when parameter types are not accessible.
Changing the parameter values is not possible. If you need to change the values, you can use the CLocalVariable annotation.

Targets

The target field of the CInject annotation specifies the position in the method to inject into.
Check out the CTarget page for more information about the different targets.
Also, check out CSlice for more information about how slices work.

Cancelling the original code flow

To cancel the original code flow, the cancellable field of the CInject annotation needs to be set to true. A runtime exception will be thrown if the field is not set to true.
If the target method returns a value, a value needs to be passed to the callback to return.

//Cancelling a void method
ic.setCancelled(true);

//Cancelling a method with a return value
//setReturnValue also calls setCancelled(true)
ic.setReturnValue("return value");

Accessing the original return value

When using the RETURN, TAIL or THROW target, the original return value can be accessed using getReturnValue() or castReturnValue().

//Accessing the old return value
@CInject(method = "method", target = @CTarget("RETURN"), cancellable = true)
public void transform(final InjectionCallback ic) {
    String returnValue = ic.castReturnValue();
    //Do something with the return value
}

//Accessing the thrown exception
@CInject(method = "method", target = @CTarget("THROW"), cancellable = true)
public void transform(final InjectionCallback ic) {
    Throwable exception = ic.castReturnValue();
    //Do something with the exception
}

When using the THROW target, be aware that the exception can not be set using setReturnValue().
Doing so will try to return the exception, which will result in a runtime exception.

//This will not work
ic.setReturnValue(new Exception());

//This will work
throw new Exception();

Targeting multiple methods

The method field can be an array of strings to target multiple methods.
The transformer method needs to be compatible with all targeted methods.
An easy way to do this is to leave out the parameters.

@CInject(method = {"method1", "method2"}, ...)
public void transform(final InjectionCallback ic)

Specifying multiple targets

The target field can be an array of CTarget annotations to specify multiple targets.

@CInject(method = "method", target = {@CTarget("HEAD"), @CTarget("RETURN")}, ...)
public void transform(final InjectionCallback ic)

Example

Original method:

public String method(final String arg) {
    return "hello " + arg;
}

Injecting at HEAD

Transformer method:

@CInject(method = "method", target = @CTarget("HEAD"), cancellable = true)
public void transform(final String arg, final InjectionCallback ic) {
    if (arg.equals("hello")) { //Check if the first argument is "hello"
        ic.setReturnValue("world"); //Return "world" instead of the original return value
    }
}

Injected code:

public String method(final String arg) {
    InjectionCallback ic = new InjectionCallback(true /* cancellable */);
    transform(arg, ic);
    if (ic.isCancelled()) {
        return (String) ic.getReturnValue();
    }
    return "hello " + arg;
}

Injecting at RETURN

Transformer method:

@CInject(method = "method", target = @CTarget("RETURN"), cancellable = true)
public void transform(final String arg, final InjectionCallback ic) {
    String returnValue = ic.castReturnValue(); //Get the original return value
    ic.setReturnValue(returnValue + " world"); //Return the original return value + " world"
}

Injected code:

public String method(final String arg) {
    String returnValue = "hello " + arg;
    InjectionCallback ic = new InjectionCallback(true /* cancellable */, returnValue /* original return value */);
    transform(arg, ic);
    if (ic.isCancelled()) {
        return (String) ic.getReturnValue();
    }
    return returnValue;
}

Injecting without cancelling and arguments

Transformer method:

@CInject(method = "method", target = @CTarget("HEAD"))
public void transform() {
    System.out.println("Hello world");
}

Injected code:

public String method(final String arg) {
    transform();
    return "hello " + arg;
}