It seems that with some (very dubious) tricks this is possible, even without going through a custom native library, by (ab)using method handles.
This method essentially tricks the JVM into thinking it is currently invoking a regular method instead of a constructor.
I just have to add a mandatory "this is probably not a good idea", but this is the only way I found for doing this. I also can't attest to how this behaves on different JVMs.
Prerequisites
To do this, an instance of sun.misc.Unsafe
is needed. I will not go into detail about how to obtain this here since you already seem to have one, but this guide explains the process.
Step 1: Obtaining a trusted MethodHandles.Lookup
Next, a java.lang.invoke.MethodHandles$Lookup
is needed to get the actual method handle for the constructor.
This class has a permission system which works through the allowedModes
property in Lookup
, which is set to a bunch of Flags. There is a special TRUSTED
flag that circumvents all permission checks.
Unfortunately, the allowedModes
field is filtered from reflection, so we cannot simply bypass the permissions by setting that value through reflection.
Even though reflecion filters can be circumvented aswell, there is a simpler way: Lookup
contains a static field IMPL_LOOKUP
, which holds a Lookup
with those TRUSTED
permissions. We can get this instance by using reflection and Unsafe
:
var field = MethodHandles.Lookup.class.getDeclaredField("IMPL_LOOKUP");
var fieldOffset = unsafe.staticFieldOffset(field);
var lookup = (MethodHandles.Lookup) unsafe.getObject(MethodHandles.Lookup.class, fieldOffset);
We use Unsafe
here instead of setAccessible
and get
, because going through reflection will cause issues with the module system in the newer java versions.
Step 2: Finding the constructor
Now we can get a MethodHandle
for the constructor we want to invoke. We do this by using the Lookup
we just obtained, just like a Lookup
would be used normally.
var type = MethodType.methodType(Void.TYPE, <your constructor argument types>);
var constructor = lookup.findConstructor(<your class>, type);
Step 3: Getting the MemberName
While the signature of findConstructor
only specifies that it returns a MethodHandle
, it actuall returns a java.lang.invoke.DirectMethodHandle$Constructor
. This type declares a initMethod
field, which contains the java.lang.invoke.MemberName
referencing our constructor. The MemberName
type is not accessible from the outside, so all interaction with it happens through Unsafe
.
We can obtain this MemberName
in the same way we also obtained the Lookup
:
var constructorClass = Class.forName("java.lang.invoke.DirectMethodHandle$Constructor");
val initMethodField = constructorClass.getDeclaredField("initMethod");
val initMethodFieldOffset = unsafe.objectFieldOffset(initMethodField);
var initMemberName = unsafe.getObject(constructor, initMethodFieldOffset)
Step 4: Tricking Java
The next step is the important part. While there are no physical barriers from the JVM that prevent you from invoking a constructor like any other method, MethodHandle
has some checks in place to ensure that you are not doing something fishy.
Most of the checks are circumvented by using the TRUSTED
Lookup
, and there remains one final check:
The MemberName
instance contains a bunch of flags that, among other things, tell the system what kind of member the MemberName
is referring to. These flags are checked.
To circumvent this, we can simply change the flags using Unsafe
:
var memberNameClass = Class.forName("java.lang.invoke.MemberName");
var flagsField = memberNameClass.getDeclaredField("flags");
var flagsFieldOffset = unsafe.objectFieldOffset(flagsField);
var flags = unsafe.getInt(initMemberName, flagsFieldOffset);
flags &= ~0x00020000; // remove "is constructor"
flags |= 0x00010000; // add "is (non-constructor) method"
unsafe.putInt(initMemberName, flagsFieldOffset, flags);
The values for the flags come from java.lang.invoke.MethodHandleNatives.Constants#MN_IS_METHOD
and java.lang.invoke.MethodHandleNatives.Constants#MN_IS_CONSTRUCTOR
.
Step 5: Obtaining a REF_invokeVirtual
method handle
Now that we have a totally legit method that is not at all a constructor, we just need to obtain a regular method handle for invoking it. Luckly, MethodHandles.Lookup.class
has a private method for turning a MemberName
into a (Direct)MethodHandle
for all kinds of invocations: getDirectMethod
.
Ironically, we actually call this method using our all-powerful lookup.
First, we obtain the MethodHandle
for getDirectMethod
:
var getDirectMethodMethodHandle = lookup.findVirtual(
MethodHandles.Lookup.class,
"getDirectMethod",
MethodType.methodType(
MethodHandle.class,
byte.class,
Class.class,
memberNameClass,
MethodHandles.Lookup.class
)
);
we can now use this with our lookup, to obtain a MethodHandle
for our MemberName
:
var handle = (MethodHandle) getDirectMethod.invoke(lookup, (byte) 5, Test.class, member, lookup);
The (byte) 5
argument stands for "invoke virtual", and comes from java.lang.invoke.MethodHandleNatives.Constants#REF_invokeVirtual
.
Step 6: Profit?
We can now use this handle
like a regular MethodHandle
, to invoke the constructor on any existing instance of that class:
handle.invoke(<instance>, <constructor arguments...>);
With this handle
, the constructor can also be called multiple times, and the instance doesn't actually have to come from Unsafe#allocateInstance
- an instance that was created just by using new
works aswell.