• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

raphw / byte-buddy / #801

27 Oct 2025 09:37AM UTC coverage: 84.715% (-0.4%) from 85.118%
#801

push

raphw
Fix imports.

29586 of 34924 relevant lines covered (84.72%)

0.85 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

80.79
/byte-buddy-dep/src/main/java/net/bytebuddy/utility/visitor/StackAwareMethodVisitor.java
1
/*
2
 * Copyright 2014 - Present Rafael Winterhalter
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16
package net.bytebuddy.utility.visitor;
17

18
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
19
import net.bytebuddy.build.AccessControllerPlugin;
20
import net.bytebuddy.description.method.MethodDescription;
21
import net.bytebuddy.implementation.bytecode.StackSize;
22
import net.bytebuddy.utility.CompoundList;
23
import net.bytebuddy.utility.OpenedClassReader;
24
import net.bytebuddy.utility.nullability.MaybeNull;
25
import net.bytebuddy.utility.privilege.GetSystemPropertyAction;
26
import org.objectweb.asm.Handle;
27
import org.objectweb.asm.Label;
28
import org.objectweb.asm.MethodVisitor;
29
import org.objectweb.asm.Opcodes;
30
import org.objectweb.asm.Type;
31

32
import java.security.PrivilegedAction;
33
import java.util.ArrayList;
34
import java.util.Collections;
35
import java.util.HashMap;
36
import java.util.List;
37
import java.util.ListIterator;
38
import java.util.Map;
39

40
/**
41
 * <p>
42
 * A method visitor that is aware of the current size of the operand stack at all times. Additionally, this method takes
43
 * care of maintaining an index for the next currently unused index of the local variable array.
44
 * </p>
45
 * <p>
46
 * <b>Important</b>: It is not always possible to apply this method visitor if it is applied to a class file compiled
47
 * for Java 5 or earlier, or if frames are computed by ASM and not passed to this visitor, if a method also contains
48
 * {@link Opcodes#GOTO} instructions. In the latter case, the stack is assumed empty after the instruction. If this
49
 * is a problem, stack adjustment can be disabled by setting {@link StackAwareMethodVisitor#UNADJUSTED_PROPERTY} to
50
 * {@code true}. With this setting, Byte Buddy does no longer attempt draining non-empty stacks and skips this visitor
51
 * in all cases. This might however lead to verification problems if stacks are left non-empty. As the latter happens
52
 * more common and since this visitor is applied defensively, using this wrapper is considered the more sensible default.
53
 * </p>
54
 */
