View Javadoc
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.ACONST_NULL;
9   import static mockit.asm.jvmConstants.Opcodes.ALOAD;
10  import static mockit.asm.jvmConstants.Opcodes.SIPUSH;
11  import static mockit.internal.util.TypeConversionBytecode.generateCastOrUnboxing;
12  import static mockit.internal.util.TypeConversionBytecode.isPrimitiveWrapper;
13  
14  import edu.umd.cs.findbugs.annotations.NonNull;
15  import edu.umd.cs.findbugs.annotations.Nullable;
16  
17  import mockit.asm.methods.MethodWriter;
18  import mockit.asm.types.ArrayType;
19  import mockit.asm.types.JavaType;
20  import mockit.asm.types.ObjectType;
21  import mockit.asm.types.ReferenceType;
22  
23  import org.checkerframework.checker.index.qual.NonNegative;
24  
25  final class Capture {
26      @NonNull
27      private final InvocationBlockModifier invocationBlockModifier;
28      @NonNull
29      private final MethodWriter mw;
30      @NonNegative
31      private final int opcode;
32      @NonNegative
33      private final int varIndex;
34      @Nullable
35      private String typeToCapture;
36      @NonNegative
37      private int parameterIndex;
38      @NonNegative
39      private boolean parameterIndexFixed;
40  
41      Capture(@NonNull InvocationBlockModifier invocationBlockModifier, @NonNegative int opcode,
42              @NonNegative int varIndex, @Nullable String typeToCapture, @NonNegative int parameterIndex) {
43          this.invocationBlockModifier = invocationBlockModifier;
44          mw = invocationBlockModifier.getMethodWriter();
45          this.opcode = opcode;
46          this.varIndex = varIndex;
47          this.typeToCapture = typeToCapture;
48          this.parameterIndex = parameterIndex;
49      }
50  
51      Capture(@NonNull InvocationBlockModifier invocationBlockModifier, @NonNegative int varIndex,
52              @NonNegative int parameterIndex) {
53          this.invocationBlockModifier = invocationBlockModifier;
54          mw = invocationBlockModifier.getMethodWriter();
55          opcode = ALOAD;
56          this.varIndex = varIndex;
57          this.parameterIndex = parameterIndex;
58      }
59  
60      /**
61       * Generates bytecode that will be responsible for performing the following steps: 1. Get the argument value (an
62       * Object) for the last matched invocation. 2. Cast to a reference type or unbox to a primitive type, as needed. 3.
63       * Store the converted value in its local variable.
64       */
65      void generateCodeToStoreCapturedValue() {
66          if (opcode != ALOAD) {
67              mw.visitIntInsn(SIPUSH, parameterIndex);
68  
69              if (typeToCapture == null) {
70                  mw.visitInsn(ACONST_NULL);
71              } else {
72                  mw.visitLdcInsn(typeToCapture);
73              }
74  
75              invocationBlockModifier.generateCallToActiveInvocationsMethod("matchedArgument",
76                      "(ILjava/lang/String;)Ljava/lang/Object;");
77  
78              JavaType argType = getArgumentType();
79              generateCastOrUnboxing(mw, argType, opcode);
80  
81              mw.visitVarInsn(opcode, varIndex);
82          }
83      }
84  
85      @NonNull
86      private JavaType getArgumentType() {
87          if (typeToCapture == null) {
88              return invocationBlockModifier.argumentMatching.getParameterType(parameterIndex);
89          }
90  
91          if (typeToCapture.charAt(0) == '[') {
92              return ArrayType.create(typeToCapture);
93          }
94  
95          return ObjectType.create(typeToCapture);
96      }
97  
98      boolean fixParameterIndex(@NonNegative int originalIndex, @NonNegative int newIndex) {
99          if (!parameterIndexFixed && parameterIndex == originalIndex) {
100             parameterIndex = newIndex;
101             parameterIndexFixed = true;
102             return true;
103         }
104 
105         return false;
106     }
107 
108     void generateCallToSetArgumentTypeIfNeeded() {
109         if (opcode == ALOAD) {
110             mw.visitIntInsn(SIPUSH, parameterIndex);
111             mw.visitLdcInsn(varIndex);
112             invocationBlockModifier.generateCallToActiveInvocationsMethod("setExpectedArgumentType", "(II)V");
113         } else if (typeToCapture != null && !isTypeToCaptureSameAsParameterType(typeToCapture)) {
114             mw.visitIntInsn(SIPUSH, parameterIndex);
115             mw.visitLdcInsn(typeToCapture);
116             invocationBlockModifier.generateCallToActiveInvocationsMethod("setExpectedArgumentType",
117                     "(ILjava/lang/String;)V");
118         }
119     }
120 
121     private boolean isTypeToCaptureSameAsParameterType(@NonNull String typeDesc) {
122         JavaType parameterType = invocationBlockModifier.argumentMatching.getParameterType(parameterIndex);
123 
124         if (parameterType instanceof ReferenceType) {
125             String parameterTypeDesc = ((ReferenceType) parameterType).getInternalName();
126             return typeDesc.equals(parameterTypeDesc);
127         }
128 
129         return isPrimitiveWrapper(typeDesc);
130     }
131 }