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.GETFIELD;
9   import static mockit.asm.jvmConstants.Opcodes.SIPUSH;
10  
11  import edu.umd.cs.findbugs.annotations.NonNull;
12  
13  import mockit.asm.methods.MethodWriter;
14  import mockit.asm.types.JavaType;
15  
16  import org.checkerframework.checker.index.qual.NonNegative;
17  
18  final class ArgumentMatching {
19      private static final JavaType[] NO_PARAMETERS = {};
20      private static final String ANY_FIELDS = "any anyString anyInt anyBoolean anyLong anyDouble anyFloat anyChar anyShort anyByte";
21      private static final String WITH_METHODS = "with(Lmockit/Delegate;)Ljava/lang/Object; "
22              + "withAny(Ljava/lang/Object;)Ljava/lang/Object; "
23              + "withArgThat(Lorg/hamcrest/Matcher;)Ljava/lang/Object; "
24              + "withCapture()Ljava/lang/Object; withCapture(Ljava/util/List;)Ljava/lang/Object; "
25              + "withCapture(Ljava/lang/Object;)Ljava/util/List; "
26              + "withEqual(Ljava/lang/Object;)Ljava/lang/Object; withEqual(DD)D withEqual(FD)F "
27              + "withInstanceLike(Ljava/lang/Object;)Ljava/lang/Object; "
28              + "withInstanceOf(Ljava/lang/Class;)Ljava/lang/Object; "
29              + "withNotEqual(Ljava/lang/Object;)Ljava/lang/Object; "
30              + "withNull()Ljava/lang/Object; withNotNull()Ljava/lang/Object; "
31              + "withSameInstance(Ljava/lang/Object;)Ljava/lang/Object; "
32              + "withSubstring(Ljava/lang/CharSequence;)Ljava/lang/CharSequence; "
33              + "withPrefix(Ljava/lang/CharSequence;)Ljava/lang/CharSequence; "
34              + "withSuffix(Ljava/lang/CharSequence;)Ljava/lang/CharSequence; "
35              + "withMatch(Ljava/lang/CharSequence;)Ljava/lang/CharSequence;";
36  
37      @NonNull
38      private final InvocationBlockModifier modifier;
39  
40      // Helper fields that allow argument matchers to be moved to the correct positions of their corresponding
41      // parameters:
42      @NonNull
43      private final int[] matcherStacks;
44      @NonNegative
45      private int matcherCount;
46      @NonNull
47      private JavaType[] parameterTypes;
48  
49      static boolean isAnyField(@NonNull String name) {
50          return name.startsWith("any") && ANY_FIELDS.contains(name);
51      }
52  
53      static boolean isCallToArgumentMatcher(@NonNull String name, @NonNull String desc) {
54          return name.startsWith("with") && WITH_METHODS.contains(name + desc);
55      }
56  
57      ArgumentMatching(@NonNull InvocationBlockModifier modifier) {
58          this.modifier = modifier;
59          matcherStacks = new int[40];
60          parameterTypes = NO_PARAMETERS;
61      }
62  
63      void addMatcher(@NonNegative int stackSize) {
64          matcherStacks[matcherCount++] = stackSize;
65      }
66  
67      @NonNegative
68      int getMatcherCount() {
69          return matcherCount;
70      }
71  
72      @NonNull
73      JavaType getParameterType(@NonNegative int parameterIndex) {
74          return parameterTypes[parameterIndex];
75      }
76  
77      void generateCodeToAddArgumentMatcherForAnyField(@NonNull String fieldOwner, @NonNull String name,
78              @NonNull String desc) {
79          MethodWriter mw = modifier.getMethodWriter();
80          mw.visitFieldInsn(GETFIELD, fieldOwner, name, desc);
81          modifier.generateCallToActiveInvocationsMethod(name);
82      }
83  
84      boolean handleInvocationParameters(@NonNegative int stackSize, @NonNull String desc) {
85          parameterTypes = JavaType.getArgumentTypes(desc);
86          int stackAfter = stackSize - getSumOfParameterSizes();
87          boolean mockedInvocationUsingTheMatchers = stackAfter < matcherStacks[0];
88  
89          if (mockedInvocationUsingTheMatchers) {
90              generateCallsToMoveArgMatchers(stackAfter);
91              modifier.argumentCapturing.generateCallsToSetArgumentTypesToCaptureIfAny();
92              matcherCount = 0;
93          }
94  
95          return mockedInvocationUsingTheMatchers;
96      }
97  
98      @NonNegative
99      private int getSumOfParameterSizes() {
100         @NonNegative
101         int sum = 0;
102 
103         for (JavaType argType : parameterTypes) {
104             sum += argType.getSize();
105         }
106 
107         return sum;
108     }
109 
110     private void generateCallsToMoveArgMatchers(@NonNegative int initialStack) {
111         @NonNegative
112         int stack = initialStack;
113         @NonNegative
114         int nextMatcher = 0;
115         @NonNegative
116         int matcherStack = matcherStacks[0];
117 
118         for (int i = 0; i < parameterTypes.length && nextMatcher < matcherCount; i++) {
119             stack += parameterTypes[i].getSize();
120 
121             if (stack == matcherStack || stack == matcherStack + 1) {
122                 if (nextMatcher < i) {
123                     generateCallToMoveArgMatcher(nextMatcher, i);
124                     modifier.argumentCapturing.updateCaptureIfAny(nextMatcher, i);
125                 }
126 
127                 nextMatcher++;
128                 matcherStack = matcherStacks[nextMatcher];
129             }
130         }
131     }
132 
133     private void generateCallToMoveArgMatcher(@NonNegative int originalMatcherIndex, @NonNegative int toIndex) {
134         MethodWriter mw = modifier.getMethodWriter();
135         mw.visitIntInsn(SIPUSH, originalMatcherIndex);
136         mw.visitIntInsn(SIPUSH, toIndex);
137         modifier.generateCallToActiveInvocationsMethod("moveArgMatcher", "(II)V");
138     }
139 }