55
public class StackAwareMethodVisitor extends MethodVisitor {
56

57
    /**
58
     * A property to disable stack adjustment. Stack adjustment is typically needed when instrumenting other
59
     * generated code that leaves excess values on the stack. This is also often the case when byte code
60
     * obfuscation is used.
61
     */
62
    public static final String UNADJUSTED_PROPERTY = "net.bytebuddy.unadjusted";
63

64
    /**
65
     * {@code true} if stack adjustment is disabled.
66
     */
67
    public static final boolean UNADJUSTED;
68

69
    /*
70
     * Reads the raw type property.
71
     */
72
    static {
73
        boolean disabled;
74
        try {
75
            disabled = Boolean.parseBoolean(doPrivileged(new GetSystemPropertyAction(UNADJUSTED_PROPERTY)));
1✔
76
        } catch (Exception ignored) {
×
77
            disabled = false;
×
78
        }
1✔
79
        UNADJUSTED = disabled;
1✔
80
    }
81

82
    /**
83
     * An array mapping any opcode to its size impact onto the operand stack. This mapping is taken from
84
     * {@link org.objectweb.asm.Frame} with the difference that the {@link Opcodes#JSR} instruction is
85
     * mapped to a size of {@code 0} as it does not impact the stack after returning from the instruction.
86
     */
87
    private static final int[] SIZE_CHANGE;
88

89
    /*
90
     * Computes a mapping of byte codes to their size impact onto the operand stack.
91
     */
92
    static {
93
        SIZE_CHANGE = new int[202];
1✔
94
        String encoded = "EFFFFFFFFGGFFFGGFFFEEFGFGFEEEEEEEEEEEEEEEEEEEEDEDEDDDDDCD" +
1✔
95
                "CDEEEEEEEEEEEEEEEEEEEEBABABBBBDCFFFGGGEDCDCDCDCDCDCDCDCDCDCEEEEDDD" +
96
                "DDDDCDCDCEFEFDDEEFFDEDEEEBDDBBDDDDDDCCCCCCCCEEEDDDCDCDEEEEEEEEEEFE" +
97
                "EEEEEDDEEDDEE";
98
        for (int index = 0; index < SIZE_CHANGE.length; index++) {
1✔
99
            SIZE_CHANGE[index] = encoded.charAt(index) - 'E';
1✔
100
        }
101
    }
1✔
102

103
    /**
104
     * A list of the current elements on the operand stack.
105
     */
106
    private List<StackSize> current;
107

108
    /**
109
     * A mapping of labels to the operand stack size that is expected at this label. Lists stored in this
110
     * map must not be mutated.
111
     */
112
    private final Map<Label, List<StackSize>> sizes;
113

114
    /**
115
     * The next index of the local variable array that is available.
116
     */
117
    private int freeIndex;
118

119
    /**
120
     * Creates a new stack aware method visitor.
121
     *
122
     * @param methodVisitor      The method visitor to delegate operations to.
123
     * @param instrumentedMethod The method description for which this method visitor is applied.
124
     */
125
    protected StackAwareMethodVisitor(MethodVisitor methodVisitor, MethodDescription instrumentedMethod) {
126
        super(OpenedClassReader.ASM_API, methodVisitor);
1✔
127
        current = new ArrayList<StackSize>();
1✔
128
        sizes = new HashMap<Label, List<StackSize>>();
1✔
129
        freeIndex = instrumentedMethod.getStackSize();
1✔
130
    }
1✔
131

132
    /**
133
     * Wraps the provided method visitor within a stack aware method visitor.
134
     *
135
     * @param methodVisitor      The method visitor to delegate operations to.
136
     * @param instrumentedMethod The method description for which this method visitor is applied.
137
     * @return An appropriate
138
     */
139
    public static MethodVisitor of(MethodVisitor methodVisitor, MethodDescription instrumentedMethod) {
140
        return UNADJUSTED
1✔
141
                ? methodVisitor
142
                : new StackAwareMethodVisitor(methodVisitor, instrumentedMethod);
143
    }
144

145
    /**
146
     * A proxy for {@code java.security.AccessController#doPrivileged} that is activated if available.
147
     *
148
     * @param action The action to execute from a privileged context.
149
     * @param <T>    The type of the action's resolved value.
150
     * @return The action's resolved value.
151
     */
152
    @AccessControllerPlugin.Enhance
153
    private static <T> T doPrivileged(PrivilegedAction<T> action) {
154
        return action.run();
×
155
    }
156

157
    /**
158
     * Adjusts the current state of the operand stack.
159
     *
160
     * @param delta The change of the current operation of the operand stack. Must not be larger than {@code 2}.
161
     */
162
    private void adjustStack(int delta) {
163
        adjustStack(delta, 0);
1✔
164
    }
1✔
165

166
    /**
167
     * Adjusts the current state of the operand stack.
168
     *
169
     * @param delta  The change of the current operation of the operand stack. Must not be larger than {@code 2}.
170
     * @param offset The offset of the value within the operand stack. Must be bigger then {@code 0} and smaller than
171
     *               the current stack size. Only permitted if the supplied {@code delta} is positive.
172
     */
173
    private void adjustStack(int delta, int offset) {
174
        if (delta > 2) {
1✔
175
            throw new IllegalStateException("Cannot push multiple values onto the operand stack: " + delta);
×
176
        } else if (delta > 0) {
1✔
177
            int position = current.size();
1✔
178
            // The operand stack can legally underflow while traversing dead code.
179
            while (offset > 0 && position > 0) {
1✔
180
                offset -= current.get(--position).getSize();
1✔
181
            }
182
            if (offset < 0) {
1✔
183
                throw new IllegalStateException("Unexpected offset underflow: " + offset);
×
184
            }
185
            current.add(position, StackSize.of(delta));
1✔
186
        } else if (offset != 0) {
1✔
187
            throw new IllegalStateException("Cannot specify non-zero offset " + offset + " for non-incrementing value: " + delta);
×
188
        } else {
189
            while (delta < 0) {
1✔
190
                // The operand stack can legally underflow while traversing dead code.
191
                if (current.isEmpty()) {
1✔
192
                    return;
1✔
193
                }
194
                delta += current.remove(current.size() - 1).getSize();
1✔
195
            }
196
            if (delta == 1) {
1✔
197
                current.add(StackSize.SINGLE);
1✔
198
            } else if (delta != 0) {
1✔
199
                throw new IllegalStateException("Unexpected remainder on the operand stack: " + delta);
×
200
            }
201
        }
202
    }
1✔
203

204
    /**
205
     * Pops all values currently on the stack.
206
     */
207
    public void drainStack() {
208
        doDrain(current);
1✔
209
    }
1✔
210

211
    /**
212
     * Drains the stack to only contain the top value. For this, the value on top of the stack is temporarily stored
213
     * in the local variable array until all values on the stack are popped off. Subsequently, the top value is pushed
214
     * back onto the operand stack.
215
     *
216
     * @param store The opcode used for storing the top value.
217
     * @param load  The opcode used for loading the top value.
218
     * @param size  The size of the value on top of the operand stack.
219
     * @return The minimal size of the local variable array that is required to perform the operation.
220
     */
221
    public int drainStack(int store, int load, StackSize size) {
222
        if (current.isEmpty()) {
1✔
223
            return 0;
1✔
224
        }
225
        int difference = current.get(current.size() - 1).getSize() - size.getSize();
1✔
226
        if (current.size() == 1 && difference == 0) {
1✔
227
            return 0;
1✔
228
        } else {
229
            super.visitVarInsn(store, freeIndex);
1✔
230
            if (difference == 1) {
1✔
231
                super.visitInsn(Opcodes.POP);
×
232
            } else if (difference != 0) {
1✔
233
                throw new IllegalStateException("Unexpected remainder on the operand stack: " + difference);
×
234
            }
235
            doDrain(current.subList(0, current.size() - 1));
1✔
236
            super.visitVarInsn(load, freeIndex);
1✔
237
            return freeIndex + size.getSize();
1✔
238
        }
239
    }
240

241
    /**
242
     * Drains all supplied elements of the operand stack.
243
     *
244
     * @param stackSizes The stack sizes of the elements to drain.
245
     */
246
    private void doDrain(List<StackSize> stackSizes) {
247
        ListIterator<StackSize> iterator = stackSizes.listIterator(stackSizes.size());
1✔
248
        while (iterator.hasPrevious()) {
1✔
249
            StackSize current = iterator.previous();
1✔
250
            switch (current) {
1✔
251
                case SINGLE:
252
                    super.visitInsn(Opcodes.POP);
1✔
253
                    break;
1✔
254
                case DOUBLE:
255
                    super.visitInsn(Opcodes.POP2);
1✔
256
                    break;
1✔
257
                default:
258
                    throw new IllegalStateException("Unexpected stack size: " + current);
×
259
            }
260
        }
1✔
261
    }
1✔
262

263
    /**
264
     * Explicitly registers a label to define a given stack state.
265
     *
266
     * @param label      The label to register a stack state for.
267
     * @param stackSizes The stack sizes to assume when reaching the supplied label.
268
     */
269
    public void register(Label label, List<StackSize> stackSizes) {
270
        sizes.put(label, stackSizes);
1✔
271
    }
1✔
272

273
    @Override
274
    public void visitInsn(int opcode) {
275
        switch (opcode) {
1✔
276
            case Opcodes.RETURN:
277
            case Opcodes.ARETURN:
278
            case Opcodes.IRETURN:
279
            case Opcodes.LRETURN:
280
            case Opcodes.FRETURN:
281
            case Opcodes.DRETURN:
282
            case Opcodes.ATHROW:
283
                current.clear();
1✔
284
                break;
1✔
285
            case Opcodes.DUP_X1:
286
            case Opcodes.DUP2_X1:
287
                adjustStack(SIZE_CHANGE[opcode], SIZE_CHANGE[opcode] + 1);
1✔
288
                break;
1✔
289
            case Opcodes.DUP_X2:
290
            case Opcodes.DUP2_X2:
291
                adjustStack(SIZE_CHANGE[opcode], SIZE_CHANGE[opcode] + 2);
1✔
292
                break;
1✔
293
            case Opcodes.D2I:
294
            case Opcodes.D2F:
295
            case Opcodes.L2F:
296
            case Opcodes.L2I:
297
                adjustStack(-2);
1✔
298
                adjustStack(1);
1✔
299
                break;
1✔
300
            case Opcodes.I2D:
301
            case Opcodes.I2L:
302
            case Opcodes.F2D:
303
            case Opcodes.F2L:
304
                adjustStack(-1);
1✔
305
                adjustStack(2);
1✔
306
                break;
1✔
307
            case Opcodes.LALOAD:
308
            case Opcodes.DALOAD:
309
                adjustStack(-2);
1✔
310
                adjustStack(+2);
1✔
311
                break;
1✔
312
            default:
313
                adjustStack(SIZE_CHANGE[opcode]);
1✔
314
        }
315
        super.visitInsn(opcode);
1✔
316
    }
1✔
317

318
    @Override
319
    public void visitIntInsn(int opcode, int operand) {
320
        adjustStack(SIZE_CHANGE[opcode]);
1✔
321
        super.visitIntInsn(opcode, operand);
1✔
322
    }
1✔
323

324
    @Override
325
    @SuppressFBWarnings(value = "SF_SWITCH_NO_DEFAULT", justification = "No action required on default option.")
326
    public void visitVarInsn(int opcode, int variable) {
327
        switch (opcode) {
1✔
328
            case Opcodes.ASTORE:
329
            case Opcodes.ISTORE:
330
            case Opcodes.FSTORE:
331
                freeIndex = Math.max(freeIndex, variable + 1);
1✔
332
                break;
1✔
333
            case Opcodes.LSTORE:
334
            case Opcodes.DSTORE:
335
                freeIndex = Math.max(freeIndex, variable + 2);
1✔
336
                break;
1✔
337
            case Opcodes.RET:
338
                current.clear();
1✔
339
                break;
340
        }
341
        adjustStack(SIZE_CHANGE[opcode]);
1✔
342
        super.visitVarInsn(opcode, variable);
1✔
343
    }
1✔
344

345
    @Override
346
    public void visitTypeInsn(int opcode, String type) {
347
        adjustStack(SIZE_CHANGE[opcode]);
1✔
348
        super.visitTypeInsn(opcode, type);
1✔
349
    }
1✔
350

351
    @Override
352
    public void visitFieldInsn(int opcode, String owner, String name, String descriptor) {
353
        int baseline = Type.getType(descriptor).getSize();
1✔
354
        switch (opcode) {
1✔
355
            case Opcodes.GETFIELD:
356
                adjustStack(-1);
1✔
357
                adjustStack(baseline);
1✔
358
                break;
1✔
359
            case Opcodes.GETSTATIC:
360
                adjustStack(baseline);
1✔
361
                break;
1✔
362
            case Opcodes.PUTFIELD:
363
                adjustStack(-baseline - 1);
1✔
364
                break;
1✔
365
            case Opcodes.PUTSTATIC:
366
                adjustStack(-baseline);
1✔
367
                break;
1✔
368
            default:
369
                throw new IllegalStateException("Unexpected opcode: " + opcode);
×
370
        }
371
        super.visitFieldInsn(opcode, owner, name, descriptor);
1✔
372
    }
1✔
373

374
    @Override
375
    public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
376
        int baseline = Type.getArgumentsAndReturnSizes(descriptor);
1✔
377
        adjustStack(-(baseline >> 2) + (opcode == Opcodes.INVOKESTATIC ? 1 : 0));
1✔
378
        adjustStack(baseline & 0x03);
1✔
379
        super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
1✔
380
    }
