Guardsquare / proguard

ProGuard, Java optimizer and obfuscator
https://www.guardsquare.com/en/products/proguard
GNU General Public License v2.0
2.89k stars 412 forks source link

Invoke does not work in Java 16 proguard #189

Closed ScottCowe closed 3 years ago

ScottCowe commented 3 years ago

I have a Fabric 1.17.1 (Java 16) mod for Minecraft that I am trying to obfuscate. Included with this mod is my event system that handles all the events for my mod. This event system uses the invoke method to call events in my event listener class at runtime.

Here is the the event I am calling using the call method:

@EventHandler
public static void onMinecraftInitialization(EventMinecraftInitialization event)
{
    // Do stuff
}

Here is the call method:

public void call(Event event)
{
    final EventData data = get(event.getClass());

    try
    {
        data.target.invoke(data.source, event); // Error here
    }
    catch (InvocationTargetException | IllegalAccessException e)
    {
        e.printStackTrace();
    }
}

I am also using mixins to register my event listener class, which works fine.

In a development enviroment this works fine and my minecraft mod runs. However, when I build my mod and attempt to use it in the Minecraft launcher it crashed, with the crash report of:

java.lang.VerifyError: Bad type on operand stack
Exception Details:
  Location:
    dev/mistercow1/sobyity/EventBus.call(Ldev/mistercow1/sobyity/Event;)V @33: invokevirtual
  Reason:
    Type 'java/lang/Object' (current frame, stack[0]) is not assignable to 
    'java/lang/ReflectiveOperationException'

dev/mistercow1/sobyity/EventBus.call(Ldev/mistercow1/sobyity/Event;)V @33 is the invoke in my call method.

In my proguard mappings I have excluded the event library and the mixin package (so they do not get obfuscated), as that is required for minecraft to run, as it is packaged into the jar using shadowjar. I have also disabled shrinking and optimizing, and repackaged classes into the base directory (dev.mistercow1.minecraftmod)

Everything works fine in the deobfuscated version of my mod and in the development enviroment, so I am confused why the obfuscated version does not work.

I have already tried using data.target.invoke(null, event); instead, but it has no effect.

Is this a problem with proguard or my event system or minecraft mod?

Does anyone have any solutions?

mrjameshamilton commented 3 years ago

Hi @MisterCow1 !

As a test, if you also use -dontobfuscate does the problem disappear? You mention you have already disabled optimization and shrinking, so this would also rule out or confirm obfuscation as well.

Have you tried adding a keep rule for dev/mistercow1/sobyity/EventBus.call(Ldev/mistercow1/sobyity/Event;)V ?

Could you show the bytecode of dev/mistercow1/sobyity/EventBus.call(Ldev/mistercow1/sobyity/Event;)V - you can print it with javap -c -v -p -cp my.jar dev.mistercow1.sobyity.EventBus

ScottCowe commented 3 years ago

I have added -dontobfuscate and it seems like Minecraft still crashes with the java.lang.VerifyError: Bad type on operand stack error. However, in the mod jar that has not been modified by proguard it works fine.

dev/mistercow1/sobyity/EventBus.call(Ldev/mistercow1/sobyity/Event;)V is being kept, along with the entire sobyity library. My Minecraft mod is separate from this, but sobyity is included in the jar, unobfuscted.

Bytecode for dev/mistercow1/sobyity/EventBus.call(Ldev/mistercow1/sobyity/Event;)V is as follows:

public void call(dev.mistercow1.sobyity.Event);
    descriptor: (Ldev/mistercow1/sobyity/Event;)V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=6, locals=4, args_size=2
         0: aload_1
         1: invokevirtual #22                 // Method java/lang/Object.getClass:()Ljava/lang/Class;
         4: invokestatic  #17                 // Method get:(Ljava/lang/Class;)Ldev/mistercow1/sobyity/EventData;
         7: astore_2
         8: aload_2
         9: getfield      #16                 // Field dev/mistercow1/sobyity/EventData.target:Ljava/lang/reflect/Method;
        12: aload_2
        13: getfield      #15                 // Field dev/mistercow1/sobyity/EventData.source:Ljava/lang/Class;
        16: iconst_1
        17: anewarray     #8                  // class java/lang/Object
        20: dup
        21: iconst_0
        22: aload_1
        23: aastore
        24: invokevirtual #26                 // Method java/lang/reflect/Method.invoke:(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;
        27: pop
        28: goto          36
        31: astore_3
        32: aload_3
        33: invokevirtual #23                 // Method java/lang/ReflectiveOperationException.printStackTrace:()V
        36: return
      Exception table:
         from    to  target type
             8    28    31   Class java/lang/reflect/InvocationTargetException
             8    28    31   Class java/lang/IllegalAccessException
      StackMapTable: number_of_entries = 2
        frame_type = 255 /* full_frame */
          offset_delta = 31
          locals = []
          stack = [ class java/lang/Object ]
        frame_type = 4 /* same */
