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