COMPUTE_MAXS is deleting a StackMapTable

212 Views Asked by At

I am using the open-source library Btrace to profile a Java application.

I have been messing with it because I have found a few bugs, and while trying to fix them I have come upon a mysterious occurrence.

Essentially, I am using a ClassWriter with the flag COMPUTE_MAXS and my intention is not to mess with the StackMapTables of existing methods (under any circumstance)

However, it appears that although it may not be recalculating the StackMapTables (which COMPUTE_FRAMES would do) in a few cases an actual StackMapTable is "missing"

Here's an example.

Original Code:

/*
 * This file is part of Sponge, licensed under the MIT License (MIT).
 *
 * Copyright (c) SpongePowered <https://www.spongepowered.org>
 * Copyright (c) contributors
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package org.spongepowered.common.command;

import static com.google.common.base.Preconditions.checkNotNull;

import net.minecraft.command.ICommandSender;
import net.minecraft.util.math.Vec3d;
import org.spongepowered.api.command.CommandSource;
import org.spongepowered.api.world.Locatable;
import org.spongepowered.api.service.permission.MemorySubjectData;
import org.spongepowered.api.service.permission.PermissionService;
import org.spongepowered.api.service.permission.SubjectCollection;
import org.spongepowered.api.text.Text;
import org.spongepowered.api.text.channel.MessageChannel;
import org.spongepowered.api.world.Location;
import org.spongepowered.api.world.World;
import org.spongepowered.common.SpongeImpl;
import org.spongepowered.common.interfaces.IMixinCommandSender;
import org.spongepowered.common.service.permission.SpongePermissionService;
import org.spongepowered.common.service.permission.base.SpongeSubject;
import org.spongepowered.common.text.SpongeTexts;
import org.spongepowered.common.util.VecHelper;

import java.util.Optional;

public class WrapperCommandSource extends SpongeSubject implements CommandSource {

    final ICommandSender sender;
    private final MemorySubjectData data;

    private WrapperCommandSource(ICommandSender sender) {
        this.sender = sender;
        this.data = new MemorySubjectData(SpongeImpl.getGame().getServiceManager().provide(PermissionService.class).get());
        CommandPermissions.populateMinecraftPermissions(sender, data);
    }

    @Override
    public String getIdentifier() {
        return this.sender.getName();
    }

    @Override
    public Optional<CommandSource> getCommandSource() {
        return Optional.of((CommandSource) this);
    }

    @Override
    public SubjectCollection getContainingCollection() {
        SpongePermissionService permission = (SpongePermissionService) SpongeImpl.getGame().getServiceManager().provide(PermissionService.class).get();
        return permission.getSubjects(PermissionService.SUBJECTS_SYSTEM);
    }

    @Override
    public MemorySubjectData getSubjectData() {
        return this.data;
    }

    @Override
    public String getName() {
        return this.sender.getName();
    }

    @Override
    public void sendMessage(Text message) {
        checkNotNull(message, "message");
        this.sender.sendMessage(SpongeTexts.toComponent(message));
    }

    @Override
    public MessageChannel getMessageChannel() {
        return SpongeImpl.getGame().getServer().getBroadcastChannel();
    }

    @Override
    public void setMessageChannel(MessageChannel channel) {
        // TODO Should I throw an exception here?
    }

    public static CommandSource of(ICommandSender sender) {
        if (sender instanceof IMixinCommandSender) {
            return ((IMixinCommandSender) sender).asCommandSource();
        }
        if (sender instanceof WrapperICommandSender) {
            return ((WrapperICommandSender) sender).source;
        }
        if (sender.getCommandSenderEntity() != null || !Vec3d.ZERO.equals(sender.getPositionVector())) {
            return new Located(sender);
        }
        return new WrapperCommandSource(sender);
    }

    public static class Located extends WrapperCommandSource implements Locatable {

        Located(ICommandSender sender) {
            super(sender);
        }

        @Override
        public Location<World> getLocation() {
            return new Location<>((World) this.sender.getEntityWorld(), VecHelper.toVector3d(this.sender.getPositionVector()));
        }

    }

}

Using the command javap -p -v -c <class> I am looking at the StackMapTables of every method in a class - sadly the full output is too long to put here, but I'll put the point I want to focus on, essentially the constructor of the class.

Original Constructor:

private org.spongepowered.common.command.WrapperCommandSource(net.minecraft.command.ICommandSender);
    descriptor: (Lnet/minecraft/command/ICommandSender;)V
    flags: ACC_PRIVATE
    Code:
      stack=6, locals=5, args_size=2
         0: aload_0
         1: invokespecial #26                 // Method org/spongepowered/common/service/permission/base/SpongeSubject."<init>":()V
         4: aload_0
         5: aload_1
         6: putfield      #28                 // Field sender:Lnet/minecraft/command/ICommandSender;
         9: aload_0
        10: new           #30                 // class org/spongepowered/api/service/permission/MemorySubjectData
        13: dup
        14: invokestatic  #36                 // Method org/spongepowered/common/SpongeImpl.getGame:()Lorg/spongepowered/common/SpongeGame;
        17: invokevirtual #42                 // Method org/spongepowered/common/SpongeGame.getServiceManager:()Lorg/spongepowered/api/service/ServiceManager;
        20: ldc           #44                 // class org/spongepowered/api/service/permission/PermissionService
        22: invokeinterface #50,  2           // InterfaceMethod org/spongepowered/api/service/ServiceManager.provide:(Ljava/lang/Class;)Ljava/util/Optional;
        27: invokevirtual #56                 // Method java/util/Optional.get:()Ljava/lang/Object;
        30: checkcast     #44                 // class org/spongepowered/api/service/permission/PermissionService
        33: invokespecial #59                 // Method org/spongepowered/api/service/permission/MemorySubjectData."<init>":(Lorg/spongepowered/api/service/permission/PermissionService;)V
        36: putfield      #61                 // Field data:Lorg/spongepowered/api/service/permission/MemorySubjectData;
        39: aload_0
        40: getfield      #61                 // Field data:Lorg/spongepowered/api/service/permission/MemorySubjectData;
        43: aload_0
        44: getfield      #28                 // Field sender:Lnet/minecraft/command/ICommandSender;
        47: dup
        48: invokevirtual #67                 // Method java/lang/Object.getClass:()Ljava/lang/Class;
        51: pop
        52: invokedynamic #89,  0             // InvokeDynamic #0:apply:(Lnet/minecraft/command/ICommandSender;)Ljava/util/function/BiFunction;
        57: invokestatic  #95                 // Method org/spongepowered/common/command/CommandPermissions.populateNonCommandPermissions:(Lorg/spongepowered/api/service/permission/SubjectData;Ljava/util/function/BiFunction;)V
        60: invokestatic  #36                 // Method org/spongepowered/common/SpongeImpl.getGame:()Lorg/spongepowered/common/SpongeGame;
        63: invokevirtual #99                 // Method org/spongepowered/common/SpongeGame.getCommandManager:()Lorg/spongepowered/api/command/CommandManager;
        66: invokeinterface #105,  1          // InterfaceMethod org/spongepowered/api/command/CommandManager.getCommands:()Ljava/util/Set;
        71: invokeinterface #111,  1          // InterfaceMethod java/util/Set.iterator:()Ljava/util/Iterator;
        76: astore_2
        77: aload_2
        78: invokeinterface #117,  1          // InterfaceMethod java/util/Iterator.hasNext:()Z
        83: ifeq          158
        86: aload_2
        87: invokeinterface #120,  1          // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
        92: checkcast     #122                // class org/spongepowered/api/command/CommandMapping
        95: astore_3
        96: aload_3
        97: invokeinterface #126,  1          // InterfaceMethod org/spongepowered/api/command/CommandMapping.getCallable:()Lorg/spongepowered/api/command/CommandCallable;
       102: instanceof    #128                // class org/spongepowered/common/command/MinecraftCommandWrapper
       105: ifeq          155
       108: aload_3
       109: invokeinterface #126,  1          // InterfaceMethod org/spongepowered/api/command/CommandMapping.getCallable:()Lorg/spongepowered/api/command/CommandCallable;
       114: checkcast     #128                // class org/spongepowered/common/command/MinecraftCommandWrapper
       117: astore        4
       119: aload_0
       120: getfield      #61                 // Field data:Lorg/spongepowered/api/service/permission/MemorySubjectData;
       123: getstatic     #134                // Field org/spongepowered/api/service/permission/SubjectData.GLOBAL_CONTEXT:Ljava/util/Set;
       126: aload         4
       128: invokevirtual #138                // Method org/spongepowered/common/command/MinecraftCommandWrapper.getCommandPermission:()Ljava/lang/String;
       131: aload         4
       133: getfield      #142                // Field org/spongepowered/common/command/MinecraftCommandWrapper.command:Lnet/minecraft/command/ICommand;
       136: aload_1
       137: invokeinterface #146,  1          // InterfaceMethod net/minecraft/command/ICommandSender.func_184102_h:()Lnet/minecraft/server/MinecraftServer;
       142: aload_1
       143: invokeinterface #152,  3          // InterfaceMethod net/minecraft/command/ICommand.func_184882_a:(Lnet/minecraft/server/MinecraftServer;Lnet/minecraft/command/ICommandSender;)Z
       148: invokestatic  #158                // Method org/spongepowered/api/util/Tristate.fromBoolean:(Z)Lorg/spongepowered/api/util/Tristate;
       151: invokevirtual #162                // Method org/spongepowered/api/service/permission/MemorySubjectData.setPermission:(Ljava/util/Set;Ljava/lang/String;Lorg/spongepowered/api/util/Tristate;)Z
       154: pop
       155: goto          77
       158: return
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
          119      36     4 wrapper   Lorg/spongepowered/common/command/MinecraftCommandWrapper;
           96      59     3 command   Lorg/spongepowered/api/command/CommandMapping;
            0     159     0  this   Lorg/spongepowered/common/command/WrapperCommandSource;
            0     159     1 sender   Lnet/minecraft/command/ICommandSender;
      LineNumberTable:
        line 56: 0
        line 57: 4
        line 58: 9
        line 62: 39
        line 63: 60
        line 64: 96
        line 65: 108
        line 66: 119
        line 67: 137
        line 66: 151
        line 69: 155
        line 70: 158
      StackMapTable: number_of_entries = 3
        frame_type = 255 /* full_frame */
          offset_delta = 77
          locals = [ class org/spongepowered/common/command/WrapperCommandSource, class net/minecraft/command/ICommandSender, class java/util/Iterator ]
          stack = []
        frame_type = 251 /* same_frame_extended */
          offset_delta = 77
        frame_type = 250 /* chop */
          offset_delta = 2
    MethodParameters:
      Name                           Flags
      sender

