Search code examples
javajava-native-interfacemetaprogrammingsandboxbytecode

Rewriting Java native methods using ASM


I'm trying to do this by re-writing the bytecode of the class using ASM 4.0 to replace all the native methods with non-native stubs.

So far I have this:

class ClassAdapter extends ClassVisitor {

    public ClassAdapter(ClassVisitor cv) {
        super(Opcodes.ASM4, cv);
    }

    @Override
    public MethodVisitor visitMethod(int access, String base, String desc, String signature, String[] exceptions) {
        return cv.visitMethod(access & ~Opcodes.ACC_NATIVE, base, desc, signature, exceptions);
    }

}

which is executed by

private static byte[] instrument(byte[] originalBytes, ClassLoader loader) {
    ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
    ClassAdapter adapter = new ClassAdapter(cw);

    ClassReader cr = new ClassReader(originalBytes);
    cr.accept(adapter, ClassReader.SKIP_FRAMES);

    return cw.toByteArray();
}

Which seems simple enough: I strip the ACC_NATIVE off of the method in visitMethod() and leave everything else unchanged. However, when I do this to java.lang.Object, it dies with a

Exception in thread "main" 
Exception: java.lang.StackOverflowError thrown from the UncaughtExceptionHandler in thread "main"

The StackOverflow happens at instrumentation time, not at runtime, which I think is rather unusual. However, if I remove the & ~Opcodes.ACC_NATIVE modifier, java.lang.Object gets rewritten (in this case unchanged) and executes perfectly.

Clearly I am not doing something right, and replacing the native method with a non-native method isn't quite as simple as stripping off the native modifier on the method, but I have no idea where to start. The ASM Docs don't talk about working with native methods at all. Does anyone with experience working with ASM know what I need to do to get the native method re-writing to work?

EDIT

Sorry, that short, useless message was what e.printStackTrace() was giving me, but using e.getStackTrace() I managed to get something useful:

java.util.concurrent.ConcurrentHashMap.hash(ConcurrentHashMap.java:332)
java.util.concurrent.ConcurrentHashMap.put(ConcurrentHashMap.java:1124)
java.util.Collections$SetFromMap.add(Collections.java:3903)
sandbox.classloader.MyClassLoader.instrument(Unknown Source)
sandbox.classloader.MyClassLoader.loadClass(Unknown Source)
java.lang.ClassLoader.defineClass1(Native Method)
java.lang.ClassLoader.defineClass(ClassLoader.java:791)
java.lang.ClassLoader.defineClass(ClassLoader.java:634)
sandbox.classloader.MyClassLoader.findClass(Unknown Source)
sandbox.classloader.MyClassLoader.loadClass(Unknown Source)
sandbox.Tester.main(Unknown Source)
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
java.lang.reflect.Method.invoke(Method.java:601)
com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)

So it seems to me that the error was in fact happening at execution time (e.g. I was mistaken in thinking it was at instrumentation time) and is the result of calling hashCode(). As it so happens, hashCode() one of the native methods which I (probably incorrectly) stripped of it's native modifier. So clearly it's calling the native-stripped methods which is causing the problem.

What seems really odd is that the stack trace is only 16 frames deep; I'd have expected kinda more given that it was a StackOverflowError.


Solution