001// ASM: a very small and fast Java bytecode manipulation framework 002// Copyright (c) 2000-2011 INRIA, France Telecom 003// All rights reserved. 004// 005// Redistribution and use in source and binary forms, with or without 006// modification, are permitted provided that the following conditions 007// are met: 008// 1. Redistributions of source code must retain the above copyright 009// notice, this list of conditions and the following disclaimer. 010// 2. Redistributions in binary form must reproduce the above copyright 011// notice, this list of conditions and the following disclaimer in the 012// documentation and/or other materials provided with the distribution. 013// 3. Neither the name of the copyright holders nor the names of its 014// contributors may be used to endorse or promote products derived from 015// this software without specific prior written permission. 016// 017// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 018// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 019// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 020// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 021// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 022// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 023// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 024// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 025// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 026// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 027// THE POSSIBILITY OF SUCH DAMAGE. 028package io.ebean.enhance.asm.commons; 029 030import io.ebean.enhance.asm.ConstantDynamic; 031import io.ebean.enhance.asm.Handle; 032import io.ebean.enhance.asm.Label; 033import io.ebean.enhance.asm.MethodVisitor; 034import io.ebean.enhance.asm.Opcodes; 035import io.ebean.enhance.asm.Type; 036 037import java.util.ArrayList; 038import java.util.HashMap; 039import java.util.List; 040import java.util.Map; 041 042/** 043 * A {@link MethodVisitor} to insert before, after and around advices in methods and constructors. 044 * For constructors, the code keeps track of the elements on the stack in order to detect when the 045 * super class constructor is called (note that there can be multiple such calls in different 046 * branches). {@code onMethodEnter} is called after each super class constructor call, because the 047 * object cannot be used before it is properly initialized. 048 * 049 * @author Eugene Kuleshov 050 * @author Eric Bruneton 051 */ 052public abstract class AdviceAdapter extends GeneratorAdapter implements Opcodes { 053 054 /** The "uninitialized this" value. */ 055 private static final Object UNINITIALIZED_THIS = new Object(); 056 057 /** Any value other than "uninitialized this". */ 058 private static final Object OTHER = new Object(); 059 060 /** Prefix of the error message when invalid opcodes are found. */ 061 private static final String INVALID_OPCODE = "Invalid opcode "; 062 063 /** The access flags of the visited method. */ 064 protected int methodAccess; 065 066 /** The descriptor of the visited method. */ 067 protected String methodDesc; 068 069 /** Whether the visited method is a constructor. */ 070 private final boolean isConstructor; 071 072 /** 073 * Whether the super class constructor has been called (if the visited method is a constructor), 074 * at the current instruction. There can be multiple call sites to the super constructor (e.g. for 075 * Java code such as {@code super(expr ? value1 : value2);}), in different branches. When scanning 076 * the bytecode linearly, we can move from one branch where the super constructor has been called 077 * to another where it has not been called yet. Therefore, this value can change from false to 078 * true, and vice-versa. 079 */ 080 private boolean superClassConstructorCalled; 081 082 /** 083 * The values on the current execution stack frame (long and double are represented by two 084 * elements). Each value is either {@link #UNINITIALIZED_THIS} (for the uninitialized this value), 085 * or {@link #OTHER} (for any other value). This field is only maintained for constructors, in 086 * branches where the super class constructor has not been called yet. 087 */ 088 private List<Object> stackFrame; 089 090 /** 091 * The stack map frames corresponding to the labels of the forward jumps made *before* the super 092 * class constructor has been called (note that the Java Virtual Machine forbids backward jumps 093 * before the super class constructor is called). Note that by definition (cf. the 'before'), when 094 * we reach a label from this map, {@link #superClassConstructorCalled} must be reset to false. 095 * This field is only maintained for constructors. 096 */ 097 private Map<Label, List<Object>> forwardJumpStackFrames; 098 099 /** 100 * Constructs a new {@link AdviceAdapter}. 101 * 102 * @param api the ASM API version implemented by this visitor. Must be one of {@link 103 * Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7}. 104 * @param methodVisitor the method visitor to which this adapter delegates calls. 105 * @param access the method's access flags (see {@link Opcodes}). 106 * @param name the method's name. 107 * @param descriptor the method's descriptor (see {@link Type Type}). 108 */ 109 protected AdviceAdapter( 110 final int api, 111 final MethodVisitor methodVisitor, 112 final int access, 113 final String name, 114 final String descriptor) { 115 super(api, methodVisitor, access, name, descriptor); 116 methodAccess = access; 117 methodDesc = descriptor; 118 isConstructor = "<init>".equals(name); 119 } 120 121 @Override 122 public void visitCode() { 123 super.visitCode(); 124 if (isConstructor) { 125 stackFrame = new ArrayList<>(); 126 forwardJumpStackFrames = new HashMap<>(); 127 } else { 128 onMethodEnter(); 129 } 130 } 131 132 @Override 133 public void visitLabel(final Label label) { 134 super.visitLabel(label); 135 if (isConstructor && forwardJumpStackFrames != null) { 136 List<Object> labelStackFrame = forwardJumpStackFrames.get(label); 137 if (labelStackFrame != null) { 138 stackFrame = labelStackFrame; 139 superClassConstructorCalled = false; 140 forwardJumpStackFrames.remove(label); 141 } 142 } 143 } 144 145 @Override 146 public void visitInsn(final int opcode) { 147 if (isConstructor && !superClassConstructorCalled) { 148 int stackSize; 149 switch (opcode) { 150 case IRETURN: 151 case FRETURN: 152 case ARETURN: 153 case LRETURN: 154 case DRETURN: 155 throw new IllegalArgumentException("Invalid return in constructor"); 156 case RETURN: // empty stack 157 onMethodExit(opcode); 158 break; 159 case ATHROW: // 1 before n/a after 160 popValue(); 161 onMethodExit(opcode); 162 break; 163 case NOP: 164 case LALOAD: // remove 2 add 2 165 case DALOAD: // remove 2 add 2 166 case LNEG: 167 case DNEG: 168 case FNEG: 169 case INEG: 170 case L2D: 171 case D2L: 172 case F2I: 173 case I2B: 174 case I2C: 175 case I2S: 176 case I2F: 177 case ARRAYLENGTH: 178 break; 179 case ACONST_NULL: 180 case ICONST_M1: 181 case ICONST_0: 182 case ICONST_1: 183 case ICONST_2: 184 case ICONST_3: 185 case ICONST_4: 186 case ICONST_5: 187 case FCONST_0: 188 case FCONST_1: 189 case FCONST_2: 190 case F2L: // 1 before 2 after 191 case F2D: 192 case I2L: 193 case I2D: 194 pushValue(OTHER); 195 break; 196 case LCONST_0: 197 case LCONST_1: 198 case DCONST_0: 199 case DCONST_1: 200 pushValue(OTHER); 201 pushValue(OTHER); 202 break; 203 case IALOAD: // remove 2 add 1 204 case FALOAD: // remove 2 add 1 205 case AALOAD: // remove 2 add 1 206 case BALOAD: // remove 2 add 1 207 case CALOAD: // remove 2 add 1 208 case SALOAD: // remove 2 add 1 209 case POP: 210 case IADD: 211 case FADD: 212 case ISUB: 213 case LSHL: // 3 before 2 after 214 case LSHR: // 3 before 2 after 215 case LUSHR: // 3 before 2 after 216 case L2I: // 2 before 1 after 217 case L2F: // 2 before 1 after 218 case D2I: // 2 before 1 after 219 case D2F: // 2 before 1 after 220 case FSUB: 221 case FMUL: 222 case FDIV: 223 case FREM: 224 case FCMPL: // 2 before 1 after 225 case FCMPG: // 2 before 1 after 226 case IMUL: 227 case IDIV: 228 case IREM: 229 case ISHL: 230 case ISHR: 231 case IUSHR: 232 case IAND: 233 case IOR: 234 case IXOR: 235 case MONITORENTER: 236 case MONITOREXIT: 237 popValue(); 238 break; 239 case POP2: 240 case LSUB: 241 case LMUL: 242 case LDIV: 243 case LREM: 244 case LADD: 245 case LAND: 246 case LOR: 247 case LXOR: 248 case DADD: 249 case DMUL: 250 case DSUB: 251 case DDIV: 252 case DREM: 253 popValue(); 254 popValue(); 255 break; 256 case IASTORE: 257 case FASTORE: 258 case AASTORE: 259 case BASTORE: 260 case CASTORE: 261 case SASTORE: 262 case LCMP: // 4 before 1 after 263 case DCMPL: 264 case DCMPG: 265 popValue(); 266 popValue(); 267 popValue(); 268 break; 269 case LASTORE: 270 case DASTORE: 271 popValue(); 272 popValue(); 273 popValue(); 274 popValue(); 275 break; 276 case DUP: 277 pushValue(peekValue()); 278 break; 279 case DUP_X1: 280 stackSize = stackFrame.size(); 281 stackFrame.add(stackSize - 2, stackFrame.get(stackSize - 1)); 282 break; 283 case DUP_X2: 284 stackSize = stackFrame.size(); 285 stackFrame.add(stackSize - 3, stackFrame.get(stackSize - 1)); 286 break; 287 case DUP2: 288 stackSize = stackFrame.size(); 289 stackFrame.add(stackSize - 2, stackFrame.get(stackSize - 1)); 290 stackFrame.add(stackSize - 2, stackFrame.get(stackSize - 1)); 291 break; 292 case DUP2_X1: 293 stackSize = stackFrame.size(); 294 stackFrame.add(stackSize - 3, stackFrame.get(stackSize - 1)); 295 stackFrame.add(stackSize - 3, stackFrame.get(stackSize - 1)); 296 break; 297 case DUP2_X2: 298 stackSize = stackFrame.size(); 299 stackFrame.add(stackSize - 4, stackFrame.get(stackSize - 1)); 300 stackFrame.add(stackSize - 4, stackFrame.get(stackSize - 1)); 301 break; 302 case SWAP: 303 stackSize = stackFrame.size(); 304 stackFrame.add(stackSize - 2, stackFrame.get(stackSize - 1)); 305 stackFrame.remove(stackSize); 306 break; 307 default: 308 throw new IllegalArgumentException(INVALID_OPCODE + opcode); 309 } 310 } else { 311 switch (opcode) { 312 case RETURN: 313 case IRETURN: 314 case FRETURN: 315 case ARETURN: 316 case LRETURN: 317 case DRETURN: 318 case ATHROW: 319 onMethodExit(opcode); 320 break; 321 default: 322 break; 323 } 324 } 325 super.visitInsn(opcode); 326 } 327 328 @Override 329 public void visitVarInsn(final int opcode, final int var) { 330 super.visitVarInsn(opcode, var); 331 if (isConstructor && !superClassConstructorCalled) { 332 switch (opcode) { 333 case ILOAD: 334 case FLOAD: 335 pushValue(OTHER); 336 break; 337 case LLOAD: 338 case DLOAD: 339 pushValue(OTHER); 340 pushValue(OTHER); 341 break; 342 case ALOAD: 343 pushValue(var == 0 ? UNINITIALIZED_THIS : OTHER); 344 break; 345 case ASTORE: 346 case ISTORE: 347 case FSTORE: 348 popValue(); 349 break; 350 case LSTORE: 351 case DSTORE: 352 popValue(); 353 popValue(); 354 break; 355 default: 356 throw new IllegalArgumentException(INVALID_OPCODE + opcode); 357 } 358 } 359 } 360 361 @Override 362 public void visitFieldInsn( 363 final int opcode, final String owner, final String name, final String descriptor) { 364 super.visitFieldInsn(opcode, owner, name, descriptor); 365 if (isConstructor && !superClassConstructorCalled) { 366 char firstDescriptorChar = descriptor.charAt(0); 367 boolean longOrDouble = firstDescriptorChar == 'J' || firstDescriptorChar == 'D'; 368 switch (opcode) { 369 case GETSTATIC: 370 pushValue(OTHER); 371 if (longOrDouble) { 372 pushValue(OTHER); 373 } 374 break; 375 case PUTSTATIC: 376 popValue(); 377 if (longOrDouble) { 378 popValue(); 379 } 380 break; 381 case PUTFIELD: 382 popValue(); 383 popValue(); 384 if (longOrDouble) { 385 popValue(); 386 } 387 break; 388 case GETFIELD: 389 if (longOrDouble) { 390 pushValue(OTHER); 391 } 392 break; 393 default: 394 throw new IllegalArgumentException(INVALID_OPCODE + opcode); 395 } 396 } 397 } 398 399 @Override 400 public void visitIntInsn(final int opcode, final int operand) { 401 super.visitIntInsn(opcode, operand); 402 if (isConstructor && !superClassConstructorCalled && opcode != NEWARRAY) { 403 pushValue(OTHER); 404 } 405 } 406 407 @Override 408 public void visitLdcInsn(final Object value) { 409 super.visitLdcInsn(value); 410 if (isConstructor && !superClassConstructorCalled) { 411 pushValue(OTHER); 412 if (value instanceof Double 413 || value instanceof Long 414 || (value instanceof ConstantDynamic && ((ConstantDynamic) value).getSize() == 2)) { 415 pushValue(OTHER); 416 } 417 } 418 } 419 420 @Override 421 public void visitMultiANewArrayInsn(final String descriptor, final int numDimensions) { 422 super.visitMultiANewArrayInsn(descriptor, numDimensions); 423 if (isConstructor && !superClassConstructorCalled) { 424 for (int i = 0; i < numDimensions; i++) { 425 popValue(); 426 } 427 pushValue(OTHER); 428 } 429 } 430 431 @Override 432 public void visitTypeInsn(final int opcode, final String type) { 433 super.visitTypeInsn(opcode, type); 434 // ANEWARRAY, CHECKCAST or INSTANCEOF don't change stack. 435 if (isConstructor && !superClassConstructorCalled && opcode == NEW) { 436 pushValue(OTHER); 437 } 438 } 439 440 @Override 441 public void visitMethodInsn( 442 final int opcodeAndSource, 443 final String owner, 444 final String name, 445 final String descriptor, 446 final boolean isInterface) { 447 if (api < Opcodes.ASM5 && (opcodeAndSource & Opcodes.SOURCE_DEPRECATED) == 0) { 448 // Redirect the call to the deprecated version of this method. 449 super.visitMethodInsn(opcodeAndSource, owner, name, descriptor, isInterface); 450 return; 451 } 452 super.visitMethodInsn(opcodeAndSource, owner, name, descriptor, isInterface); 453 int opcode = opcodeAndSource & ~Opcodes.SOURCE_MASK; 454 455 doVisitMethodInsn(opcode, descriptor); 456 } 457 458 private void doVisitMethodInsn(final int opcode, final String descriptor) { 459 if (isConstructor && !superClassConstructorCalled) { 460 for (Type argumentType : Type.getArgumentTypes(descriptor)) { 461 popValue(); 462 if (argumentType.getSize() == 2) { 463 popValue(); 464 } 465 } 466 switch (opcode) { 467 case INVOKEINTERFACE: 468 case INVOKEVIRTUAL: 469 popValue(); 470 break; 471 case INVOKESPECIAL: 472 Object value = popValue(); 473 if (value == UNINITIALIZED_THIS && !superClassConstructorCalled) { 474 superClassConstructorCalled = true; 475 onMethodEnter(); 476 } 477 break; 478 default: 479 break; 480 } 481 482 Type returnType = Type.getReturnType(descriptor); 483 if (returnType != Type.VOID_TYPE) { 484 pushValue(OTHER); 485 if (returnType.getSize() == 2) { 486 pushValue(OTHER); 487 } 488 } 489 } 490 } 491 492 @Override 493 public void visitInvokeDynamicInsn( 494 final String name, 495 final String descriptor, 496 final Handle bootstrapMethodHandle, 497 final Object... bootstrapMethodArguments) { 498 super.visitInvokeDynamicInsn(name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments); 499 doVisitMethodInsn(Opcodes.INVOKEDYNAMIC, descriptor); 500 } 501 502 @Override 503 public void visitJumpInsn(final int opcode, final Label label) { 504 super.visitJumpInsn(opcode, label); 505 if (isConstructor && !superClassConstructorCalled) { 506 switch (opcode) { 507 case IFEQ: 508 case IFNE: 509 case IFLT: 510 case IFGE: 511 case IFGT: 512 case IFLE: 513 case IFNULL: 514 case IFNONNULL: 515 popValue(); 516 break; 517 case IF_ICMPEQ: 518 case IF_ICMPNE: 519 case IF_ICMPLT: 520 case IF_ICMPGE: 521 case IF_ICMPGT: 522 case IF_ICMPLE: 523 case IF_ACMPEQ: 524 case IF_ACMPNE: 525 popValue(); 526 popValue(); 527 break; 528 case JSR: 529 pushValue(OTHER); 530 break; 531 default: 532 break; 533 } 534 addForwardJump(label); 535 } 536 } 537 538 @Override 539 public void visitLookupSwitchInsn(final Label dflt, final int[] keys, final Label[] labels) { 540 super.visitLookupSwitchInsn(dflt, keys, labels); 541 if (isConstructor && !superClassConstructorCalled) { 542 popValue(); 543 addForwardJumps(dflt, labels); 544 } 545 } 546 547 @Override 548 public void visitTableSwitchInsn( 549 final int min, final int max, final Label dflt, final Label... labels) { 550 super.visitTableSwitchInsn(min, max, dflt, labels); 551 if (isConstructor && !superClassConstructorCalled) { 552 popValue(); 553 addForwardJumps(dflt, labels); 554 } 555 } 556 557 @Override 558 public void visitTryCatchBlock( 559 final Label start, final Label end, final Label handler, final String type) { 560 super.visitTryCatchBlock(start, end, handler, type); 561 // By definition of 'forwardJumpStackFrames', 'handler' should be pushed only if there is an 562 // instruction between 'start' and 'end' at which the super class constructor is not yet 563 // called. Unfortunately, try catch blocks must be visited before their labels, so we have no 564 // way to know this at this point. Instead, we suppose that the super class constructor has not 565 // been called at the start of *any* exception handler. If this is wrong, normally there should 566 // not be a second super class constructor call in the exception handler (an object can't be 567 // initialized twice), so this is not issue (in the sense that there is no risk to emit a wrong 568 // 'onMethodEnter'). 569 if (isConstructor && !forwardJumpStackFrames.containsKey(handler)) { 570 List<Object> handlerStackFrame = new ArrayList<>(); 571 handlerStackFrame.add(OTHER); 572 forwardJumpStackFrames.put(handler, handlerStackFrame); 573 } 574 } 575 576 private void addForwardJumps(final Label dflt, final Label[] labels) { 577 addForwardJump(dflt); 578 for (Label label : labels) { 579 addForwardJump(label); 580 } 581 } 582 583 private void addForwardJump(final Label label) { 584 if (forwardJumpStackFrames.containsKey(label)) { 585 return; 586 } 587 forwardJumpStackFrames.put(label, new ArrayList<>(stackFrame)); 588 } 589 590 private Object popValue() { 591 return stackFrame.remove(stackFrame.size() - 1); 592 } 593 594 private Object peekValue() { 595 return stackFrame.get(stackFrame.size() - 1); 596 } 597 598 private void pushValue(final Object value) { 599 stackFrame.add(value); 600 } 601 602 /** 603 * Generates the "before" advice for the visited method. The default implementation of this method 604 * does nothing. Subclasses can use or change all the local variables, but should not change state 605 * of the stack. This method is called at the beginning of the method or after super class 606 * constructor has been called (in constructors). 607 */ 608 protected void onMethodEnter() {} 609 610 /** 611 * Generates the "after" advice for the visited method. The default implementation of this method 612 * does nothing. Subclasses can use or change all the local variables, but should not change state 613 * of the stack. This method is called at the end of the method, just before return and athrow 614 * instructions. The top element on the stack contains the return value or the exception instance. 615 * For example: 616 * 617 * <pre> 618 * public void onMethodExit(final int opcode) { 619 * if (opcode == RETURN) { 620 * visitInsn(ACONST_NULL); 621 * } else if (opcode == ARETURN || opcode == ATHROW) { 622 * dup(); 623 * } else { 624 * if (opcode == LRETURN || opcode == DRETURN) { 625 * dup2(); 626 * } else { 627 * dup(); 628 * } 629 * box(Type.getReturnType(this.methodDesc)); 630 * } 631 * visitIntInsn(SIPUSH, opcode); 632 * visitMethodInsn(INVOKESTATIC, owner, "onExit", "(Ljava/lang/Object;I)V"); 633 * } 634 * 635 * // An actual call back method. 636 * public static void onExit(final Object exitValue, final int opcode) { 637 * ... 638 * } 639 * </pre> 640 * 641 * @param opcode one of {@link Opcodes#RETURN}, {@link Opcodes#IRETURN}, {@link Opcodes#FRETURN}, 642 * {@link Opcodes#ARETURN}, {@link Opcodes#LRETURN}, {@link Opcodes#DRETURN} or {@link 643 * Opcodes#ATHROW}. 644 */ 645 protected void onMethodExit(final int opcode) {} 646}