Skip to content
Jonathan edited this page Feb 3, 2018 · 10 revisions

KoresProxy provides ways to generate additional bytecode-level instructions to proxy methods, also provide way to generate entire body of proxy methods, which can result in methods with zero-overhead invocation.

CustomGen

The custom gen is used to add instructions after invocation of invoke method of InvocationHandler, an example of use case is to invoke super method of current proxy class, which is not possible thorugh InvocationHandler (is possible through MethodHandle, but is not easy to achieve and can result in access exceptions).

Let's try

The following custom gen takes the result of InvocatioHandler#invoke, multiplies it by 2 and store in result variable, which is then returned:

@Test
public void readmeCustoms() {
    InvocationHandler myHandler = (instance, methodInfo, args, proxyData) -> {
        if (methodInfo.getName().equals("hash"))
            return 2;
        else
            return InvokeSuper.INSTANCE;
    };

    Data data = KoresProxy.newProxyInstance(new Class[0], new Object[0], builder ->
            builder.classLoader(ReadmeCustomGen.class.getClassLoader())
                    .addCustomGenerator(InvokeSuper.class)
                    .addCustomGenerator(ReadmeCustomGen.class)
                    .addInterface(Data.class)
                    .invocationHandler(myHandler)
    );

    Assert.assertEquals(4, data.hash());
}

public interface Data {
    int hash();
}

public static class ReadmeCustomGen implements CustomGen {

    @NotNull
    @Override
    public Instructions gen(@NotNull Method target,
                            @NotNull MethodDeclaration methodDeclaration,
                            @Nullable VariableDeclaration returnVariable) {
        // Inserts: result = result * 2; after InvocationHandler.invoke
        if (returnVariable != null && target.getName().equals("hash")) {

            VariableAccess variableAccess = Factories.accessVariable(returnVariable);
            Cast cast = Factories.cast(returnVariable.getType(), Integer.TYPE, variableAccess);
            Operate operate = Factories.operate(cast, Operators.MULTIPLY, Literals.INT(2));
            Cast result = Factories.cast(Integer.TYPE, Object.class, operate);

            return Instructions.fromPart(
                    Factories.setVariableValue(returnVariable, result)
            );
        }

        return Instructions.empty();
    }
}

Note that you need to cast returnVariable value to Integer.TYPE before applying the operation, this is because InvocationHandler#invoke returns an Object, and you are manipulating this object, also you need to cast the operation result before assigning returnVariable to it.

Generated method

The generated method looks like:

public int hash() {
    Object result = this.invocationHandler.invoke(this, METHOD5, new Object[0], this.proxyData);
    if (result instanceof InvokeSuper) {
        result = new Integer(super.hash());
    }
    
    result = ((Integer)result) * 2;
    
    return (Integer)result;
}

CustomHandlerGenerator

This one is like CustomGen, but instead of inserting instructions after InvocationHandler#invoke, this inserts before. Also this generator can disable generation of InvocationHandler#invoke (which disables CustomGens too).

Let's try

Modifying parameters

This CustomHandlerGenerator generates multiplication parameters a and b by 10, and then assign the results:

@Test
public void readmeCustomHandlerGenerators() {
    InvocationHandler myHandler = (instance, methodInfo, args, proxyData) -> {
        if (methodInfo.getName().equals("calc"))
            return (Integer) args[0] + (Integer) args[1];
        else
            return InvokeSuper.INSTANCE;
    };

    Data data = KoresProxy.newProxyInstance(new Class[0], new Object[0], builder ->
            builder.classLoader(ReadmeCustomHandlerGenerators.class.getClassLoader())
                    .addCustomGenerator(InvokeSuper.class)
                    .addCustomHandlerGenerator(ReadmeCustomHGenerator.class)
                    .addInterface(Data.class)
                    .invocationHandler(myHandler)
    );

    Assert.assertEquals(((5 * 10) + (5 * 10)), data.calc(5, 5));
}

public interface Data {
    int calc(int a, int b);
}