mrjameshamilton commented 3 years ago

It looks like the type in the StackMapTable is wrong - at offset 31 there should be an exception on the stack since that's the start of the exception handler. This could be a problem with the preverifier since that's where these stack map tables are generated in ProGuard.

ScottCowe commented 3 years ago

Original bytecode for call method is as follows:

public void call(dev.mistercow1.sobyity.Event);
    descriptor: (Ldev/mistercow1/sobyity/Event;)V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=6, locals=4, args_size=2
         0: aload_1
         1: invokevirtual #98                 // Method java/lang/Object.getClass:()Ljava/lang/Class;
         4: invokestatic  #100                // Method get:(Ljava/lang/Class;)Ldev/mistercow1/sobyity/EventData;
         7: astore_2
         8: aload_2
         9: getfield      #72                 // Field dev/mistercow1/sobyity/EventData.target:Ljava/lang/reflect/Method;
        12: aload_2
        13: getfield      #103                // Field dev/mistercow1/sobyity/EventData.source:Ljava/lang/Class;
        16: iconst_1
        17: anewarray     #4                  // class java/lang/Object
        20: dup
        21: iconst_0
        22: aload_1
        23: aastore
        24: invokevirtual #107                // Method java/lang/reflect/Method.invoke:(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;
        27: pop
        28: goto          36
        31: astore_3
        32: aload_3
        33: invokevirtual #114                // Method java/lang/ReflectiveOperationException.printStackTrace:()V
        36: return
      Exception table:
         from    to  target type
             8    28    31   Class java/lang/reflect/InvocationTargetException
             8    28    31   Class java/lang/IllegalAccessException
      StackMapTable: number_of_entries = 2
        frame_type = 255 /* full_frame */
          offset_delta = 31
          locals = [ class dev/mistercow1/sobyity/EventBus, class dev/mistercow1/sobyity/Event, class dev/mistercow1/sobyity/EventData ]
          stack = [ class java/lang/ReflectiveOperationException ]
        frame_type = 4 /* same */
      LineNumberTable:
        line 166: 0
        line 171: 8
        line 176: 28
        line 173: 31
        line 175: 32
        line 177: 36
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
           32       4     3     e   Ljava/lang/ReflectiveOperationException;
            0      37     0  this   Ldev/mistercow1/sobyity/EventBus;
            0      37     1 event   Ldev/mistercow1/sobyity/Event;
            8      29     2  data   Ldev/mistercow1/sobyity/EventData;

Bytecode with -dontpreverify is as follows:

public void call(dev.mistercow1.sobyity.Event);
    descriptor: (Ldev/mistercow1/sobyity/Event;)V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=6, locals=4, args_size=2
         0: aload_1
         1: invokevirtual #20                 // Method java/lang/Object.getClass:()Ljava/lang/Class;
         4: invokestatic  #15                 // Method get:(Ljava/lang/Class;)Ldev/mistercow1/sobyity/EventData;
         7: astore_2
         8: aload_2
         9: getfield      #14                 // Field dev/mistercow1/sobyity/EventData.target:Ljava/lang/reflect/Method;
        12: aload_2
        13: getfield      #13                 // Field dev/mistercow1/sobyity/EventData.source:Ljava/lang/Class;
        16: iconst_1
        17: anewarray     #6                  // class java/lang/Object
        20: dup
        21: iconst_0
        22: aload_1
        23: aastore
        24: invokevirtual #24                 // Method java/lang/reflect/Method.invoke:(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;
        27: pop
        28: goto          36
        31: astore_3
        32: aload_3
        33: invokevirtual #21                 // Method java/lang/ReflectiveOperationException.printStackTrace:()V
        36: return
      Exception table:
         from    to  target type
             8    28    31   Class java/lang/reflect/InvocationTargetException
             8    28    31   Class java/lang/IllegalAccessException
EricLafortune commented 3 years ago

The problem is that ProGuard outputs e as Object instead of ReflectiveOperationException in the preverification attribute, which then upsets the java verifier.

Did ProGuard complain about not finding common superclasses, and did you then add ProGuard options -ignorewarnings or -dontwarn to get around these warnings? This could lead to problems if some classes are missing from the input. For example, Java 6 didn't have ReflectiveOperationException yet. Are you specifying the proper version of the Java runtime with -libraryjars?

ScottCowe commented 3 years ago

Adding the jmods to -libraryjars fixed the issue, thanks!