Oleg Pyzhcov
Oleg Pyzhcov

Reputation: 7353

Is it possible to call constructor on existing instance?

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

Answers (5)

fxshlein
fxshlein

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.

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.

Upvotes: 6

apangin
apangin

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

Andy Turner
Andy Turner

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

Andy Turner
Andy Turner

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

Bohemian
Bohemian

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

Related Questions