1✔
381

382
    @Override
383
    public void visitInvokeDynamicInsn(String name, String descriptor, Handle bootstrap, Object... bootstrapArguments) {
384
        int baseline = Type.getArgumentsAndReturnSizes(descriptor);
×
385
        adjustStack(-(baseline >> 2) + 1);
×
386
        adjustStack(baseline & 0x03);
×
387
        super.visitInvokeDynamicInsn(name, descriptor, bootstrap, bootstrapArguments);
×
388
    }
×
389

390
    @Override
391
    public void visitLdcInsn(Object value) {
392
        adjustStack((value instanceof Long || value instanceof Double) ? 2 : 1);
1✔
393
        super.visitLdcInsn(value);
1✔
394
    }
1✔
395

396
    @Override
397
    public void visitMultiANewArrayInsn(String descriptor, int dimension) {
398
        adjustStack(1 - dimension);
×
399
        super.visitMultiANewArrayInsn(descriptor, dimension);
×
400
    }
×
401

402
    @Override
403
    public void visitJumpInsn(int opcode, Label label) {
404
        adjustStack(SIZE_CHANGE[opcode]);
1✔
405
        sizes.put(label, new ArrayList<StackSize>(opcode == Opcodes.JSR
1✔
406
                ? CompoundList.of(current, StackSize.SINGLE)
1✔
407
                : current));
408
        if (opcode == Opcodes.GOTO) {
1✔
409
            current.clear();
1✔
410
        }
411
        super.visitJumpInsn(opcode, label);
1✔
412
    }
