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

hazendaz / jmockit1 / 758

15 Mar 2026 03:43AM UTC coverage: 74.062% (+0.07%) from 73.988%
758

push

github

web-flow
Merge pull request #478 from hazendaz/copilot/check-issue-133-status

Fix constructor faking for `this()`-delegation constructors where argument creation throws

5913 of 8490 branches covered (69.65%)

Branch coverage included in aggregate %.

99 of 110 new or added lines in 2 files covered. (90.0%)

12424 of 16269 relevant lines covered (76.37%)

0.76 hits per line

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

94.41
/main/src/main/java/mockit/internal/BaseClassModifier.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;
7

8
import static java.lang.reflect.Modifier.isNative;
9
import static java.lang.reflect.Modifier.isStatic;
10

11
import static mockit.asm.jvmConstants.Opcodes.AASTORE;
12
import static mockit.asm.jvmConstants.Opcodes.ACONST_NULL;
13
import static mockit.asm.jvmConstants.Opcodes.ALOAD;
14
import static mockit.asm.jvmConstants.Opcodes.ANEWARRAY;
15
import static mockit.asm.jvmConstants.Opcodes.DUP;
16
import static mockit.asm.jvmConstants.Opcodes.GETSTATIC;
17
import static mockit.asm.jvmConstants.Opcodes.ICONST_0;
18
import static mockit.asm.jvmConstants.Opcodes.ILOAD;
19
import static mockit.asm.jvmConstants.Opcodes.INVOKEINTERFACE;
20
import static mockit.asm.jvmConstants.Opcodes.INVOKESPECIAL;
21
import static mockit.asm.jvmConstants.Opcodes.INVOKESTATIC;
22
import static mockit.asm.jvmConstants.Opcodes.IRETURN;
23
import static mockit.asm.jvmConstants.Opcodes.NEW;
24
import static mockit.asm.jvmConstants.Opcodes.NEWARRAY;
25
import static mockit.asm.jvmConstants.Opcodes.RETURN;
26
import static mockit.asm.jvmConstants.Opcodes.SIPUSH;
27

28
import edu.umd.cs.findbugs.annotations.NonNull;
29
import edu.umd.cs.findbugs.annotations.Nullable;
30

31
import java.util.ArrayList;
32
import java.util.List;
33

34
import mockit.asm.annotations.AnnotationVisitor;
35
import mockit.asm.classes.ClassInfo;
36
import mockit.asm.classes.ClassReader;
37
import mockit.asm.classes.ClassWriter;
38
import mockit.asm.classes.WrappingClassVisitor;
39
import mockit.asm.controlFlow.Label;
40
import mockit.asm.jvmConstants.Access;
41
import mockit.asm.jvmConstants.ClassVersion;
42
import mockit.asm.methods.MethodVisitor;
43
import mockit.asm.methods.MethodWriter;
44
import mockit.asm.methods.WrappingMethodVisitor;
45
import mockit.asm.types.ArrayType;
46
import mockit.asm.types.JavaType;
47
import mockit.asm.types.ObjectType;
48
import mockit.asm.types.PrimitiveType;
49
import mockit.asm.types.ReferenceType;
50
import mockit.internal.expectations.ExecutionMode;
51
import mockit.internal.state.TestRun;
52
import mockit.internal.util.ClassLoad;
53
import mockit.internal.util.TypeConversionBytecode;
54

55
import org.checkerframework.checker.index.qual.NonNegative;
56

