Haasip Satang
Haasip Satang

Reputation: 477

ByteBuddy - Rebasing native methods

I'm checking if I can use ByteBuddy to replace some bulky ASM code with a smaller, more elegant solution. One part is to "wrap" native methods, meaning prefix any existing native method in a class and provide a new, non-native method with the same name and signature calling the prefixed one. All this can happen during build time, so I don't need any agents, neither need to worry about potential class schema changes, etc

From the documentation it seemed like a case for rebasing where ByteBuddy leaves a copy of the original method and provides the possibilty to call it via @SuperCall.

Besides not being able to influence the new names for the original methods (they need to have a prefix, not a postfix) the following code leads to the error below:

        new ByteBuddy()
        .rebase(typePool.describe("test.ClassWithNatives").resolve(), ClassFileLocator.ForClassLoader.ofClassPath())
        .method(named("someNativeMethod"))
        .intercept(MethodDelegation.to(NativeInterceptor.class))
        .make()
        .load(getClass().getClassLoader())
        .getLoaded();

.

Exception in thread "main" java.lang.IllegalStateException: Error invoking java.lang.ClassLoader#findClass
    at net.bytebuddy.dynamic.loading.ClassInjector$UsingReflection$Dispatcher$Direct.defineClass(ClassInjector.java:410)
    at net.bytebuddy.dynamic.loading.ClassInjector$UsingReflection.inject(ClassInjector.java:183)
    at net.bytebuddy.dynamic.loading.ClassLoadingStrategy$Default$InjectionDispatcher.load(ClassLoadingStrategy.java:187)
    at net.bytebuddy.dynamic.loading.ClassLoadingStrategy$Default.load(ClassLoadingStrategy.java:120)
    at net.bytebuddy.dynamic.TypeResolutionStrategy$Passive.initialize(TypeResolutionStrategy.java:79)
    at net.bytebuddy.dynamic.DynamicType$Default$Unloaded.load(DynamicType.java:4376)
    at net.bytebuddy.dynamic.DynamicType$Default$Unloaded.load(DynamicType.java:4366)
    at ByteBuddyTest.wrapNatives(ByteBuddyTest.java:45)
    at ByteBuddyTest.start(ByteBuddyTest.java:22)
    at ByteBuddyTest.main(ByteBuddyTest.java:16)
Caused by: java.lang.ClassFormatError: Absent Code attribute in method that is not native or abstract in class file ClassWithNatives
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at net.bytebuddy.dynamic.loading.ClassInjector$UsingReflection$Dispatcher$Direct.defineClass(ClassInjector.java:406)
    ... 9 more

Is it possible to realize such a scenario with ByteBuddy?

Upvotes: 1

Views: 1228

Answers (1)

Rafael Winterhalter
Rafael Winterhalter

Reputation: 44077

This is only possible by using a Java agent via an Instrumentation. Otherwise, the JVM does not know how to rebase a native method which has native adapters that have to follow method naming conventions. For Byte Buddy to work, the original code is moved to a different method for being invokable from the original method which is not possible otherwise.

Using a Java agent, it is possible to run:

new AgentBuilder.Default()
  .enableNativeMethodPrefix("foo")
  .type(named("test.ClassWithNatives"))
  .transform((b,t,c,m) -> b
    .method(named("someNativeMethod"))
    .intercept(MethodDelegation.to(NativeInterceptor.class))
  ).installOn(inst);

Upvotes: 3

Related Questions