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

hazendaz / jmockit1 / 496

15 Nov 2025 05:33PM UTC coverage: 72.192% (-0.008%) from 72.2%
496

push

github

web-flow
Merge pull request #412 from hazendaz/renovate/major-spring-core

Update spring core to v7 (major)

5677 of 8360 branches covered (67.91%)

Branch coverage included in aggregate %.

11922 of 16018 relevant lines covered (74.43%)

0.74 hits per line

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

89.79
/main/src/main/java/mockit/internal/expectations/transformation/InvocationBlockModifier.java
1
/*
2
 * MIT License
3
 * Copyright (c) 2006-2025 JMockit developers
4
 * See LICENSE file for full license text.
5
 */
6
package mockit.internal.expectations.transformation;
7

8
import static mockit.asm.jvmConstants.Opcodes.ALOAD;
9
import static mockit.asm.jvmConstants.Opcodes.DCONST_0;
10
import static mockit.asm.jvmConstants.Opcodes.FCONST_0;
11
import static mockit.asm.jvmConstants.Opcodes.GETFIELD;
12
import static mockit.asm.jvmConstants.Opcodes.GETSTATIC;
13
import static mockit.asm.jvmConstants.Opcodes.ICONST_0;
14
import static mockit.asm.jvmConstants.Opcodes.INVOKESTATIC;
15
import static mockit.asm.jvmConstants.Opcodes.INVOKEVIRTUAL;
16
import static mockit.asm.jvmConstants.Opcodes.LCONST_0;
17
import static mockit.asm.jvmConstants.Opcodes.NEW;
18
import static mockit.asm.jvmConstants.Opcodes.NEWARRAY;
19
import static mockit.asm.jvmConstants.Opcodes.POP;
20
import static mockit.asm.jvmConstants.Opcodes.PUTFIELD;
21
import static mockit.asm.jvmConstants.Opcodes.PUTSTATIC;
22
import static mockit.asm.jvmConstants.Opcodes.RETURN;
23
import static mockit.internal.util.TypeConversionBytecode.isBoxing;
24
import static mockit.internal.util.TypeConversionBytecode.isUnboxing;
25

26
import edu.umd.cs.findbugs.annotations.NonNull;
27
import edu.umd.cs.findbugs.annotations.Nullable;
28

29
import mockit.asm.controlFlow.Label;
30
import mockit.asm.jvmConstants.JVMInstruction;
31
import mockit.asm.methods.MethodWriter;
32
import mockit.asm.methods.WrappingMethodVisitor;
33
import mockit.asm.types.JavaType;
34

35
import org.checkerframework.checker.index.qual.NonNegative;
36