Transformed Constructor:

private org.spongepowered.common.command.WrapperCommandSource(net.minecraft.command.ICommandSender);
    descriptor: (Lnet/minecraft/command/ICommandSender;)V
    flags: ACC_PRIVATE
    Code:
      stack=6, locals=9, args_size=2
         0: aload_0
         1: invokespecial #26                 // Method org/spongepowered/common/service/permission/base/SpongeSubject."<init>":()V
         4: ldc_w         #282                // String private void org.spongepowered.common.command.WrapperCommandSource#<init>(net.minecraft.command.ICommandSender)
         7: invokestatic  #286                // Method $btrace$com$negafinity$btrace$Logger$entry:(Ljava/lang/String;)V
        10: lconst_0
        11: lstore_2
        12: invokestatic  #292                // Method java/lang/System.nanoTime:()J
        15: lstore        4
        17: aload_0
        18: aload_1
        19: putfield      #28                 // Field sender:Lnet/minecraft/command/ICommandSender;
        22: aload_0
        23: new           #30                 // class org/spongepowered/api/service/permission/MemorySubjectData
        26: dup
        27: invokestatic  #36                 // Method org/spongepowered/common/SpongeImpl.getGame:()Lorg/spongepowered/common/SpongeGame;
        30: invokevirtual #42                 // Method org/spongepowered/common/SpongeGame.getServiceManager:()Lorg/spongepowered/api/service/ServiceManager;
        33: ldc           #44                 // class org/spongepowered/api/service/permission/PermissionService
        35: invokeinterface #50,  2           // InterfaceMethod org/spongepowered/api/service/ServiceManager.provide:(Ljava/lang/Class;)Ljava/util/Optional;
        40: invokevirtual #56                 // Method java/util/Optional.get:()Ljava/lang/Object;
        43: checkcast     #44                 // class org/spongepowered/api/service/permission/PermissionService
        46: invokespecial #59                 // Method org/spongepowered/api/service/permission/MemorySubjectData."<init>":(Lorg/spongepowered/api/service/permission/PermissionService;)V
        49: putfield      #61                 // Field data:Lorg/spongepowered/api/service/permission/MemorySubjectData;
        52: aload_0
        53: getfield      #61                 // Field data:Lorg/spongepowered/api/service/permission/MemorySubjectData;
        56: aload_0
        57: getfield      #28                 // Field sender:Lnet/minecraft/command/ICommandSender;
        60: dup
        61: invokevirtual #67                 // Method java/lang/Object.getClass:()Ljava/lang/Class;
        64: pop
        65: invokedynamic #89,  0             // InvokeDynamic #0:apply:(Lnet/minecraft/command/ICommandSender;)Ljava/util/function/BiFunction;
        70: invokestatic  #95                 // Method org/spongepowered/common/command/CommandPermissions.populateNonCommandPermissions:(Lorg/spongepowered/api/service/permission/SubjectData;Ljava/util/function/BiFunction;)V
        73: invokestatic  #36                 // Method org/spongepowered/common/SpongeImpl.getGame:()Lorg/spongepowered/common/SpongeGame;
        76: invokevirtual #99                 // Method org/spongepowered/common/SpongeGame.getCommandManager:()Lorg/spongepowered/api/command/CommandManager;
        79: invokeinterface #105,  1          // InterfaceMethod org/spongepowered/api/command/CommandManager.getCommands:()Ljava/util/Set;
        84: invokeinterface #111,  1          // InterfaceMethod java/util/Set.iterator:()Ljava/util/Iterator;
        89: astore        6
        91: aload         6
        93: invokeinterface #117,  1          // InterfaceMethod java/util/Iterator.hasNext:()Z
        98: ifeq          177
       101: aload         6
       103: invokeinterface #120,  1          // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
       108: checkcast     #122                // class org/spongepowered/api/command/CommandMapping
       111: astore        7
       113: aload         7
       115: invokeinterface #126,  1          // InterfaceMethod org/spongepowered/api/command/CommandMapping.getCallable:()Lorg/spongepowered/api/command/CommandCallable;
       120: instanceof    #128                // class org/spongepowered/common/command/MinecraftCommandWrapper
       123: ifeq          174
       126: aload         7
       128: invokeinterface #126,  1          // InterfaceMethod org/spongepowered/api/command/CommandMapping.getCallable:()Lorg/spongepowered/api/command/CommandCallable;
       133: checkcast     #128                // class org/spongepowered/common/command/MinecraftCommandWrapper
       136: astore        8
       138: aload_0
       139: getfield      #61                 // Field data:Lorg/spongepowered/api/service/permission/MemorySubjectData;
       142: getstatic     #134                // Field org/spongepowered/api/service/permission/SubjectData.GLOBAL_CONTEXT:Ljava/util/Set;
       145: aload         8
       147: invokevirtual #138                // Method org/spongepowered/common/command/MinecraftCommandWrapper.getCommandPermission:()Ljava/lang/String;
       150: aload         8
       152: getfield      #142                // Field org/spongepowered/common/command/MinecraftCommandWrapper.command:Lnet/minecraft/command/ICommand;
       155: aload_1
       156: invokeinterface #146,  1          // InterfaceMethod net/minecraft/command/ICommandSender.func_184102_h:()Lnet/minecraft/server/MinecraftServer;
       161: aload_1
       162: invokeinterface #152,  3          // InterfaceMethod net/minecraft/command/ICommand.func_184882_a:(Lnet/minecraft/server/MinecraftServer;Lnet/minecraft/command/ICommandSender;)Z
       167: invokestatic  #158                // Method org/spongepowered/api/util/Tristate.fromBoolean:(Z)Lorg/spongepowered/api/util/Tristate;
       170: invokevirtual #162                // Method org/spongepowered/api/service/permission/MemorySubjectData.setPermission:(Ljava/util/Set;Ljava/lang/String;Lorg/spongepowered/api/util/Tristate;)Z
       173: pop
       174: goto          91
       177: invokestatic  #292                // Method java/lang/System.nanoTime:()J
       180: lload         4
       182: lsub
       183: lstore_2
       184: ldc_w         #282                // String private void org.spongepowered.common.command.WrapperCommandSource#<init>(net.minecraft.command.ICommandSender)
       187: lload_2
       188: invokestatic  #296                // Method $btrace$com$negafinity$btrace$Logger$exit:(Ljava/lang/String;J)V
       191: return
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
          138      36     8 wrapper   Lorg/spongepowered/common/command/MinecraftCommandWrapper;
          113      61     7 command   Lorg/spongepowered/api/command/CommandMapping;
            0     192     0  this   Lorg/spongepowered/common/command/WrapperCommandSource;
            0     192     1 sender   Lnet/minecraft/command/ICommandSender;
      LineNumberTable:
        line 56: 0
        line 57: 17
        line 58: 22
        line 62: 52
        line 63: 73
        line 64: 113
        line 65: 126
        line 66: 138
        line 67: 156
        line 66: 170
        line 69: 174
        line 70: 177
    MethodParameters:
      Name                           Flags
      sender

