1 /*
2  * Copyright 2014 - 2020 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.implementation.bytecode.member;
17
18 import net.bytebuddy.build.HashCodeAndEqualsPlugin;
19 import net.bytebuddy.description.method.MethodDescription;
20 import net.bytebuddy.description.method.ParameterDescription;
21 import net.bytebuddy.description.type.TypeDefinition;
22 import net.bytebuddy.description.type.TypeDescription;
23 import net.bytebuddy.implementation.Implementation;
24 import net.bytebuddy.implementation.bytecode.StackManipulation;
25 import net.bytebuddy.implementation.bytecode.StackSize;
26 import net.bytebuddy.implementation.bytecode.assign.TypeCasting;
27 import net.bytebuddy.jar.asm.MethodVisitor;
28 import net.bytebuddy.jar.asm.Opcodes;
29
30 import java.util.ArrayList;
31 import java.util.List;
32
33 /**
34  * A stack assignment that loads a method variable from a given index of the local variable array.
35  */

36 public enum MethodVariableAccess {
37
38     /**
39      * The accessor handler for a JVM-integer.
40      */

41     INTEGER(Opcodes.ILOAD, Opcodes.ISTORE, StackSize.SINGLE),
42
43     /**
44      * The accessor handler for a {@code long}.
45      */

46     LONG(Opcodes.LLOAD, Opcodes.LSTORE, StackSize.DOUBLE),
47
48     /**
49      * The accessor handler for a {@code float}.
50      */

51     FLOAT(Opcodes.FLOAD, Opcodes.FSTORE, StackSize.SINGLE),
52
53     /**
54      * The accessor handler for a {@code double}.
55      */

56     DOUBLE(Opcodes.DLOAD, Opcodes.DSTORE, StackSize.DOUBLE),
57
58     /**
59      * The accessor handler for a reference type.
60      */

61     REFERENCE(Opcodes.ALOAD, Opcodes.ASTORE, StackSize.SINGLE);
62
63     /**
64      * The opcode for loading this variable type.
65      */

66     private final int loadOpcode;
67
68     /**
69      * The opcode for storing a local variable type.
70      */

71     private final int storeOpcode;
72
73     /**
74      * The size of the local variable on the JVM stack.
75      */

76     private final StackSize size;
77
78     /**
79      * Creates a new method variable access for a given JVM type.
80      *
81      * @param loadOpcode  The opcode for loading this variable type.
82      * @param storeOpcode The opcode for storing this variable type.
83      * @param stackSize   The size of the JVM type.
84      */

85     MethodVariableAccess(int loadOpcode, int storeOpcode, StackSize stackSize) {
86         this.loadOpcode = loadOpcode;
87         this.size = stackSize;
88         this.storeOpcode = storeOpcode;
89     }
90
91     /**
92      * Locates the correct accessor for a variable of a given type.
93      *
94      * @param typeDefinition The type of the variable to be loaded.
95      * @return An accessor for the given type.
96      */

97     public static MethodVariableAccess of(TypeDefinition typeDefinition) {
98         if (typeDefinition.isPrimitive()) {
99             if (typeDefinition.represents(long.class)) {
100                 return LONG;
101             } else if (typeDefinition.represents(double.class)) {
102                 return DOUBLE;
103             } else if (typeDefinition.represents(float.class)) {
104                 return FLOAT;
105             } else if (typeDefinition.represents(void.class)) {
106                 throw new IllegalArgumentException("Variable type cannot be void");
107             } else {
108                 return INTEGER;
109             }
110         } else {
111             return REFERENCE;
112         }
113     }
114
115     /**
116      * Loads all arguments of the provided method onto the operand stack.
117      *
118      * @param methodDescription The method for which all parameters are to be loaded onto the operand stack.
119      * @return A stack manipulation that loads all parameters of the provided method onto the operand stack.
120      */

121     public static MethodLoading allArgumentsOf(MethodDescription methodDescription) {
122         return new MethodLoading(methodDescription, MethodLoading.TypeCastingHandler.NoOp.INSTANCE);
123     }
124
125     /**
126      * Loads a reference to the {@code this} reference what is only meaningful for a non-static method.
127      *
128      * @return A stack manipulation loading the {@code this} reference.
129      */

130     public static StackManipulation loadThis() {
131         return MethodVariableAccess.REFERENCE.loadFrom(0);
132     }
133
134     /**
135      * Creates a stack assignment for a reading given offset of the local variable array.
136      *
137      * @param offset The offset of the variable where {@code double} and {@code long} types count two slots.
138      * @return A stack manipulation representing the variable read.
139      */

140     public StackManipulation loadFrom(int offset) {
141         return new OffsetLoading(offset);
142     }
143
144     /**
145      * Creates a stack assignment for writing to a given offset of the local variable array.
146      *
147      * @param offset The offset of the variable where {@code double} and {@code long} types count two slots.
148      * @return A stack manipulation representing the variable write.
149      */

150     public StackManipulation storeAt(int offset) {
151         return new OffsetWriting(offset);
152     }
153
154     /**
155      * Creates a stack assignment for incrementing the given offset of the local variable array.
156      *
157      * @param offset The offset of the variable where {@code double} and {@code long} types count two slots.
158      * @param value  The incremented value.
159      * @return A stack manipulation representing the variable write.
160      */

161     public StackManipulation increment(int offset, int value) {
162         if (this != INTEGER) {
163             throw new IllegalStateException("Cannot increment type: " + this);
164         }
165         return new OffsetIncrementing(offset, value);
166     }
167
168     /**
169      * Loads a parameter's value onto the operand stack.
170      *
171      * @param parameterDescription The parameter which to load onto the operand stack.
172      * @return A stack manipulation loading a parameter onto the operand stack.
173      */

174     public static StackManipulation load(ParameterDescription parameterDescription) {
175         return of(parameterDescription.getType()).loadFrom(parameterDescription.getOffset());
176     }
177
178     /**
179      * Stores the top operand stack value at the supplied parameter.
180      *
181      * @param parameterDescription The parameter which to store a value for.
182      * @return A stack manipulation storing the top operand stack value at this parameter.
183      */

184     public static StackManipulation store(ParameterDescription parameterDescription) {
185         return of(parameterDescription.getType()).storeAt(parameterDescription.getOffset());
186     }
187
188     /**
189      * Increments the value of the supplied parameter.
190      *
191      * @param parameterDescription The parameter which to increment.
192      * @param value                The value to increment with.
193      * @return A stack manipulation incrementing the supplied parameter.
194      */

195     public static StackManipulation increment(ParameterDescription parameterDescription, int value) {
196         return of(parameterDescription.getType()).increment(parameterDescription.getOffset(), value);
197     }
198
199     /**
200      * A stack manipulation that loads all parameters of a given method onto the operand stack.
201      */

202     @HashCodeAndEqualsPlugin.Enhance
203     public static class MethodLoading implements StackManipulation {
204
205         /**
206          * The method for which all parameters are loaded onto the operand stack.
207          */

208         private final MethodDescription methodDescription;
209
210         /**
211          * A type casting handler which is capable of transforming all method parameters.
212          */

213         private final TypeCastingHandler typeCastingHandler;
214
215         /**
216          * Creates a new method loading stack manipulation.
217          *
218          * @param methodDescription  The method for which all parameters are loaded onto the operand stack.
219          * @param typeCastingHandler A type casting handler which is capable of transforming all method parameters.
220          */

221         protected MethodLoading(MethodDescription methodDescription, TypeCastingHandler typeCastingHandler) {
222             this.methodDescription = methodDescription;
223             this.typeCastingHandler = typeCastingHandler;
224         }
225
226         /**
227          * {@inheritDoc}
228          */

229         public boolean isValid() {
230             return true;
231         }
232
233         /**
234          * {@inheritDoc}
235          */

236         public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
237             List<StackManipulation> stackManipulations = new ArrayList<StackManipulation>();
238             for (ParameterDescription parameterDescription : methodDescription.getParameters()) {
239                 TypeDescription parameterType = parameterDescription.getType().asErasure();
240                 stackManipulations.add(of(parameterType).loadFrom(parameterDescription.getOffset()));
241                 stackManipulations.add(typeCastingHandler.ofIndex(parameterType, parameterDescription.getIndex()));
242             }
243             return new Compound(stackManipulations).apply(methodVisitor, implementationContext);
244         }
245
246         /**
247          * Prepends a reference to the {@code this} instance to the loaded parameters if the represented method is non-static.
248          *
249          * @return A stack manipulation that loads all method parameters onto the operand stack while additionally loading a reference
250          * to {@code thisif the represented is non-static. Any potential parameter transformation is preserved.
251          */

252         public StackManipulation prependThisReference() {
253             return methodDescription.isStatic()
254                     ? this
255                     : new Compound(MethodVariableAccess.loadThis(), this);
256         }
257
258         /**
259          * Applies a transformation to all loaded arguments of the method being loaded to be casted to the corresponding parameter of
260          * the provided method. This way, the parameters can be used for invoking a bridge target method.
261          *
262          * @param bridgeTarget The method that is the target of the bridge method for which the parameters are being loaded.
263          * @return A stack manipulation that loads all parameters casted to the types of the supplied bridge target.
264          */

265         public MethodLoading asBridgeOf(MethodDescription bridgeTarget) {
266             return new MethodLoading(methodDescription, new TypeCastingHandler.ForBridgeTarget(bridgeTarget));
267         }
268
269         /**
270          * A type casting handler allows a type transformation of all arguments of a method after loading them onto the operand stack.
271          */

272         protected interface TypeCastingHandler {
273
274             /**
275              * Yields a stack transformation to transform the given argument of the method for which the arguments are loaded onto the operand stack.
276              *
277              * @param parameterType The parameter type that is to be transformed.
278              * @param index         The index of the transformed parameter.
279              * @return A transformation to apply after loading the parameter onto the operand stack.
280              */

281             StackManipulation ofIndex(TypeDescription parameterType, int index);
282
283             /**
284              * A non-operative type casting handler.
285              */

286             enum NoOp implements TypeCastingHandler {
287
288                 /**
289                  * The singleton instance.
290                  */

291                 INSTANCE;
292
293                 /**
294                  * {@inheritDoc}
295                  */

296                 public StackManipulation ofIndex(TypeDescription parameterType, int index) {
297                     return Trivial.INSTANCE;
298                 }
299             }
300
301             /**
302              * A type casting handler that casts all parameters of a method to the parameter types of a compatible method
303              * with covariant parameter types. This allows a convenient implementation of bridge methods.
304              */

305             @HashCodeAndEqualsPlugin.Enhance
306             class ForBridgeTarget implements TypeCastingHandler {
307
308                 /**
309                  * The target of the method bridge.
310                  */

311                 private final MethodDescription bridgeTarget;
312
313                 /**
314                  * Creates a new type casting handler for a bridge target.
315                  *
316                  * @param bridgeTarget The target of the method bridge.
317                  */

318                 public ForBridgeTarget(MethodDescription bridgeTarget) {
319                     this.bridgeTarget = bridgeTarget;
320                 }
321
322                 /**
323                  * {@inheritDoc}
324                  */

325                 public StackManipulation ofIndex(TypeDescription parameterType, int index) {
326                     TypeDescription targetType = bridgeTarget.getParameters().get(index).getType().asErasure();
327                     return parameterType.equals(targetType)
328                             ? Trivial.INSTANCE
329                             : TypeCasting.to(targetType);
330                 }
331             }
332         }
333     }
334
335     /**
336      * A stack manipulation for loading a variable of a method's local variable array onto the operand stack.
337      */