37
public final class InvocationBlockModifier extends WrappingMethodVisitor {
38
    private static final String CLASS_DESC = "mockit/internal/expectations/ActiveInvocations";
39

40
    // Input data:
41
    @NonNull
42
    private final String blockOwner;
43

44
    // Keeps track of the current stack size (after each bytecode instruction) within the invocation block:
45
    @NonNegative
46
    private int stackSize;
47

48
    // Handle withCapture()/anyXyz/withXyz matchers, if any:
49
    @NonNull
50
    final ArgumentMatching argumentMatching;
51
    @NonNull
52
    final ArgumentCapturing argumentCapturing;
53
    private boolean justAfterWithCaptureInvocation;
54

55
    // Stores the index of the local variable holding a list passed in a withCapture(List) call, if any:
56
    @NonNegative
57
    private int lastLoadedVarIndex;
58

59
    InvocationBlockModifier(@NonNull MethodWriter mw, @NonNull String blockOwner) {
60
        super(mw);
1✔
61
        this.blockOwner = blockOwner;
1✔
62
        argumentMatching = new ArgumentMatching(this);
1✔
63
        argumentCapturing = new ArgumentCapturing(this);
1✔
64
    }
1✔
65

66
    void generateCallToActiveInvocationsMethod(@NonNull String name) {
67
        mw.visitMethodInsn(INVOKESTATIC, CLASS_DESC, name, "()V", false);
1✔
68
    }
1✔
69

70
    void generateCallToActiveInvocationsMethod(@NonNull String name, @NonNull String desc) {
71
        visitMethodInstruction(INVOKESTATIC, CLASS_DESC, name, desc, false);
1✔
72
    }
1✔
73

74
    @Override
75
    public void visitFieldInsn(@NonNegative int opcode, @NonNull String owner, @NonNull String name,
76
            @NonNull String desc) {
77
        boolean getField = opcode == GETFIELD;
1✔
78

79
        if ((getField || opcode == PUTFIELD) && blockOwner.equals(owner)) {
1✔
80
            if (name.indexOf('$') >= 1) {
1✔
81
                // Nothing to do.
82
            } else if (getField && ArgumentMatching.isAnyField(name)) {
1✔
83
                argumentMatching.generateCodeToAddArgumentMatcherForAnyField(owner, name, desc);
1✔
84
                argumentMatching.addMatcher(stackSize);
1✔
85
                return;
1✔
86
            } else if (!getField && generateCodeThatReplacesAssignmentToSpecialField(name)) {
1✔
87
                visitInsn(POP);
1✔
88
                return;
1✔
89
            }
90
        }
91

92
        stackSize += stackSizeVariationForFieldAccess(opcode, desc);
1✔
93
        mw.visitFieldInsn(opcode, owner, name, desc);
1✔
94
    }
1✔
95

96
    private boolean generateCodeThatReplacesAssignmentToSpecialField(@NonNull String fieldName) {
97
        if ("result".equals(fieldName)) {
1✔
98
            generateCallToActiveInvocationsMethod("addResult", "(Ljava/lang/Object;)V");
1✔
99
            return true;
1✔
100
        }
101

102
        if ("times".equals(fieldName) || "minTimes".equals(fieldName) || "maxTimes".equals(fieldName)) {
1✔
103
            generateCallToActiveInvocationsMethod(fieldName, "(I)V");
1✔
104
            return true;
1✔
105
        }
106

107
        return false;
1✔
108
    }
109

110
    private static int stackSizeVariationForFieldAccess(@NonNegative int opcode, @NonNull String fieldType) {
111
        char c = fieldType.charAt(0);
1✔
112
        boolean twoByteType = c == 'D' || c == 'J';
1!
113

114
        switch (opcode) {
1!
115
            case GETSTATIC:
116
                return twoByteType ? 2 : 1;
1!
117
            case PUTSTATIC:
118
                return twoByteType ? -2 : -1;
×
119
            case GETFIELD:
120
                return twoByteType ? 1 : 0;
1!
121
            case PUTFIELD:
122
                return twoByteType ? -3 : -2;
1!
123
            default:
124
                throw new IllegalArgumentException("Invalid field access opcode: " + opcode);
×
125
        }
126
    }
127

128
    @Override
129
    public void visitMethodInsn(@NonNegative int opcode, @NonNull String owner, @NonNull String name,
130
            @NonNull String desc, boolean itf) {
131
        if (opcode == INVOKESTATIC && (isBoxing(owner, name, desc) || isAccessMethod(owner, name))) {
1!
132
            // It's an invocation to a primitive boxing method or to a synthetic method for private access, just ignore
133
            // it.
134
            visitMethodInstruction(INVOKESTATIC, owner, name, desc, itf);
1✔
135
        } else if (isCallToArgumentMatcher(opcode, owner, name, desc)) {
1✔
136
            visitMethodInstruction(INVOKEVIRTUAL, owner, name, desc, itf);
1✔
137

138
            boolean withCaptureMethod = "withCapture".equals(name);
1✔
139

140
            if (argumentCapturing.registerMatcher(withCaptureMethod, desc, lastLoadedVarIndex)) {
1✔
141
                justAfterWithCaptureInvocation = withCaptureMethod;
1✔
142
                argumentMatching.addMatcher(stackSize);
1✔
143
            }
144
        } else if (isUnboxing(opcode, owner, desc)) {
1✔
145
            if (justAfterWithCaptureInvocation) {
1✔
146
                generateCodeToReplaceNullWithZeroOnTopOfStack(desc);
1✔
147
                justAfterWithCaptureInvocation = false;
1✔
148
            } else {
149
                visitMethodInstruction(opcode, owner, name, desc, itf);
1✔
150
            }
151
        } else {
152
            handleMockedOrNonMockedInvocation(opcode, owner, name, desc, itf);
1✔
153
        }
154
    }
1✔
155

156
    private boolean isAccessMethod(@NonNull String methodOwner, @NonNull String name) {
157
        return !methodOwner.equals(blockOwner) && name.startsWith("access$");
1!
158
    }
159

160
    private void visitMethodInstruction(@NonNegative int opcode, @NonNull String owner, @NonNull String name,
161
            @NonNull String desc, boolean itf) {
162
        if (!"()V".equals(desc)) {
1✔
163
            int argAndRetSize = JavaType.getArgumentsAndReturnSizes(desc);
1✔
164
            int argSize = argAndRetSize >> 2;
1✔
165

166
            if (opcode == INVOKESTATIC) {
1✔
167
                argSize--;
1✔
168
            }
169

170
            stackSize -= argSize;
1✔
171

172
            int retSize = argAndRetSize & 0x03;
1✔
173
            stackSize += retSize;
1✔
174
        } else if (opcode != INVOKESTATIC) {
1✔
175
            stackSize--;
1✔
176
        }
177

178
        mw.visitMethodInsn(opcode, owner, name, desc, itf);
1✔
179
    }
1✔
180

181
    private boolean isCallToArgumentMatcher(@NonNegative int opcode, @NonNull String owner, @NonNull String name,
182
            @NonNull String desc) {
183
        return opcode == INVOKEVIRTUAL && owner.equals(blockOwner)
1✔
184
                && ArgumentMatching.isCallToArgumentMatcher(name, desc);
1✔
185
    }
186

187
    private void generateCodeToReplaceNullWithZeroOnTopOfStack(@NonNull String unboxingMethodDesc) {
188
        visitInsn(POP);
1✔
189

190
        char primitiveTypeCode = unboxingMethodDesc.charAt(2);
1✔
191
        int zeroOpcode;
192

193
        switch (primitiveTypeCode) {
1✔
194
            case 'J':
195
                zeroOpcode = LCONST_0;
1✔
196
                break;
1✔
197
            case 'F':
198
                zeroOpcode = FCONST_0;
1✔
199
                break;
1✔
200
            case 'D':
201
                zeroOpcode = DCONST_0;
1✔
202
                break;
1✔
203
            default:
204
                zeroOpcode = ICONST_0;
1✔
205
        }
206

207
        visitInsn(zeroOpcode);
1✔
208
    }
1✔
209

210
    private void handleMockedOrNonMockedInvocation(@NonNegative int opcode, @NonNull String owner, @NonNull String name,
211
            @NonNull String desc, boolean itf) {
212
        if (argumentMatching.getMatcherCount() == 0) {
1✔
213
            visitMethodInstruction(opcode, owner, name, desc, itf);
1✔
214
        } else {
215
            boolean mockedInvocationUsingTheMatchers = argumentMatching.handleInvocationParameters(stackSize, desc);
1✔
216
            visitMethodInstruction(opcode, owner, name, desc, itf);
1✔
217
            handleArgumentCapturingIfNeeded(mockedInvocationUsingTheMatchers);
1✔
218
        }
219
    }
1✔
220

221
    private void handleArgumentCapturingIfNeeded(boolean mockedInvocationUsingTheMatchers) {
222
        if (mockedInvocationUsingTheMatchers) {
1✔
223
            argumentCapturing.generateCallsToCaptureMatchedArgumentsIfPending();
1✔
224
        }
225

226
        justAfterWithCaptureInvocation = false;
1✔
227
    }
1✔
228

229
    @Override
230
    public void visitLabel(@NonNull Label label) {
231
        mw.visitLabel(label);
1✔
232

233
        if (!label.isDebug()) {
1✔
234
            stackSize = 0;
1✔
235
        }
236
    }
1✔
237

238
    @Override
239
    public void visitTypeInsn(@NonNegative int opcode, @NonNull String typeDesc) {
240
        argumentCapturing.registerTypeToCaptureIfApplicable(opcode, typeDesc);
1✔
241

242
        if (opcode == NEW) {
1✔
243
            stackSize++;
1✔
244
        }
245

246
        mw.visitTypeInsn(opcode, typeDesc);
1✔
247
    }
1✔
248

249
    @Override
250
    public void visitIntInsn(@NonNegative int opcode, int operand) {
251
        if (opcode != NEWARRAY) {
1✔
252
            stackSize++;
1✔
253
        }
254

255
        mw.visitIntInsn(opcode, operand);
1✔
256
    }
1✔
257

258
    @Override
259
    public void visitVarInsn(@NonNegative int opcode, @NonNegative int varIndex) {
260
        if (opcode == ALOAD) {
1✔
261
            lastLoadedVarIndex = varIndex;
1✔
262
        }
263

264
        argumentCapturing.registerAssignmentToCaptureVariableIfApplicable(opcode, varIndex);
1✔
265
        stackSize += JVMInstruction.SIZE[opcode];
1✔
266
        mw.visitVarInsn(opcode, varIndex);
1✔
267
    }
1✔
268

269
    @Override
270
    public void visitLdcInsn(@NonNull Object cst) {
271
        stackSize++;
1✔
272

273
        if (cst instanceof Long || cst instanceof Double) {
1✔
274
            stackSize++;
1✔
275
        }
276

277
        mw.visitLdcInsn(cst);
1✔
278
    }
1✔
279

280
    @Override
281
    public void visitJumpInsn(@NonNegative int opcode, @NonNull Label label) {
282
        stackSize += JVMInstruction.SIZE[opcode];
1✔
283
        mw.visitJumpInsn(opcode, label);
1✔
284
    }
1✔
285

286
    @Override
287
    public void visitTableSwitchInsn(int min, int max, @NonNull Label dflt, @NonNull Label... labels) {
288
        stackSize--;
×
289
        mw.visitTableSwitchInsn(min, max, dflt, labels);
×
290
    }
×
291

292
    @Override
293
    public void visitLookupSwitchInsn(@NonNull Label dflt, @NonNull int[] keys, @NonNull Label[] labels) {
294
        stackSize--;
×
295
        mw.visitLookupSwitchInsn(dflt, keys, labels);
×
296
    }
×
297

298
    @Override
299
    public void visitMultiANewArrayInsn(@NonNull String desc, @NonNegative int dims) {
300
        stackSize += 1 - dims;
×
301
        mw.visitMultiANewArrayInsn(desc, dims);
×
302
    }
×
303

304
    @Override
305
    public void visitInsn(@NonNegative int opcode) {
306
        if (opcode == RETURN) {
1✔
307
            generateCallToActiveInvocationsMethod("endInvocations");
1✔
308
        } else {
309
            stackSize += JVMInstruction.SIZE[opcode];
1✔
310
        }
311

312
        mw.visitInsn(opcode);
1✔
313
    }
1✔
314

315
    @Override
316
    public void visitLocalVariable(@NonNull String name, @NonNull String desc, @Nullable String signature,
317
            @NonNull Label start, @NonNull Label end, @NonNegative int index) {
318
        if (signature != null) {
1✔
319
            ArgumentCapturing.registerTypeToCaptureIntoListIfApplicable(index, signature);
1✔
320
        }
321

322
        // In classes instrumented with EMMA some local variable information can be lost, so we discard it entirely to
323
        // avoid a ClassFormatError.
324
        if (end.position > 0) {
1!
325
            mw.visitLocalVariable(name, desc, signature, start, end, index);
1✔
326
        }
327
    }
1✔
328

329
    @NonNull
330
    MethodWriter getMethodWriter() {
331
        return mw;
1✔
332
    }
333
}
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