It's not easy to see, but after running both outputs through a text diff tool, I found this:

enter image description here

Clearly it is removing a whole StackMapTable. My question is why/how would this happen?

I researched around about the COMPUTE_MAXS flag and I'm not sure whether this is intended behavior, or if this is happening because of something else.

Thanks!

Update:

I forgot to put the error that alerted me to the issue:

java.lang.VerifyError: Expecting a stackmap frame at branch target 177
Exception Details:
  Location:
    org/spongepowered/common/command/WrapperCommandSource.<init>(Lnet/minecraft/command/ICommandSender;)V @98: ifeq
  Reason:
    Expected stackmap frame at this location.
  Bytecode:
    0x0000000: 2ab7 001a 1301 1ab8 011e 0941 b801 2437
    0x0000010: 042a 2bb5 001c 2abb 001e 59b8 0024 b600
    0x0000020: 2a12 2cb9 0032 0200 b600 38c0 002c b700
    0x0000030: 3bb5 003d 2ab4 003d 2ab4 001c 59b6 0043
    0x0000040: 57ba 0059 0000 b800 5fb8 0024 b600 63b9
    0x0000050: 0069 0100 b900 6f01 003a 0619 06b9 0075
    0x0000060: 0100 9900 4f19 06b9 0078 0100 c000 7a3a
    0x0000070: 0719 07b9 007e 0100 c100 8099 0033 1907
    0x0000080: b900 7e01 00c0 0080 3a08 2ab4 003d b200
    0x0000090: 8619 08b6 008a 1908 b400 8e2b b900 9201
    0x00000a0: 002b b900 9803 00b8 009e b600 a257 a7ff
    0x00000b0: adb8 0124 1604 6541 1301 1a20 b801 28b1
    0x00000c0:
1

There are 1 best solutions below

0
On

Very late answer, but hopefully will be useful for future readers with regards to StackMapTable removal.

In my company we recently faced a similar issue, in which MAX values (specifically max_stack) were wrongly calculated for some instrumented methods, which eventually resulted in JVM GC crashes.

What first appeared as an ASM issue, turned out to be a JVM issue (still to be confirmed).
We noticed that the class bytes are missing StackMapTable's when classes are retransformed (instrumented after already being loaded), as opposed to classes instrumented when they are first loaded.

On Java 7+, ASM will attempt to calculate MAXs based on the existing stackframes, always assuming they are there. When StackMapTables are missing, ASM may emit bad max_stack values.

So, it is possible that it isn't ASM that's omitting the StackMapTables, but the JVM itself.

Interestengly, this issue doesn't occur on IBM J9 VMs - only on HotSpot (OpenJDK).