57
public class BaseClassModifier extends WrappingClassVisitor {
58
    private static final int METHOD_ACCESS_MASK = 0xFFFF - Access.ABSTRACT - Access.NATIVE;
59
    protected static final JavaType VOID_TYPE = ObjectType.create("java/lang/Void");
1✔
60

61
    @NonNull
1✔
62
    protected final MethodVisitor methodAnnotationsVisitor = new MethodVisitor() {
1✔
63
        @Override
64
        public AnnotationVisitor visitAnnotation(@NonNull String desc) {
65
            return mw.visitAnnotation(desc);
×
66
        }
67
    };
68

69
    protected MethodWriter mw;
70
    protected boolean useClassLoadingBridge;
71
    protected String superClassName;
72
    protected String classDesc;
73
    protected int methodAccess;
74
    protected String methodName;
75
    protected String methodDesc;
76

77
    protected BaseClassModifier(@NonNull ClassReader classReader) {
78
        super(new ClassWriter(classReader));
1✔
79
    }
1✔
80

81
    protected final void setUseClassLoadingBridge(@Nullable ClassLoader classLoader) {
82
        useClassLoadingBridge = ClassLoad.isClassLoaderWithNoDirectAccess(classLoader);
1✔
83
    }
1✔
84

85
    @Override
86
    public void visit(int version, int access, @NonNull String name, @NonNull ClassInfo additionalInfo) {
87
        int modifiedVersion = version;
1✔
88
        int originalVersion = version & 0xFFFF;
1✔
89

90
        if (originalVersion < ClassVersion.V5) {
1!
91
            // LDC instructions (see MethodVisitor#visitLdcInsn) are more capable in JVMs with support for class files
92
            // of
93
            // version 49 (Java 5) or newer, so we "upgrade" it to avoid a VerifyError:
94
            modifiedVersion = ClassVersion.V5;
×
95
        }
96

97
        cw.visit(modifiedVersion, access, name, additionalInfo);
1✔
98
        superClassName = additionalInfo.superName;
1✔
99
        classDesc = name;
1✔
100
    }
1✔
101

102
    /**
103
     * Just creates a new MethodWriter which will write out the method bytecode when visited.
104
     * <p>
105
     * Removes any "abstract" or "native" modifiers for the modified version.
106
     */
107
    protected final void startModifiedMethodVersion(int access, @NonNull String name, @NonNull String desc,
108
            @Nullable String signature, @Nullable String[] exceptions) {
109
        mw = cw.visitMethod(access & METHOD_ACCESS_MASK, name, desc, signature, exceptions);
1✔
110
        methodAccess = access;
1✔
111
        methodName = name;
1✔
112
        methodDesc = desc;
1✔
113

114
        if (isNative(access)) {
1✔
115
            TestRun.mockFixture().addRedefinedClassWithNativeMethods(classDesc);
1✔
116
        }
117
    }
1✔
118

119
    public final boolean wasModified() {
120
        return methodName != null;
1✔
121
    }
122

123
    protected final void generateDirectCallToHandler(@NonNull String className, int access, @NonNull String name,
124
            @NonNull String desc, @Nullable String genericSignature) {
125
        generateDirectCallToHandler(className, access, name, desc, genericSignature, ExecutionMode.Regular);
1✔
126
    }
1✔
127

128
    protected final void generateDirectCallToHandler(@NonNull String className, int access, @NonNull String name,
129
            @NonNull String desc, @Nullable String genericSignature, @NonNull ExecutionMode executionMode) {
130
        // First argument: the mock instance, if any.
131
        boolean isStatic = generateCodeToPassThisOrNullIfStaticMethod(access);
1✔
132

133
        // Second argument: method access flags.
134
        mw.visitLdcInsn(access);
1✔
135

136
        // Third argument: class name.
137
        mw.visitLdcInsn(className);
1✔
138

139
        // Fourth argument: method signature.
140
        mw.visitLdcInsn(name + desc);
1✔
141

142
        // Fifth argument: generic signature, or null if none.
143
        generateInstructionToLoadNullableString(genericSignature);
1✔
144

145
        // Sixth argument: indicate regular or special modes of execution.
146
        mw.visitLdcInsn(executionMode.ordinal());
1✔
147

148
        // Seventh argument: array with invocation arguments.
149
        JavaType[] argTypes = JavaType.getArgumentTypes(desc);
1✔
150
        int argCount = argTypes.length;
1✔
151

152
        if (argCount == 0) {
1✔
153
            mw.visitInsn(ACONST_NULL);
1✔
154
        } else {
155
            generateCodeToCreateArrayOfObject(argCount);
1✔
156
            generateCodeToFillArrayWithParameterValues(argTypes, 0, isStatic ? 0 : 1);
1✔
157
        }
158

159
        mw.visitMethodInsn(INVOKESTATIC, "mockit/internal/expectations/RecordAndReplayExecution", "recordOrReplay",
1✔
160
                "(Ljava/lang/Object;ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;I[Ljava/lang/Object;)Ljava/lang/Object;",
161
                false);
162
    }
1✔
163

164
    private void generateInstructionToLoadNullableString(@Nullable String text) {
165
        if (text == null) {
1✔
166
            mw.visitInsn(ACONST_NULL);
1✔
167
        } else {
168
            mw.visitLdcInsn(text);
1✔
169
        }
170
    }
1✔
171

172
    protected final void generateReturnWithObjectAtTopOfTheStack(@NonNull String mockedMethodDesc) {
173
        JavaType returnType = JavaType.getReturnType(mockedMethodDesc);
1✔
174
        TypeConversionBytecode.generateCastFromObject(mw, returnType);
1✔
175
        mw.visitInsn(returnType.getOpcode(IRETURN));
1✔
176
    }
1✔
177

178
    protected final boolean generateCodeToPassThisOrNullIfStaticMethod() {
179
        return generateCodeToPassThisOrNullIfStaticMethod(methodAccess);
1✔
180
    }
181

182
    private boolean generateCodeToPassThisOrNullIfStaticMethod(int access) {
183
        boolean isStatic = isStatic(access);
1✔
184

185
        if (isStatic) {
1✔
186
            mw.visitInsn(ACONST_NULL);
1✔
187
        } else {
188
            mw.visitVarInsn(ALOAD, 0);
1✔
189
        }
190

191
        return isStatic;
1✔
192
    }
193

194
    protected final void generateCodeToCreateArrayOfObject(@NonNegative int arrayLength) {
195
        mw.visitIntInsn(SIPUSH, arrayLength);
1✔
196
        mw.visitTypeInsn(ANEWARRAY, "java/lang/Object");
1✔
197
    }
1✔
198

199
    protected final void generateCodeToFillArrayWithParameterValues(@NonNull JavaType[] parameterTypes,
200
            @NonNegative int initialArrayIndex, @NonNegative int initialParameterIndex) {
201
        int i = initialArrayIndex;
1✔
202
        int j = initialParameterIndex;
1✔
203

204
        for (JavaType parameterType : parameterTypes) {
1✔
205
            mw.visitInsn(DUP);
1✔
206
            mw.visitIntInsn(SIPUSH, i);
1✔
207
            i++;
1✔
208
            mw.visitVarInsn(parameterType.getOpcode(ILOAD), j);
1✔
209
            TypeConversionBytecode.generateCastToObject(mw, parameterType);
1✔
210
            mw.visitInsn(AASTORE);
1✔
211
            j += parameterType.getSize();
1✔
212
        }
213
    }
1✔
214

215
    protected final void generateCodeToObtainInstanceOfClassLoadingBridge(
216
            @NonNull ClassLoadingBridge classLoadingBridge) {
217
        String hostClassName = ClassLoadingBridge.getHostClassName();
1✔
218
        mw.visitFieldInsn(GETSTATIC, hostClassName, classLoadingBridge.id, "Ljava/lang/reflect/InvocationHandler;");
1✔
219
    }
1✔
220

221
    protected final void generateCodeToFillArrayElement(@NonNegative int arrayIndex, @Nullable Object value) {
222
        mw.visitInsn(DUP);
1✔
223
        mw.visitIntInsn(SIPUSH, arrayIndex);
1✔
224

225
        if (value == null) {
1✔
226
            mw.visitInsn(ACONST_NULL);
1✔
227
        } else if (value instanceof Integer) {
1✔
228
            mw.visitIntInsn(SIPUSH, (Integer) value);
1✔
229
            mw.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false);
1✔
230
        } else {
231
            mw.visitLdcInsn(value);
1✔
232
        }
233

234
        mw.visitInsn(AASTORE);
1✔
235
    }
