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:
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:
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).