public static class ReadmeCustomHGenerator implements CustomHandlerGenerator {

    @NotNull
    @Override
    public Instructions handle(@NotNull Method target, @NotNull MethodDeclaration methodDeclaration, @NotNull GenEnv env) {
        if (target.getName().equals("calc")) {
            KoresParameter p1 = methodDeclaration.getParameters().get(0);
            KoresParameter p2 = methodDeclaration.getParameters().get(1);
            VariableRef v1 = new VariableRef(p1.getType(), p1.getName());
            VariableRef v2 = new VariableRef(p2.getType(), p2.getName());

            VariableAccess access1 = Factories.accessVariable(v1);
            VariableAccess access2 = Factories.accessVariable(v2);

            return Instructions.fromVarArgs(
                    Factories.setVariableValue(v1, Factories.operate(access1, Operators.MULTIPLY, Literals.INT(10))),
                    Factories.setVariableValue(v2, Factories.operate(access2, Operators.MULTIPLY, Literals.INT(10)))
            );

        }

        return Instructions.empty();
    }
}
Generated method

The generated method looks like:

public int calc(int a, int b) {
    a *= 10;
    b *= 10;
    Object result = this.invocationHandler.invoke(this, Method5, new Object[]{a, b}, this.proxyData);
    
    if (result instanceof InvokeSuper) {
        result = super.calc(arg0, arg1);
    }
    
    return (Integer) result;
}

Entire method

Here we generate entire method body by disabling generation of InvocationHandler#invoke, this will simply return multiplication of a by b:

@Test
public void readmeCustomHandlerGenerators2() {
    InvocationHandler myHandler = (instance, methodInfo, args, proxyData) -> {
        if (methodInfo.getName().equals("calc"))
            return (Integer) args[0] + (Integer) args[1];
        else
            return InvokeSuper.INSTANCE;
    };

    Data data = KoresProxy.newProxyInstance(new Class[0], new Object[0], builder ->
            builder.classLoader(ReadmeCustomHandlerGenerators2.class.getClassLoader())
                    .addCustomGenerator(InvokeSuper.class)
                    .addCustomHandlerGenerator(ReadmeCustomHGenerator.class)
                    .addInterface(Data.class)
                    .invocationHandler(myHandler)
    );

    Assert.assertEquals(((5 * 5)), data.calc(5, 5));
}

public interface Data {
    int calc(int a, int b);
}

public static class ReadmeCustomHGenerator implements CustomHandlerGenerator {

    @NotNull
    @Override
    public Instructions handle(@NotNull Method target, @NotNull MethodDeclaration methodDeclaration, @NotNull GenEnv env) {
        if (target.getName().equals("calc")) {
            env.setMayProceed(false);
            env.setInvokeHandler(false);

            KoresParameter p1 = methodDeclaration.getParameters().get(0);
            KoresParameter p2 = methodDeclaration.getParameters().get(1);
            VariableRef v1 = new VariableRef(p1.getType(), p1.getName());
            VariableRef v2 = new VariableRef(p2.getType(), p2.getName());

            VariableAccess access1 = Factories.accessVariable(v1);
            VariableAccess access2 = Factories.accessVariable(v2);


            return Instructions.fromPart(
                    Factories.returnValue(Integer.TYPE, Factories.operate(access1, Operators.MULTIPLY, access2))
            );

        }

        return Instructions.empty();
    }
}
Generated method

The generated method looks like:

public int calc(int a, int b) {
    return a * b;
}

Entire method supporting CustomGen

To support CustomGen while generating entire method body, you should call GenEnv#callCustomGenerators, example:

@Test
public void readmeCustomHandlerGenerators2() {
    InvocationHandler myHandler = (instance, methodInfo, args, proxyData) -> {
        if (methodInfo.getName().equals("calc"))
            return (Integer) args[0] + (Integer) args[1];
        else
            return InvokeSuper.INSTANCE;
    };

    Data data = KoresProxy.newProxyInstance(new Class[0], new Object[0], builder ->
            builder.classLoader(ReadmeCustomHandlerGenerators3.class.getClassLoader())
                    .addCustomGenerator(InvokeSuper.class)
                    .addCustomHandlerGenerator(ReadmeCustomHGenerator.class)
                    .addInterface(Data.class)
                    .invocationHandler(myHandler)
    );

    Assert.assertEquals(((5 * 5)), data.calc(5, 5));
}

public interface Data {
    int calc(int a, int b);
}

public static class ReadmeCustomHGenerator implements CustomHandlerGenerator {

    @NotNull
    @Override
    public Instructions handle(@NotNull Method target, @NotNull MethodDeclaration methodDeclaration, @NotNull GenEnv env) {
        if (target.getName().equals("calc")) {
            env.setMayProceed(false);
            env.setInvokeHandler(false);

            KoresParameter p1 = methodDeclaration.getParameters().get(0);
            KoresParameter p2 = methodDeclaration.getParameters().get(1);
            VariableRef v1 = new VariableRef(p1.getType(), p1.getName());
            VariableRef v2 = new VariableRef(p2.getType(), p2.getName());

            VariableAccess access1 = Factories.accessVariable(v1);
            VariableAccess access2 = Factories.accessVariable(v2);

            MutableInstructions instructions = MutableInstructions.create();
            VariableDeclaration result = VariableFactory.variable(Object.class, "result",
                    Factories.cast(Integer.TYPE, Object.class,
                            Factories.operate(access1, Operators.MULTIPLY, access2)));

            instructions.add(result);

            env.callCustomGenerators(result, instructions);

            VariableAccess access = Factories.accessVariable(result);

            instructions.add(Factories.returnValue(Integer.TYPE, Factories.cast(Object.class, Integer.TYPE, access)));

            return instructions;

        }

        return Instructions.empty();
    }
}

Note: I know, this is the worst use case for this feature, but it is only an example of how to support CustomGen while generating entire method body.

Generated method

The generated method looks like:

Object result = arg0 * arg1;

if (result instanceof InvokeSuper) {
    result = super.calc(arg0, arg1);
}

return (Integer) result;

Custom

Custom is the master guy here, with Custom you can add fields using Custom.Property, control which methods should have their spec cached or not and provide CustomGens and CustomHandlerGenerators to provide more functionalities.

Custom is commonly used to generate direct invocations of methods based on user input (without leaving all the hard work of writing the generator to the user).

Note: The field name is not the same name as the name provided to Property, to get the field name of property use Util#getAdditionalPropertyFieldName with the property name or Custom.Property#getFieldName.

If you want examples of Customs see this package.

Builtin customs

INVOKE_SUPER | CustomGen

Builtin custom gen that allows InvocationHandler to invoke super method. To invoke a super method you need to return InvokeSuper.INSTANCE or InvokeSuper.INVOKE_SUPER as InvocationHandler#invoke result.

DirectToFunction / Custom

Directly invoke a Java 8 Function for each invoked method.

DirectToResolveMethod / Custom

Directly invoke to a target method.

DynamicLazyInstance / Custom

Delegates to evaluated instance of a lazy evaluator.

This uses a dynamic bootstrap to resolve type of evaluated instance without evaluating the object. This type is used to resolve method to invoke.

LazyInstance / Custom

Delegates to evaluated instance of a lazy evaluator.

This uses specified static type to resolve methods.

MutableInstance / Custom

Delegates invocations to a instance that can be changed. (A static base type is needed).

DirectInvocationCustom.Static / Custom

Delegates invocations to static methods of a class.

DirectInvocationCustom.Instance / Custom

Delegates invocations methods of an instance.

DirectInvocationCustom.MultiInstanceResolved / Custom

Delegates invocations methods to different instances resolved based on methods.

GenEnv

Environment that provides additional information of generation and parameters to control instructions generation.

mayProceed

Parameter that defines whether generator should continue calling CustomHandlerGenerator to generate additional instructions before InvocationHandler#invoke.

invokeHandler

Parameter that defines whether the generator should generate invocation of InvocationHandler#invoke or not.