Mixin icon indicating copy to clipboard operation
Mixin copied to clipboard

Add InjectionPoint for after object initialization

Open ItsDoot opened this issue 4 years ago • 5 comments

It would be nice to have an injection point for after an object has been created, initialized, and stored.

Given Object object = new Object(); roughly compiles to:

NEW Ljava/lang/Object;
INVOKESPECIAL Ljava/lang/Object;<init>()V
ASTORE 1

this injection point would inject after the ASTORE.

Potential Names:

  • INVOKE_INIT
  • NEW_INIT
  • NEW_ASSIGN

ItsDoot avatar Sep 26 '19 16:09 ItsDoot

Doesn't an inject to an INVOKE_ASSIGN for an <init> method call suffice?

liach avatar Sep 26 '19 18:09 liach

No, per mumfrey in discord:

INVOKE_ASSIGN is looking for a method call where the return value is assigned to a local
constructors don't return a value, the "value" is created by the NEW and just initialised by the ctor call
so INVOKE_ASSIGN isn't going to match that invocation in the first place since it returns void```

ItsDoot avatar Sep 26 '19 18:09 ItsDoot

ASTORE 1

One problem I see here is that it's not always stored, it's possible the new object is not stored to the local variable table, and simply just provided on the stack, and immediately passed through another method invocation.

One way that I've already mentioned to work around this is with:


@Inject(method = "derp(I)V",
    at = @At(value = "NEW", target = "java/lang/StringBuilder", shift = Shift.AFTER),
    locals = LocalCapture.FAIL_HARD
)
private void derpStringBuilder(int arg, CallbackInfo ci, StringBuilder builder) {

} 

gabizou avatar Sep 26 '19 19:09 gabizou

I found an instance of the case @gabizou described:

(new Minecraft(var45)).run();

(This appears in net.minecraft.client.main.Main in 1.8.9) This example doesn't store it in a local variable:

L70 {
    new net/minecraft/client/Minecraft
    dup
    aload45
    invokespecial net/minecraft/client/Minecraft.<init>(Lnet/minecraft/client/main/GameConfiguration;)V
    invokevirtual net/minecraft/client/Minecraft.run()V
}

I was trying to inject something between the <init> and run calls. (I ended up moving my code to inject into run itself instead.) The workaround posted isn't working because I believe it is using the instance of Minecraft before it can be passed to run, and then there is no instance for run to use.

The Mixin I was trying

    @Inject(method = "main([Ljava/lang/String;)V",
            at = @At(value = "NEW", target = "net/minecraft/client/Minecraft", shift = At.Shift.BY, by = 3),
            locals = LocalCapture.CAPTURE_FAILHARD)
    private static void impl$mainMc(String[] args, CallbackInfo ci, Minecraft instance) {
        System.out.println("Got Minecraft instance!!!\n!!!!!\n!!!\n" + instance.mcDataDir.getAbsolutePath());
    }
 Critical injection failure: LVT in net/minecraft/client/main/Main::main([Ljava/lang/String;)V has incompatible changes at opcode 600 in callback net/minecraft/client/main/Main::impl$mainMc.
Expected: [Lave;]
   Found: [Ljoptsimple/OptionParser;]

So I think if you want to capture the new instance as a local variable in your Mixin, then Mixin would need to insert a dup instruction so the reference can stay on the stack.

phase avatar May 28 '20 18:05 phase

@phase why are you shifting by 3? You would want to shift after the init to get the minecraft instance, or just inject at run with no shift and your injection would have the LVT along with the Minecraft instance.

gabizou avatar Jul 02 '20 15:07 gabizou