Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a fast approach to reflective field access?

I need a way to access fields in a reflective nature without the performance hits from standard reflection. I have figured out how to do this with methods/constructors via LambdaMetaFactory using a privileged lookup handle, however, I can't seem to figure out how to gain field access.

I thought I could generate an inner class via something like javaassist which should theoretically have access to that field but that did not work out, throwing an IllegalAccessError.

If I could redefine the class the task would be trivial as I could generate getter/setter methods. However, for the project I am working on, I am unable to use an agent as it would need to be loaded at runtime and I would have to dynamically import the attach api from tools.

Could anyone guide me in the right direction here? I've looked into how LambdaMetaFactory generates it's interface for methods and tried to mirror that with fields with no success. Is there something internally different with fields and methods that makes this task impossible without redefinition?

like image 513
harrisondev Avatar asked Oct 26 '22 19:10

harrisondev


1 Answers

In case of OpendJDK (and JDKs built atop it), the LambdaMetaFactory generates a mostly ordinary class file (just accessing private member(s) of another class) and uses a special method in sun.misc.Unsafe, to create an anonymous class.

Creating a similar class file accessing a field, is straight-forward and creating an anonymous class with it, does work, as can be demonstrated with the following quick&dirty program:

public class Generator {
    public static void main(String[] args) throws Throwable {
        ToIntFunction<Thread> ft=generateIntFieldAccessor(Thread.class, "threadStatus");
        System.out.println(ft.applyAsInt(Thread.currentThread()));
    }

    private static <X> ToIntFunction<X> generateIntFieldAccessor(
        Class<? super X> c, String name) throws Throwable {

        byte[] code = Generator.generateIntReaderCode(c.getDeclaredField(name));
        Class<?> unsafe = Class.forName("sun.misc.Unsafe");
        Field u = unsafe.getDeclaredField("theUnsafe");
        u.setAccessible(true);
        Object theUnsafe = u.get(null);
        Class<ToIntFunction<X>> gen = (Class<ToIntFunction<X>>)
            MethodHandles.publicLookup().bind(theUnsafe, "defineAnonymousClass",
                 MethodType.methodType(
                     Class.class, Class.class, byte[].class, Object[].class))
                .invokeExact(c, code, (Object[])null);
        return gen.getConstructor().newInstance();
    }

    private static final String HEAD = "Êþº¾\0\0\0004\0\24\7\0\21\7\0\t\7\0\n\7\0\22"
        + "\n\0\2\0\6\f\0\13\0\f\t\0\4\0\b\f\0\23\0\20\1\0\20java/lang/Object\1\0\40"
        + "java/util/function/ToIntFunction\1\0\6<init>\1\0\3()V\1\0\4Code\1\0\n"
        + "applyAsInt\1\0\25(Ljava/lang/Object;)I\1\0\1I";
    private static final String TAIL = "\0001\0\1\0\2\0\1\0\3\0\0\0\2\0\1\0\13\0\f\0"
        + "\1\0\r\0\0\0\21\0\1\0\1\0\0\0\5*·\0\5±\0\0\0\0\0\21\0\16\0\17\0\1\0\r\0\0"
        + "\0\24\0\1\0\2\0\0\0\b+À\0\4´\0\7¬\0\0\0\0\0\0";

    public static byte[] generateIntReaderCode(Field f) {
        return new ByteArrayOutputStream(HEAD.length() + TAIL.length() + 100) {
            @SuppressWarnings("deprecation") byte[] get() {
                HEAD.getBytes(0, count = HEAD.length(), buf, 0);
                try(DataOutputStream dos = new DataOutputStream(this)) {
                    String decl = f.getDeclaringClass().getName().replace('.', '/');
                    dos.writeByte(1); dos.writeUTF(decl+"$"+f.getName()+"$access");
                    dos.writeByte(1); dos.writeUTF(decl);
                    dos.writeByte(1); dos.writeUTF(f.getName());
                } catch (IOException ex) {
                    throw new UncheckedIOException(ex);
                }
                int dynSize = count;
                byte[] result = Arrays.copyOf(buf, dynSize + TAIL.length());
                TAIL.getBytes(0, TAIL.length(), result, dynSize);
                return result;
            }
        }.get();
    }
}

Demo on Ideone

Of course, for production code you should better resort to one of the commonly used code generation libraries, to have maintainable factory code. E.g., OpenJDK’s LambdaMetaFactory uses the ASM library under the hood.

If your attempt to implement a similar solution failed, you have to post what you’ve tried, so we can help identifying the problem. But perhaps, knowing that it is possible in general, does already help you.

like image 70
Holger Avatar answered Oct 29 '22 22:10

Holger