1✔
413

414
    @Override
415
    public void visitLabel(Label label) {
416
        List<StackSize> current = sizes.get(label);
1✔
417
        if (current != null) {
1✔
418
            this.current = new ArrayList<StackSize>(current);
1✔
419
        }
420
        super.visitLabel(label);
1✔
421
    }
1✔
422

423
    @Override
424
    public void visitTableSwitchInsn(int minimum, int maximum, Label defaultOption, Label... option) {
425
        adjustStack(-1);
×
426
        List<StackSize> current = new ArrayList<StackSize>(this.current);
×
427
        sizes.put(defaultOption, current);
×
428
        for (Label label : option) {
×
429
            sizes.put(label, current);
×
430
        }
431
        super.visitTableSwitchInsn(minimum, maximum, defaultOption, option);
×
432
    }
×
433

434
    @Override
435
    public void visitLookupSwitchInsn(Label defaultOption, int[] key, Label[] option) {
436
        adjustStack(-1);
×
437
        List<StackSize> current = new ArrayList<StackSize>(this.current);
×
438
        sizes.put(defaultOption, current);
×
439
        for (Label label : option) {
×
440
            sizes.put(label, current);
×
441
        }
442
        super.visitLookupSwitchInsn(defaultOption, key, option);
×
443
    }
