Reputation: 7353
It is known that using sun.misc.Unsafe#allocateInstance
one can create an object without calling any class constructors.
Is it possible to do the opposite: given an existing instance, invoke a constructor on it?
Clarification: this is not the question about something I'd do in production code. I'm curious about JVM internals and crazy things that can still be done. Answers specific to some JVM version are welcome.
Upvotes: 4
Views: 3127
Reputation: 153
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.
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.
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.
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);
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)
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
.
REF_invokeVirtual
method handleNow 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
.
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.
Upvotes: 6
Reputation: 98284
JVMS §2.9 forbids invocation of constructor on already initialized objects:
Instance initialization methods may be invoked only within the Java Virtual Machine by the invokespecial instruction, and they may be invoked only on uninitialized class instances.
However, it is still technically possible to invoke constructor on initialized object with JNI. CallVoidMethod function does not make difference between <init>
and ordinary Java methods. Moreover, JNI specification hints that CallVoidMethod
may be used to call a constructor, though it does not say whether an instance has to be initialized or not:
When these functions are used to call private methods and constructors, the method ID must be derived from the real class of obj, not from one of its superclasses.
I've verified that the following code works both in JDK 8 and JDK 9. JNI allows you to do unsafe things, but you should not rely on this in production applications.
ConstructorInvoker.java
public class ConstructorInvoker {
static {
System.loadLibrary("constructorInvoker");
}
public static native void invoke(Object instance);
}
constructorInvoker.c
#include <jni.h>
JNIEXPORT void JNICALL
Java_ConstructorInvoker_invoke(JNIEnv* env, jclass self, jobject instance) {
jclass cls = (*env)->GetObjectClass(env, instance);
jmethodID constructor = (*env)->GetMethodID(env, cls, "<init>", "()V");
(*env)->CallVoidMethod(env, instance, constructor);
}
TestObject.java
public class TestObject {
int x;
public TestObject() {
System.out.println("Constructor called");
x++;
}
public static void main(String[] args) {
TestObject obj = new TestObject();
System.out.println("x = " + obj.x); // x = 1
ConstructorInvoker.invoke(obj);
System.out.println("x = " + obj.x); // x = 2
}
}
Upvotes: 7
Reputation: 140318
In the JVM spec for invokespecial
:
An
invokespecial
instruction is type safe iff all of the following are true:... (Stuff about non-init methods)
- MethodName is
<init>
.- Descriptor specifies a void return type.
- One can validly pop types matching the argument types given in Descriptor and an uninitialized type, UninitializedArg, off the incoming operand stack, yielding OperandStack.
- ...
If you've already initialized the instance, it's not an uninitialized type, so this will fail.
Note that other invoke*
instructions (invokevirtual
, invokeinterface
, invokestatic
, invokedynamic
) explicitly preclude invocation of <init>
methods, so invokespecial
is the only way to invoke them.
Upvotes: 1
Reputation: 140318
From JLS Sec 8.8
Constructors are invoked by class instance creation expressions (§15.9), by the conversions and concatenations caused by the string concatenation operator +(§15.18.1), and by explicit constructor invocations from other constructors (§8.8.7).
...
Constructors are never invoked by method invocation expressions (§15.12).
So no, it's not possible.
If there is some common action you want to take in the constructor and elsewhere, put it into a method, and invoke that from the constructor.
Upvotes: 0
Reputation: 425003
A constructor is not an instance method, so no you can't invoke a constructor on an instance.
If you look at the reflection library, you'll see that the return type of Class.getConstructor()
is Constructor
, which doesn't have any methods that can accept a instance - its only relevant method is newInstance()
, which doesn't accept a target instance; it creates one.
On the other hand, the return type of Class.getMethod()
is Method
, whose first parameter is the instance.
A Constructor
is not a Method
.
Upvotes: 2