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 this} if 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