×
444

445
    @Override
446
    public void visitTryCatchBlock(Label start, Label end, Label handler, @MaybeNull String type) {
447
        sizes.put(handler, Collections.singletonList(StackSize.SINGLE));
1✔
448
        super.visitTryCatchBlock(start, end, handler, type);
1✔
449
    }
1✔
450

451
    @Override
452
    @SuppressFBWarnings(value = "RC_REF_COMPARISON_BAD_PRACTICE", justification = "ASM models frames by reference identity.")
453
    public void visitFrame(int type, int localVariableLength, @MaybeNull Object[] localVariable, int stackSize, @MaybeNull Object[] stack) {
454
        switch (type) {
1✔
455
            case Opcodes.F_SAME:
456
            case Opcodes.F_CHOP:
457
            case Opcodes.F_APPEND:
458
                current.clear();
1✔
459
                break;
1✔
460
            case Opcodes.F_SAME1:
461
                current.clear();
1✔
462
                if (stack[0] == Opcodes.LONG || stack[0] == Opcodes.DOUBLE) {
1✔
463
                    current.add(StackSize.DOUBLE);
1✔
464
                } else {
465
                    current.add(StackSize.SINGLE);
1✔
466
                }
467
                break;
1✔
468
            case Opcodes.F_NEW:
469
            case Opcodes.F_FULL:
470
                current.clear();
1✔
471
                for (int index = 0; index < stackSize; index++) {
1✔
472
                    if (stack[index] == Opcodes.LONG || stack[index] == Opcodes.DOUBLE) {
1✔
473
                        current.add(StackSize.DOUBLE);
1✔
474
                    } else {
475
                        current.add(StackSize.SINGLE);
1✔
476
                    }
477
                }
478
                break;
1✔
479
            default:
480
                throw new IllegalStateException("Unknown frame type: " + type);
×
481
        }
482
        super.visitFrame(type, localVariableLength, localVariable, stackSize, stack);
1✔
483
    }
1✔
484
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc