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