-
Notifications
You must be signed in to change notification settings - Fork 0
Custom
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.
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).
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 the value.
The generated method looks something 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;
}
This one is like CustomGen
, but instead of inserting instructions after InvocationHandler#invoke
, this inserts before. Also generation of InvocationHandler#invoke
can be disabled.
This CustomHandlerGenerator
will multiply both 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();
}
}
The generated method looks something 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;
}
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();
}
}
The generated method looks something like:
public int calc(int a, int b) {
return a * b;
}
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.
The generated method looks something like:
Object result = arg0 * arg1;
if (result instanceof InvokeSuper) {
result = super.calc(arg0, arg1);
}
return (Integer) result;
Custom is the master guy here, with Custom
you can add fields using Custom.Property
, control whether methods should have their spec cached or not and provide CustomGen
s and CustomHandlerGenerator
s 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 Custom
s see this package.
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.
Directly invoke a Java 8 Function
for each invoked method.
Directly invoke to a target method.
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.
Delegates to evaluated instance of a lazy evaluator.
This uses specified static type to resolve methods.
Delegates invocations to a instance that can be changed. (A static base type is needed).
Delegates invocations to static methods of a class.
Delegates invocations methods of an instance.
Delegates invocations methods to different instances resolved based on methods.
Environment that provides additional information of generation and parameters to control instructions generation.
Parameter that defines whether generator should continue calling CustomHandlerGenerator
to generate additional instructions before InvocationHandler#invoke
.
Parameter that defines whether the generator should generate invocation of InvocationHandler#invoke
or not.