1✔
236

237
    private void pushDefaultValueForType(@NonNull JavaType type) {
238
        if (type instanceof ArrayType) {
1✔
239
            generateCreationOfEmptyArray((ArrayType) type);
1✔
240
        } else {
241
            int constOpcode = type.getConstOpcode();
1✔
242

243
            if (constOpcode > 0) {
1✔
244
                mw.visitInsn(constOpcode);
1✔
245
            }
246
        }
247
    }
1✔
248

249
    private void generateCreationOfEmptyArray(@NonNull ArrayType arrayType) {
250
        int dimensions = arrayType.getDimensions();
1✔
251

252
        for (int dimension = 0; dimension < dimensions; dimension++) {
1✔
253
            mw.visitInsn(ICONST_0);
1✔
254
        }
255

256
        if (dimensions > 1) {
1✔
257
            mw.visitMultiANewArrayInsn(arrayType.getDescriptor(), dimensions);
1✔
258
            return;
1✔
259
        }
260

261
        JavaType elementType = arrayType.getElementType();
1✔
262

263
        if (elementType instanceof ReferenceType) {
1✔
264
            mw.visitTypeInsn(ANEWARRAY, ((ReferenceType) elementType).getInternalName());
1✔
265
        } else {
266
            int typeCode = PrimitiveType.getArrayElementType((PrimitiveType) elementType);
1✔
267
            mw.visitIntInsn(NEWARRAY, typeCode);
1✔
268
        }
269
    }
