1

I have a public abstract class java.nio.ByteBuffer instance which is actually an instance of private class java.nio.HeapByteBuffer and I need to make a proxy object which would call some invocation method handler to check access permissions and then call the invoked method on the actual instance.

The problem is that the java.nio.ByteBuffer class has only private constructors and also has some final methods, thus I can not create proxy instances with javassist.util.proxy.ProxyFactory class.

So, how can I make a proxy object to control the invocation of a java.nio.ByteBuffer instance including those final methods invocation?

Rafael Winterhalter
  • 42,759
  • 13
  • 108
  • 192
KoichiSenada
  • 183
  • 1
  • 1
  • 10
  • At least not using javassist and not using cglib. Could you probably explain in more details why do you need this? `ByteBuffer` is a part of NIO that was introduced partially to be very fast IO. Using proxy (and therefore reflection calls) will kill the performance. So, again: why? – AlexR Sep 16 '14 at 08:53
  • @AlexR, the proxy object is not for a production code execution, it is to be created when the application is running in special debugging mode. I have some foreign legacy code which caches instances of classes which use ByteBuffer instances stored within their inner fields and it turns out that those are still used from somewhere after being placed back into the cache. I see that javassist and cglib can not handle such case. But what can? – KoichiSenada Sep 16 '14 at 09:17
  • I have examined [javassist CtClass.addConstructor](http://www.csg.ci.i.u-tokyo.ac.jp/~chiba/javassist/html/javassist/CtClass.html) and [ByteBuddy](http://bytebuddy.net/) briefly, but they are not easy to figure out the proxy object creation part. Some sample code might help but I can't find any. – KoichiSenada Sep 16 '14 at 09:18
  • @AlexR Proxies are not slow in Java, thanks to the JIT compiler. Reflective calls are an easy optimization target. – Rafael Winterhalter Sep 16 '14 at 11:04
  • @KoichiSenada Neither Byte Buddy, nor Javassist are concerned with *instance* creation. They are both meant for *class* creation. Objenesis is however concerned with object creation where this library almost became an industry standard for mocking frameworks. – Rafael Winterhalter Sep 16 '14 at 11:11

3 Answers3

3

Please be aware that I am presenting a solution based on my own (FOSS) framework Byte Buddy which is however already mentioned as a potential solution in one of the comments.

Here is a simple proxy approach which creates a subclass. First, we introduce a type for creating proxies for ByteBuffers:

interface ByteBufferProxy {
  ByteBuffer getOriginal();
  void setOriginal(ByteBuffer byteBuffer);
}

Furthermore, we need to introduce an interceptor to use with a MethodDelegation:

class Interceptor {
  @RuntimeType
  public static Object intercept(@Origin(cacheMethod = true) Method method,
                                 @This ByteBufferProxy proxy,
                                 @AllArguments Object[] arguments) 
                                     throws Exception {
    // Do stuff here such as:
    System.out.println("Calling " + method + " on " + proxy.getOriginal());
    return method.invoke(proxy.getOriginal(), arguments);
  }
}

This interceptor is capable of intercepting any method as the @RuntimeType casts the return type in case that it does not fit the Object signature. As you are merely delegating, you are safe. Plase read the documentation for details. As you can see from the annotations, this interceptor is only applicable for instances of ByteBufferProxy. Bases on this assumption, we want to:

  1. Create a subclass of ByteBuffer.
  2. Add a field to store the original (proxied) instance.
  3. Implement ByteBufferProxy and implement the interface methods to access the field for the stored instance.
  4. Override all other methods to call the interceptor that we defined above.

This we can do as follows:

@Test
public void testProxyExample() throws Exception {

  // Create proxy type.
  Class<? extends ByteBuffer> proxyType = new ByteBuddy()
    .subclass(ByteBuffer.class)
    .method(any()).intercept(MethodDelegation.to(Interceptor.class))
    .defineField("original", ByteBuffer.class, Visibility.PRIVATE)
    .implement(ByteBufferProxy.class).intercept(FieldAccessor.ofBeanProperty())
    .make()
    .load(getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
    .getLoaded();

    // Create fake constructor, works only on HotSpot. (Use Objenesis!)
    Constructor<? extends ByteBufferProxy> constructor = ReflectionFactory
      .getReflectionFactory()
      .newConstructorForSerialization(proxyType, 
                                      Object.class.getDeclaredConstructor());

    // Create a random instance which we want to proxy.
    ByteBuffer byteBuffer = ByteBuffer.allocate(42);

    // Create a proxy and set its proxied instance.
    ByteBufferProxy proxy = constructor.newInstance();
    proxy.setOriginal(byteBuffer);

    // Example: demonstrates interception.
    ((ByteBuffer) proxy).get();
}

final methods are obviously not intercepted. However as the final methods in ByteBuffer only serve as convenience methods (e.g. put(byte[]) calls put(byte[],int,int) with the additional arguments 0 and the array length), you are still able to intercept any method invocation eventually as these "most general" methods are still overridable. You could even trace the original invocation via Thread.currentCallStack().

Byte Buddy normally copies all constructors of its super class if you do not specify another ConstructorStrategy. With no accessible constructor, it simply creates a class without constructors what is perfectly legal in the Java class file format. You cannot define a constructor because, by definition, this constructor would need to call another constructor what is impossible. If you defined a constructor without this property, you would get a VerifierError as long as you do not disable the verifier altogether (what is a terrible solution as it makes Java intrinsically unsafe to run).

Instead, for instantiation, we call a popular trick that is used by many mocking frameworks but which requires an internal call into the JVM. Note that you should probably use a library such as Objenesis instead of directly using the ReflectionFactory because Objenesis is more robust when code is run on a different JVM than HotSpot. Also, rather use this in non-prduction code. Do however not worry about performance. When using a reflective Method that can be cached by Byte Buddy for you (via cacheMethod = true), the just-in-time compiler takes care of the rest and there is basically no performance overhead (see the benchmark on bytebuddy.net for details.) While reflective lookup is expensive, reflective invocation is not.

I just released Byte Buddy version 0.3 and I am currently working on documentation. In Byte Buddy 0.4, I plan to introduce an agent builder which allows you to redefine classes during load-time without knowing a thing about agents or byte code.

Rafael Winterhalter
  • 42,759
  • 13
  • 108
  • 192
  • I have combined everything into a TestNG class and the `load` method throws `IllegalAccessException`. `java.lang.IllegalAccessError: class net.bytebuddy.renamed.java.nio.ByteBuffer$ByteBuddy$100644077 cannot access its superinterface ByteBuddyTest$ByteBufferProxy` – KoichiSenada Sep 16 '14 at 11:34
  • 1
    I assume that `ByteBufferProxy` is package-private in your solution? This won't work unless you define the created `proxyType` to be in the same package as `ByteBufferProxy`. Alternatively, make it `public`. Same goes for the interceptor. Also, from the default type naming pattern, I assume that you use version 0.2.1 - rather use 0.3, it fixes some minor issues. – Rafael Winterhalter Sep 16 '14 at 11:43
  • it worked and I have also replaced the instantiation with ObjenesisStd instantiator which keeps working, too. But whether is that possible to intercept methods the way it is done with `javassist` to call some method invocation handler instance? The [ProxyObject](http://www.csg.ci.i.u-tokyo.ac.jp/~chiba/javassist/html/javassist/util/proxy/ProxyObject.html).setHandler method accepts a handler which can be a custom [MethodHandler](http://www.csg.ci.i.u-tokyo.ac.jp/~chiba/javassist/html/javassist/util/proxy/MethodHandler.html) instance constructed with the original object passed to it. – KoichiSenada Sep 16 '14 at 12:30
  • Javassist does effectively nothing else than what I have written down for you. However, Javassist takes a lot of flexibility from you by requiring an interceptor to be an instance of the fixed `MethodHandler` type. With Byte Buddy, you can use any POJO and even static methods as shown above. Instead of using `ProxyObject.setHandler`, you can simply use your own type such as `ByteBufferProxy`. Other than Javassist, Byte Buddy does not maintain a class cache. I explained why in this answer: http://stackoverflow.com/questions/23732236/caching-generated-classes-in-byte-buddy – Rafael Winterhalter Sep 16 '14 at 13:18
1

I can suggest you 2 solutions.

First, simple, not universal, but probably useful for you.

As far as I can see ByteBuffer has several package-private constructors that allow its subclassing and the following final methods:

public final ByteBuffer put(byte[] src) {
public final boolean hasArray() {
public final byte[] array() {
public final int arrayOffset() {
public final ByteOrder order() {

ByteBuffer extends Buffer that declares some of these methods:

public final boolean hasArray() {
public final Object array() {
public final int arrayOffset() {

As you can see, put() and order() are absent here, return type of array() is a little bit confusing, but still can be used. So, if you use only these 3 methods you can subclass Buffer and create universal wrapper that wraps any other Buffer including ByteBuffers. If you want you can use javaassist's proxy although IMHO it is not necessarily here.

Second, more universal but more tricky solution. You can create agent that removes final modifiers from speicific class (ByteBuffer in your case) during class loading. Then you can create javassist proxy.

Variation of second solution is following. Copy ByteBuffer soruce code to separate project. Remove final modifiers and compile it. Then push it into bootstrap classpath. This solutions is probably easier than second.

Good luck anyway.

AlexR
  • 114,158
  • 16
  • 130
  • 208
0

Thanks to @raphw I have managed to make a proxy object construction class which makes a proxy for java.nio.ByteBuffer but that class has final methods which I can not overcome and they are extensively used in the required code, those final methods are Buffer.remaining() and Buffer.hasRemaining(), thus they just can not be proxy mapped.

But I would like to share the classes I have made, just as a report.

public final class CacheReusableCheckerUtils {
        private static ByteBuddy buddy = new ByteBuddy();
        private static Objenesis objenesis = new ObjenesisStd();

        public static <T> T createChecker(T object) {
            return createChecker(new CacheReusableCheckerInterceptor<>(object));
        }

        public static <T> T createChecker(CacheReusableCheckerInterceptor<T> interceptor) {
            return objenesis.getInstantiatorOf(createCheckerClass(interceptor)).newInstance();
        }

        private static <T> Class<? extends T> createCheckerClass(CacheReusableCheckerInterceptor<T> interceptor) {
            Class<T> objectClass = interceptor.getObjectClass();
            Builder<? extends T> builder = buddy.subclass(objectClass);
            builder = builder.implement(CacheReusableChecker.class).intercept(StubMethod.INSTANCE);
            builder = builder.method(MethodMatchers.any()).intercept(MethodDelegation.to(interceptor));
            return builder.make().load(getClassLoader(objectClass, interceptor), Default.WRAPPER).getLoaded();
        }

        private static <T> ClassLoader getClassLoader(Class<T> objectClass, CacheReusableCheckerInterceptor<T> interceptor) {
            ClassLoader classLoader = objectClass.getClassLoader();
            if (classLoader == null) {
                return interceptor.getClass().getClassLoader();
            } else {
                return classLoader;
            }
        }
    }

public class CacheReusableCheckerInterceptor<T> {
    private T object;
    private boolean allowAccess;
    private Throwable denyThrowable;

    public CacheReusableCheckerInterceptor(@NotNull T object) {
        this.object = object;
    }

    @SuppressWarnings("unchecked")
    public Class<T> getObjectClass() {
        return (Class<T>) object.getClass();
    }

    @RuntimeType
    public final Object intercept(@Origin(cacheMethod = true) Method method, @This T proxy, @AllArguments Object[] arguments) {
        try {
            switch (method.getName()) {
                case "allowAccess":
                    allowAccess();
                    return null;
                case "denyAccess":
                    denyAccess();
                    return null;
                default:
                    return invokeMethod(method, arguments);
            }
        } catch (Exception e) {
            throw new CacheReusableCheckerException(method, object, proxy, e);
        }
    }

    private Object invokeMethod(Method method, Object[] arguments) throws IllegalAccessException, InvocationTargetException {
        checkMethodAccess(method.getName());
        return method.invoke(object, arguments);
    }

    private void allowAccess() {
        if (allowAccess) {
            error("double use");
        }
        allowAccess = true;
        onAccessAllowedAfter(object);
    }

    private void denyAccess() {
        if (!allowAccess) {
            error("double free");
        }
        onAccessDeniedBefore(object);
        allowAccess = false;
        denyThrowable = new Throwable();
    }

    private void checkMethodAccess(String name) {
        if (!allowAccess) {
            switch (name) {
                case "hash":
                case "equals":
                case "toString":
                case "finalize":
                    break;
                default:
                    error("use after free");
            }
        }
    }

    private void error(String message) {
        throw new CacheReusableCheckerException(message, denyThrowable);
    }

    protected void onAccessAllowedAfter(T object) {
    }

    protected void onAccessDeniedBefore(T object) {
    }
}

public interface CacheReusableChecker {

    void allowAccess();

    void denyAccess();

}
KoichiSenada
  • 183
  • 1
  • 1
  • 10
  • 1
    Thanks for sharing this. Look out for Byte Buddy version 0.4 which comes with a agent builder which will allow you to generate such proxies by redefining classes during class loading. – Rafael Winterhalter Sep 22 '14 at 09:03