This is the fourth part of the Playing With args4j series. For your convenience you can find other parts in the table of contents in Part 1 – Mixins
Last time we ended with the setter of a form
1 |
parameters.set(IJobParameters::parameter, "Value", JobParameters.class); |
. What we don’t like here is the last parameters — why would we want to pass the class type if we pass the method reference? Can we extract it somehow?
In theory we cannot but with some help of unsafe methods we will be able to do so. Beware — this is not production ready, this is just for fun.
We will need the Unsafe library to make it a little easier. We could go with using sun.misc.unsafe
directly but this library does that and gives nice interface so we can use it.
First, we need to change the lambda type:
1 2 3 4 5 |
default < T> void set(Supplier<T> getter, T value) { Object target = getTarget(getter); Method method = MethodReferenceUtils.getReferencedMethod(getter, target); set(method.getAnnotation(Option.class).name(), value); } |
So we get the Supplier
which points to the method directly. Next, we get the target from the lambda. This is how we do it:
1 2 3 4 5 6 |
default Object getTarget(Object lambda){ byte[] bytes = UnsafeHelper.toByteArray(lambda); int offset = bytes.length - 1; int address = bytes[offset] << 24 | (bytes[offset - 1] & 0xFF) << 16 | (bytes[offset - 2] & 0xFF) << 8 | (bytes[offset - 3] & 0xFF); return UnsafeHelper.fromAddress(address); } |
My JVM version running on Windows 10 Enterprise x64 is:
1 2 3 |
java version "1.8.0_191" Java(TM) SE Runtime Environment (build 1.8.0_191-b12) Java HotSpot(TM) 64-Bit Server VM (build 25.191-b12, mixed mode) |
This explains why we get the 4 bytes pointer.
Next, we need to modify the cglib logic:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
public static < T, U> Method getReferencedMethod(Supplier<T> getter, U target) { Class<?> klass = target.getClass(); AtomicReference<Method> ref = new AtomicReference<>(); Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(klass); enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> { ref.set(method); return null; }); try { setTarget(getter, enhancer.create()); getter.get(); } catch (ClassCastException e) { throw new IllegalArgumentException(String.format("Invalid method reference on class [%s]", klass)); } Method method = ref.get(); if (method == null) { throw new IllegalArgumentException(String.format("Invalid method reference on class [%s]", klass)); } return method; } |
So now we get the type by calling getClass
and then we modify the target of the lambda. We use pointer arithmetic once again to do that:
1 2 3 4 5 6 7 8 9 |
static void setTarget(Object lambda, Object target){ long targetAddress = UnsafeHelper.toAddress(target); long lambdaAddress = UnsafeHelper.toAddress(lambda); Unsafe unsafe = UnsafeHelper.getUnsafe(); unsafe.putByte(lambdaAddress + 15, (byte) (targetAddress >> 24 & 0xFF)); unsafe.putByte(lambdaAddress + 14, (byte) (targetAddress >> 16 & 0xFF)); unsafe.putByte(lambdaAddress + 13, (byte) (targetAddress >> 8 & 0xFF)); unsafe.putByte(lambdaAddress + 12, (byte) (targetAddress & 0xFF)); } |
Thanks to that we can now write:
1 |
parameters.set(parameters::parameter, "Some value"); |