1✔
270

271
    protected final void generateCallToInvocationHandler() {
272
        mw.visitMethodInsn(INVOKEINTERFACE, "java/lang/reflect/InvocationHandler", "invoke",
1✔
273
                "(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;", true);
274
    }
1✔
275

276
    protected final void generateEmptyImplementation(@NonNull String desc) {
277
        JavaType returnType = JavaType.getReturnType(desc);
1✔
278
        pushDefaultValueForType(returnType);
1✔
279
        mw.visitInsn(returnType.getOpcode(IRETURN));
1✔
280
        mw.visitMaxStack(1);
1✔
281
    }
1✔
282

283
    protected final void generateEmptyImplementation() {
284
        mw.visitInsn(RETURN);
1✔
285
        mw.visitMaxStack(1);
1✔
286
    }
1✔
287

288
    @NonNull
289
    protected final MethodVisitor copyOriginalImplementationWithInjectedInterceptionCode() {
290
        if ("<init>".equals(methodName)) {
1✔
291
            return new DynamicConstructorModifier();
1✔
292
        }
293

294
        generateInterceptionCode();
1✔
295
        return new DynamicModifier();
1✔
296
    }
297

298
    protected void generateInterceptionCode() {
299
    }
×
300

301
    /**
302
     * Called when a constructor with {@code this()} delegation is detected, allowing subclasses to emit an early fake
303
     * interception check before the original argument-creation bytecode runs. The default implementation simply emits
304
     * the supplied label so execution falls through to the original constructor body.
305
     *
306
     * @param originalCodeLabel
307
     *            the label that marks the start of the original (unmodified) constructor body; the overriding method
308
     *            must emit this label when the early-check path is not taken
309
     */
310
    protected void generateEarlyInterceptionCodeForThisDelegationConstructor(@NonNull Label originalCodeLabel) {
311
        mw.visitLabel(originalCodeLabel);
1✔
312
    }
1✔
313

314
    private class DynamicModifier extends WrappingMethodVisitor {
315
        DynamicModifier() {
1✔
316
            super(BaseClassModifier.this.mw);
1✔
317
        }
1✔
318

319
        @Override
320
        public final void visitLocalVariable(@NonNull String name, @NonNull String desc, @Nullable String signature,
321
                @NonNull Label start, @NonNull Label end, @NonNegative int index) {
322
            // For some reason, the start position for "this" gets displaced by bytecode inserted at the beginning,
323
            // in a method modified by the EMMA tool. If not treated, this causes a ClassFormatError.
324
            if (end.position > 0 && start.position > end.position) {
1!
325
                start.position = end.position;
×
326
            }
327

328
            // Ignores any local variable with required information missing, to avoid a VerifyError/ClassFormatError.
329
            if (start.position > 0 && end.position > 0) {
1!
330
                mw.visitLocalVariable(name, desc, signature, start, end, index);
1✔
331
            }
332
        }
1✔
333
    }
