/KoresProxy

Proxy generator written on top of Kores!

Primary LanguageJavaOtherNOASSERTION

KoresProxy

KoresProxy is a Proxy generator written on top of Kores.

Using KoresProxy

A basic Proxy example:
public class MyClass {
  public int getNumber() {
    return 10;
  }
}

MyClass instance = KoresProxy.newProxyInstance(this.getClass().getClassLoader(), MyClass.class, (instance0, method, args, proxyData) -> {
  if (method.getName().equals("getNumber"))
    return 5;

  return null;
});

Assert.assertEquals("getNumber", 5, instance.getNumber());
Delegate to another instance
public class MyClass {
  public int getNumber() {
    return 10;
  }
}

MyClass origin = new MyClass();
MyClass instance = KoresProxy.newProxyInstance(this.getClass().getClassLoader(), MyClass.class, (instance0, method, args, proxyData) -> {
  if (method.getName().equals("getNumber"))
    return 5;

  return method.resolveOrFail(origin.getClass()).bindTo(origin).invokeWithArguments(args);
});

Assert.assertEquals("getNumber", 5, instance.getNumber());
Assert.assertEquals("hash", origin.hashCode(), instance.hashCode());
Class with constructors
public class ClassWithConstructor {
 private final String name;

 public ClassWithConstructor(String name) {
   this.name = name;
 }

 public String getName() {
   return this.name;
 }
}

ClassWithConstructor cwcOrigin2 = new ClassWithConstructor("Origin 2");
ClassWithConstructor cwc = KoresProxy.newProxyInstance(this.getClass().getClassLoader(), ClassWithConstructor.class, (instance0, method, args, proxyData) -> {
 return method.resolveOrFail(cwcOrigin2.getClass()).bindTo(cwcOrigin2).invokeWithArguments(args);
}, new Class[] { String.class }, new Object[]{ cwcOrigin2.getName() });

Assert.assertEquals("getName", "Origin 2", cwc.getName());

Limitations

  • KoresProxy only handles public, protected and package-private (for package private, see below) methods.
  • KoresProxy only handles non-final methods.
  • KoresProxy only generate proxies to classes that have accessible constructors.

KoresProxy compared to Java Proxies

KoresProxy support Super-classes and Interfaces inheritance, Java Proxies only supports Interfaces inheritance.

Since 2.1, KoresProxy uses MethodInfo to provide method information and delegation instead of Java methods.

Java 9+

In Java 9 (or superior) KoresProxy works in a different way, instead of trying to inject classes in the ProxyData.classLoader using private inaccessible methods, it only injects when the defineClass method is public and if the method is not public, it creates a new class loader (CodeClassLoader) and loads the class with it. If the ProxyData.classLoader is a CodeClassLoader, it will use the instance instead of creating a new one. Also, KoresProxy does not override package-private methods in Java 9 nor defines the class in the same package as the target super class. You can disable this behavior using the option described below.

VM options

Specify them using -D or defining using the System.setProperty(String, String).

  • koresproxy.saveproxies

    • Description: Save proxies generated classes and disassembled code in gen/.
    • Values: true|false
    • Default: false
  • koresproxy.ignore_module_rules

    • Description: Ignore rules that applies to Java 9+, see the Java 9+ section above.
    • Values: true|false
    • Default: false

Known issues

Custom

ClassFormatError Invalid start_pc ...

  • java.lang.ClassFormatError: Invalid start_pc * in LocalVariableTable in class file *

This happens when a Custom adds a return statement out-side a flow or inside a flow that is always reached without invoking env.setInvokeHandler(false);, example:

public class MyCustomGen implements CustomHandlerGenerator {
    @Override
    public CodeSource gen(Method target, MethodDeclaration methodDeclaration, GenEnv env) {
        return CodeSource.fromPart(Factories.returnValue(target.getReturnType(),
            InvocationFactory.invokeSpecial(
                        target.getDeclaringClass(), Access.SUPER, target.getName(), methodDeclaration.getTypeSpec(),
                        methodDeclaration.getParameters().stream()
                            .map(ConversionsKt::toVariableAccess)
                            .collect(Collectors.toList())
                )
        ));
    }
}

This code will cause class proxy to fail to load with class format error, to solve that, you should add:

env.setInvokeHandler(false);
env.setMayProceed(false);

Before the return.

Why this happens?

This happens because without env.setInvokeHandler(false) KoresProxy will append invocation to InvocationHandler after the code generated by your CustomHandlerGenerator, resulting in dead code. CodeAPI-BytecodeWriter optimizers will remove this dead code, but optimizers does not update LocalVariableTable, meaning that local variable entries will point to a label that does not exists anymore in the bytecode, resulting in verifier throwing exception.

The env.setMayProceed(false); will only prevent others Custom to be run, this is necessary to ensure that no other Custom will add code after your (resulting in dead code too).