338     @HashCodeAndEqualsPlugin.Enhance(includeSyntheticFields = true)
339     protected class OffsetLoading implements StackManipulation {
340
341         /**
342          * The offset of the local variable array from which the variable should be loaded.
343          */

344         private final int offset;
345
346         /**
347          * Creates a new argument loading stack manipulation.
348          *
349          * @param offset The offset of the local variable array from which the variable should be loaded.
350          */

351         protected OffsetLoading(int offset) {
352             this.offset = offset;
353         }
354
355         /**
356          * {@inheritDoc}
357          */

358         public boolean isValid() {
359             return true;
360         }
361
362         /**
363          * {@inheritDoc}
364          */

365         public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
366             methodVisitor.visitVarInsn(loadOpcode, offset);
367             return size.toIncreasingSize();
368         }
369     }
370
371     /**
372      * A stack manipulation for storing a variable into a method's local variable array.
373      */

374     @HashCodeAndEqualsPlugin.Enhance(includeSyntheticFields = true)
375     protected class OffsetWriting implements StackManipulation {
376
377         /**
378          * The offset of the local variable array to which the value should be written.
379          */

380         private final int offset;
381
382         /**
383          * Creates a new argument writing stack manipulation.
384          *
385          * @param offset The offset of the local variable array to which the value should be written.
386          */

387         protected OffsetWriting(int offset) {
388             this.offset = offset;
389         }
390
391         /**
392          * {@inheritDoc}
393          */

394         public boolean isValid() {
395             return true;
396         }
397
398         /**
399          * {@inheritDoc}
400          */

401         public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
402             methodVisitor.visitVarInsn(storeOpcode, offset);
403             return size.toDecreasingSize();
404         }
405     }
406
407     /**
408      * A stack manipulation that increments an integer variable.
409      */

410     @HashCodeAndEqualsPlugin.Enhance
411     protected static class OffsetIncrementing implements StackManipulation {
412
413         /**
414          * The index of the local variable array from which the variable should be loaded.
415          */

416         private final int offset;
417
418         /**
419          * The value to increment.
420          */

421         private final int value;
422
423         /**
424          * Creates a new argument loading stack manipulation.
425          *
426          * @param offset The index of the local variable array from which the variable should be loaded.
427          * @param value  The value to increment.
428          */

429         protected OffsetIncrementing(int offset, int value) {
430             this.offset = offset;
431             this.value = value;
432         }
433
434         /**
435          * {@inheritDoc}
436          */

437         public boolean isValid() {
438             return true;
439         }
440
441         /**
442          * {@inheritDoc}
443          */

444         public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
445             methodVisitor.visitIincInsn(offset, value);
446             return new Size(0, 0);
447         }
448     }
449 }
450
451