334

335
    private final class DynamicConstructorModifier extends DynamicModifier {
1✔
336
        private boolean pendingCallToConstructorOfSameClass;
337
        private boolean callToAnotherConstructorAlreadyCopied;
338

339
        /**
340
         * Buffers all instructions that appear before the first {@code this()}/{@code super()} call so we can decide
341
         * whether to insert an early fake check ahead of argument-creation code that may throw.
342
         */
343
        @NonNull
1✔
344
        private final List<Runnable> pendingInstructions = new ArrayList<>();
345

346
        @Override
347
        public void visitInsn(int opcode) {
348
            if (!callToAnotherConstructorAlreadyCopied) {
1✔
349
                pendingInstructions.add(() -> mw.visitInsn(opcode));
1✔
350
            } else {
351
                mw.visitInsn(opcode);
1✔
352
            }
353
        }
1✔
354

355
        @Override
356
        public void visitIntInsn(int opcode, int operand) {
357
            if (!callToAnotherConstructorAlreadyCopied) {
1✔
358
                pendingInstructions.add(() -> mw.visitIntInsn(opcode, operand));
1✔
359
            } else {
360
                mw.visitIntInsn(opcode, operand);
1✔
361
            }
362
        }
1✔
363

364
        @Override
365
        public void visitVarInsn(int opcode, @NonNegative int varIndex) {
366
            if (!callToAnotherConstructorAlreadyCopied) {
1✔
367
                pendingInstructions.add(() -> mw.visitVarInsn(opcode, varIndex));
1✔
368
            } else {
369
                mw.visitVarInsn(opcode, varIndex);
1✔
370
            }
371
        }
1✔
372

373
        @Override
374
        public void visitTypeInsn(int opcode, @NonNull String typeDesc) {
375
            if (!callToAnotherConstructorAlreadyCopied) {
1✔
376
                pendingInstructions.add(() -> mw.visitTypeInsn(opcode, typeDesc));
1✔
377

378
                if (opcode == NEW && typeDesc.equals(classDesc)) {
1✔
379
                    pendingCallToConstructorOfSameClass = true;
1✔
380
                }
381
            } else {
382
                mw.visitTypeInsn(opcode, typeDesc);
1✔
383
            }
384
        }
1✔
385

386
        @Override
387
        public void visitFieldInsn(int opcode, @NonNull String owner, @NonNull String name, @NonNull String desc) {
388
            if (!callToAnotherConstructorAlreadyCopied) {
1✔
389
                pendingInstructions.add(() -> mw.visitFieldInsn(opcode, owner, name, desc));
1✔
390
            } else {
391
                mw.visitFieldInsn(opcode, owner, name, desc);
1✔
392
            }
393
        }
1✔
394

395
        @Override
396
        public void visitLdcInsn(@NonNull Object cst) {
397
            if (!callToAnotherConstructorAlreadyCopied) {
1✔
398
                pendingInstructions.add(() -> mw.visitLdcInsn(cst));
1✔
399
            } else {
400
                mw.visitLdcInsn(cst);
1✔
401
            }
402
        }
1✔
403

404
        @Override
405
        public void visitJumpInsn(int opcode, @NonNull Label label) {
406
            if (!callToAnotherConstructorAlreadyCopied) {
1✔
407
                pendingInstructions.add(() -> mw.visitJumpInsn(opcode, label));
1✔
408
            } else {
409
                mw.visitJumpInsn(opcode, label);
1✔
410
            }
411
        }
1✔
412

413
        @Override
414
        public void visitLabel(@NonNull Label label) {
415
            if (!callToAnotherConstructorAlreadyCopied) {
1✔
416
                pendingInstructions.add(() -> mw.visitLabel(label));
1✔
417
            } else {
418
                mw.visitLabel(label);
1✔
419
            }
420
        }
1✔
421

422
        @Override
423
        public void visitIincInsn(@NonNegative int varIndex, int increment) {
424
            if (!callToAnotherConstructorAlreadyCopied) {
1!
NEW
425
                pendingInstructions.add(() -> mw.visitIincInsn(varIndex, increment));
×
426
            } else {
427
                mw.visitIincInsn(varIndex, increment);
1✔
428
            }
429
        }
1✔
430

431
        @Override
432
        public void visitTryCatchBlock(@NonNull Label start, @NonNull Label end, @NonNull Label handler,
433
                @Nullable String type) {
434
            if (!callToAnotherConstructorAlreadyCopied) {
1!
435
                pendingInstructions.add(() -> mw.visitTryCatchBlock(start, end, handler, type));
1✔
436
            } else {
NEW
437
                mw.visitTryCatchBlock(start, end, handler, type);
×
438
            }
439
        }
1✔
440

441
        @Override
442
        public void visitLineNumber(@NonNegative int line, @NonNull Label start) {
443
            if (!callToAnotherConstructorAlreadyCopied) {
1✔
444
                pendingInstructions.add(() -> mw.visitLineNumber(line, start));
1✔
445
            } else {
446
                mw.visitLineNumber(line, start);
1✔
447
            }
448
        }
1✔
449

450
        @Override
451
        public void visitMethodInsn(int opcode, @NonNull String owner, @NonNull String name, @NonNull String desc,
452
                boolean itf) {
453
            if (!callToAnotherConstructorAlreadyCopied) {
1✔
454
                if (pendingCallToConstructorOfSameClass) {
1✔
455
                    if (opcode == INVOKESPECIAL && "<init>".equals(name) && owner.equals(classDesc)) {
1!
456
                        // Matches the invokespecial for "new SameClass(...)"; reset the flag so the
457
                        // *next* INVOKESPECIAL on the same class (if any) is treated as the real this() call.
458
                        pendingCallToConstructorOfSameClass = false;
1✔
459
                    }
460
                    // Buffer this instruction; it belongs to a 'new SameClass(...)' expression
461
                    pendingInstructions.add(() -> mw.visitMethodInsn(opcode, owner, name, desc, itf));
1✔
462
                } else if (opcode == INVOKESPECIAL && "<init>".equals(name)
1!
463
                        && (owner.equals(superClassName) || owner.equals(classDesc))) {
1✔
464
                    // This is the first this()/super() call on 'this'
465
                    callToAnotherConstructorAlreadyCopied = true;
1✔
466

467
                    if (owner.equals(classDesc)) {
1✔
468
                        // this() delegation: insert early fake check before the buffered argument-creation code
469
                        Label originalCodeLabel = new Label();
1✔
470
                        generateEarlyInterceptionCodeForThisDelegationConstructor(originalCodeLabel);
1✔
471
                        // Flush the buffered instructions (original argument-creation code)
472
                        flushPendingInstructions();
1✔
473
                        // Emit the this() call
474
                        mw.visitMethodInsn(opcode, owner, name, desc, itf);
1✔
475
                        // Emit post-this() interception (for Invocation.proceed() support)
476
                        generateInterceptionCode();
1✔
477
                    } else {
1✔
478
                        // super() delegation: flush buffer and use existing post-super() interception
479
                        flushPendingInstructions();
1✔
480
                        mw.visitMethodInsn(opcode, owner, name, desc, itf);
1✔
481
                        generateInterceptionCode();
1✔
482
                    }
483
                } else {
484
                    // Some other method call before the init call - buffer it
485
                    pendingInstructions.add(() -> mw.visitMethodInsn(opcode, owner, name, desc, itf));
1✔
486
                }
487
            } else {
488
                // After the first init call has been processed - no more buffering needed
489
                mw.visitMethodInsn(opcode, owner, name, desc, itf);
1✔
490
            }
491
        }
1✔
492

493
        private void flushPendingInstructions() {
494
            for (Runnable r : pendingInstructions) {
1✔
495
                r.run();
1✔
496
            }
1✔
497
            pendingInstructions.clear();
1✔
498
        }
1✔
499
    }
500
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc