/src/kilim/analysis/MethodWeaver.java
Java | 659 lines | 470 code | 67 blank | 122 comment | 110 complexity | 3cabb109e4b4290962edbd444d33468c MD5 | raw file
1/* Copyright (c) 2006, Sriram Srinivasan 2 * 3 * You may distribute this software under the terms of the license 4 * specified in the file "License" 5 */ 6 7package kilim.analysis; 8import static kilim.Constants.D_FIBER_LAST_ARG; 9import static kilim.Constants.D_INT; 10import static kilim.Constants.D_VOID; 11import static kilim.Constants.FIBER_CLASS; 12import static kilim.Constants.TASK_CLASS; 13import static kilim.analysis.VMType.TOBJECT; 14import static kilim.analysis.VMType.loadVar; 15import static org.objectweb.asm.Opcodes.ALOAD; 16import static org.objectweb.asm.Opcodes.ASTORE; 17import static org.objectweb.asm.Opcodes.DUP; 18import static org.objectweb.asm.Opcodes.GETFIELD; 19import static org.objectweb.asm.Opcodes.GOTO; 20import static org.objectweb.asm.Opcodes.INVOKESTATIC; 21import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL; 22import static org.objectweb.asm.Opcodes.RETURN; 23 24import java.util.ArrayList; 25import java.util.List; 26import java.util.ListIterator; 27 28import kilim.Constants; 29import kilim.mirrors.Detector; 30 31import org.objectweb.asm.AnnotationVisitor; 32import org.objectweb.asm.Attribute; 33import org.objectweb.asm.ClassVisitor; 34import org.objectweb.asm.Handle; 35import org.objectweb.asm.MethodVisitor; 36import org.objectweb.asm.Opcodes; 37import org.objectweb.asm.Type; 38import org.objectweb.asm.tree.AbstractInsnNode; 39import org.objectweb.asm.tree.AnnotationNode; 40import org.objectweb.asm.tree.InvokeDynamicInsnNode; 41import org.objectweb.asm.tree.LabelNode; 42import org.objectweb.asm.tree.LdcInsnNode; 43import org.objectweb.asm.tree.LocalVariableNode; 44import org.objectweb.asm.tree.LookupSwitchInsnNode; 45import org.objectweb.asm.tree.TableSwitchInsnNode; 46import org.objectweb.asm.tree.TryCatchBlockNode; 47 48/** 49 * This class takes the basic blocks from a MethodFlow and generates 50 * all the extra code to support continuations. 51 */ 52 53public class MethodWeaver { 54 55 private ClassWeaver classWeaver; 56 57 private MethodFlow methodFlow; 58 59 private boolean isPausable; 60 61 private int maxVars; 62 63 private int maxStack; 64 65 private boolean isSAM; 66 67 /** 68 * The last parameter to a pausable method is a Fiber ref. The rest of the 69 * code doesn't know this because we do local surgery, and so is likely to 70 * stomp on the corresponding local var. We need to save this in a slot 71 * beyond (the original) maxLocals that is a safe haven for keeping the 72 * fiberVar. 73 */ 74 private int fiberVar; 75 private int numWordsInSig; 76 private ArrayList<CallWeaver> callWeavers = new ArrayList<CallWeaver>(5); 77 78 private Detector detector; 79 80 MethodWeaver(ClassWeaver cw, Detector detector, MethodFlow mf, boolean isSAM) { 81 this.detector = detector; 82 this.classWeaver = cw; 83 this.methodFlow = mf; 84 isPausable = mf.isPausable(); 85 fiberVar = methodFlow.maxLocals; 86 maxVars = fiberVar + 1; 87 maxStack = methodFlow.maxStack + 1; // plus Fiber 88 this.isSAM = isSAM; 89 if (!mf.isAbstract()) { 90 createCallWeavers(); 91 } 92 } 93 94 95 public void accept(ClassVisitor cv) { 96 MethodFlow mf = methodFlow; 97 String[] exceptions = ClassWeaver.toStringArray(mf.exceptions); 98 String desc = mf.desc; 99 String sig = mf.signature; 100 int access = mf.access; 101 if (mf.isPausable()) { 102 access &= ~Opcodes.ACC_VARARGS; 103 if (!isSAM) { 104 desc = desc.replace(")", D_FIBER_LAST_ARG); 105 if (sig != null) 106 sig = sig.replace(")", D_FIBER_LAST_ARG); 107 } 108 } 109 MethodVisitor mv = cv.visitMethod(access, mf.name, desc, sig, exceptions); 110 111 if (!mf.isAbstract()) { 112 if (mf.needsWeaving()) { 113 accept(mv); 114 } else { 115 mf.accept(mv); 116 } 117 } else { 118 mf.accept(mv); 119 } 120 } 121 122 void accept(MethodVisitor mv) { 123 visitAttrs(mv); 124 visitCode(mv); 125 mv.visitEnd(); 126 } 127 128 private void visitAttrs(MethodVisitor mv) { 129 MethodFlow mf = methodFlow; 130 // visits the method attributes 131 int i, j, n; 132 if (mf.annotationDefault != null) { 133 AnnotationVisitor av = mv.visitAnnotationDefault(); 134 MethodFlow.acceptAnnotation(av, null, mf.annotationDefault); 135 av.visitEnd(); 136 } 137 n = mf.visibleAnnotations == null ? 0 : mf.visibleAnnotations.size(); 138 for (i = 0; i < n; ++i) { 139 AnnotationNode an = (AnnotationNode) mf.visibleAnnotations.get(i); 140 an.accept(mv.visitAnnotation(an.desc, true)); 141 } 142 n = mf.invisibleAnnotations == null ? 0 143 : mf.invisibleAnnotations.size(); 144 for (i = 0; i < n; ++i) { 145 AnnotationNode an = (AnnotationNode) mf.invisibleAnnotations.get(i); 146 an.accept(mv.visitAnnotation(an.desc, false)); 147 } 148 n = mf.visibleParameterAnnotations == null ? 0 149 : mf.visibleParameterAnnotations.length; 150 for (i = 0; i < n; ++i) { 151 List<?> l = mf.visibleParameterAnnotations[i]; 152 if (l == null) { 153 continue; 154 } 155 for (j = 0; j < l.size(); ++j) { 156 AnnotationNode an = (AnnotationNode) l.get(j); 157 an.accept(mv.visitParameterAnnotation(i, an.desc, true)); 158 } 159 } 160 n = mf.invisibleParameterAnnotations == null ? 0 161 : mf.invisibleParameterAnnotations.length; 162 for (i = 0; i < n; ++i) { 163 List<?> l = mf.invisibleParameterAnnotations[i]; 164 if (l == null) { 165 continue; 166 } 167 for (j = 0; j < l.size(); ++j) { 168 AnnotationNode an = (AnnotationNode) l.get(j); 169 an.accept(mv.visitParameterAnnotation(i, an.desc, false)); 170 } 171 } 172 n = mf.attrs == null ? 0 : mf.attrs.size(); 173 for (i = 0; i < n; ++i) { 174 mv.visitAttribute((Attribute) mf.attrs.get(i)); 175 } 176 } 177 178 private void visitCode(MethodVisitor mv) { 179 mv.visitCode(); 180 methodFlow.resetLabels(); 181 visitTryCatchBlocks(mv); 182 visitInstructions(mv); 183 visitLocals(mv); 184 visitLineNumbers(mv); 185 mv.visitMaxs(maxStack, maxVars); 186 } 187 188 private void visitLineNumbers(MethodVisitor mv) { 189 methodFlow.visitLineNumbers(mv); 190 } 191 192 private void visitLocals(MethodVisitor mv) { 193 for (Object l: methodFlow.localVariables) { 194 ((LocalVariableNode)l).accept(mv); 195 } 196 } 197 198 private void visitInstructions(MethodVisitor mv) { 199 MethodFlow mf = methodFlow; 200 genPrelude(mv); 201 BasicBlock lastBB = null; 202 boolean dsl = dslName.equals(mf.name) & dslDesc.equals(mf.desc); 203 if (dsl) 204 preweaveDeserializeLambda(); 205 for (BasicBlock bb : mf.getBasicBlocks()) { 206 int from = bb.startPos; 207 208 if (bb.isPausable() && bb.startFrame != null) { 209 genPausableMethod(mv, bb); 210 from = bb.startPos + 1; // first instruction is consumed 211 } else if (bb.isCatchHandler()) { 212 List<CallWeaver> cwList = getCallsUnderCatchBlock(bb); 213 if (cwList != null) { 214 genException(mv, bb, cwList); 215 from = bb.startPos + 1; // first instruction is consumed 216 } // else no different from any other block 217 } 218 int to = bb.endPos; 219 for (int i = from; i <= to; i++) { 220 LabelNode l = mf.getLabelAt(i); 221 if (l != null) { 222 l.accept(mv); 223 } 224 AbstractInsnNode ain = bb.getInstruction(i); 225 if (ain.getOpcode() == Constants.INVOKEDYNAMIC) { 226 transformIndyBootstrap(mv, ain); 227 } else { 228 ain.accept(mv); 229 } 230 } 231 lastBB = bb; 232 } 233 if (lastBB != null) { 234 LabelNode l = methodFlow.getLabelAt(lastBB.endPos+1); 235 if (l != null) { 236 l.accept(mv); 237 } 238 } 239 } 240 static String dslName = "$deserializeLambda$"; 241 static String dslDesc = "(Ljava/lang/invoke/SerializedLambda;)Ljava/lang/Object;"; 242 private static String addFiber(String type) { 243 return type.replace(")", Constants.D_FIBER_LAST_ARG); 244 } 245 246 /** 247 * at least with openjdk 8-11, deserializing lambdas checks the signature or the target. 248 * FIXME: this method is implementation dependent (probably unfixable, but want this line to show in a grep) 249 */ 250 public void preweaveDeserializeLambda() { 251 int maxState = 22; 252 int state = 0; 253 String cname = null; 254 String mname = null; 255 256 int num = methodFlow.instructions.size(); 257 for (int ii=0; ii < num; ii++) { 258 AbstractInsnNode node = methodFlow.instructions.get(ii); 259 260 int opcode = node.getOpcode(); 261 if (opcode != Opcodes.LDC) { 262 if (opcode==Opcodes.INVOKEDYNAMIC || opcode==Opcodes.LOOKUPSWITCH) 263 state = 0; 264 continue; 265 } 266 LdcInsnNode ldc = (LdcInsnNode) node; 267 268 269 switch (state) { 270 case 0: cname = (String) ldc.cst; break; 271 case 1: mname = (String) ldc.cst; break; 272 case 2: 273 String mdesc = (String) ldc.cst; 274 if (detector.getPausableStatus(cname,mname,mdesc)==Detector.PAUSABLE_METHOD_FOUND) 275 ldc.cst = addFiber(mdesc); 276 else 277 state = maxState; 278 break; 279 280 281 case 4: ldc.cst = addFiber((String) ldc.cst); break; 282 } 283 state++; 284 } 285 } 286 287 private void transformIndyBootstrap(MethodVisitor mv, AbstractInsnNode ain) { 288 InvokeDynamicInsnNode indy = (InvokeDynamicInsnNode)ain; 289 Object[]bsmArgs = indy.bsmArgs; 290 // Is it a lambda conversion 291 if (indy.bsm.getOwner().equals("java/lang/invoke/LambdaMetafactory")) { 292 Handle lambdaBody = (Handle)bsmArgs[1]; 293 String desc = lambdaBody.getDesc(); 294 if (detector.isPausable(lambdaBody.getOwner(), lambdaBody.getName(), desc)) { 295 bsmArgs[0] = addFiberType((Type)bsmArgs[0]); 296 bsmArgs[1] = new Handle(lambdaBody.getTag(), 297 lambdaBody.getOwner(), 298 lambdaBody.getName(), 299 desc.replace(")", D_FIBER_LAST_ARG), 300 lambdaBody.isInterface()); 301 bsmArgs[2] = addFiberType((Type)bsmArgs[2]); 302 } 303 } 304 ain.accept(mv); 305 } 306 307 private static Type addFiberType(Type type) { 308 String typeDesc = type.toString().replace(")", D_FIBER_LAST_ARG); 309 return Type.getType(typeDesc); 310 } 311 312 313 314 private List<CallWeaver> getCallsUnderCatchBlock(BasicBlock catchBB) { 315 List<CallWeaver> cwList = null; // create it lazily 316 for (CallWeaver cw: callWeavers) { 317 for (Handler h: cw.bb.handlers) { 318 if (h.catchBB == catchBB) { 319 if (cwList == null) { 320 cwList = new ArrayList<CallWeaver>(callWeavers.size()); 321 } 322 if (!cwList.contains(cw)) { 323 cwList.add(cw); 324 } 325 } 326 } 327 } 328 return cwList; 329 } 330 331 /** 332 * For a method invocation f(...), this method assumes that the arguments to 333 * the call have already been pushed in. We need to push in the Fiber as the 334 * final argument, make the call, then add the code for post-calls, then 335 * leave it to visitInstructions() to resume visiting the remaining 336 * instructions in the block 337 * 338 * <pre> 339 * F_CALL: 340 * aload <fiberVar> 341 * invokevirtual fiber.down() ;; returns Fiber 342 * ... invoke .... 343 * aload <fiberVar> 344 * ... post call code 345 * F_RESUME: 346 * </pre> 347 * 348 * @param bb 349 * The BasicBlock that contains the pausable method invocation as the first 350 * instruction 351 * @param mv 352 */ 353 private void genPausableMethod(MethodVisitor mv, BasicBlock bb) { 354 CallWeaver caw = null; 355 if (bb.isGetCurrentTask()) { 356 genGetCurrentTask(mv, bb); 357 return; 358 } 359 for (CallWeaver cw : callWeavers) { 360 if (cw.getBasicBlock() == bb) { 361 caw = cw; 362 break; 363 } 364 } 365 caw.genCall(mv); 366 caw.genPostCall(mv); 367 } 368 369 /* 370 * The Task.getCurrentTask() method is marked pausable to force 371 * the caller to be pausable too. But the method doesn't really 372 * pause; it merely looks up the task from the fiber. This is a 373 * special case where the call to getCurrentTask is replaced by 374 * <pre> 375 * load fiberVar 376 * getfield task 377 * @param mv 378 */ 379 void genGetCurrentTask(MethodVisitor mv, BasicBlock bb) { 380 bb.startLabel.accept(mv); 381 loadVar(mv, TOBJECT, getFiberVar()); 382 mv.visitFieldInsn(GETFIELD, FIBER_CLASS, "task", Constants.D_TASK); 383 } 384 385 private boolean hasGetCurrentTask() { 386 MethodFlow mf = methodFlow; 387 for (BasicBlock bb : mf.getBasicBlocks()) { 388 if (!bb.isPausable() || bb.startFrame==null) continue; 389 if (bb.isGetCurrentTask()) return true; 390 } 391 return false; 392 } 393 private void createCallWeavers() { 394 MethodFlow mf = methodFlow; 395 for (BasicBlock bb : mf.getBasicBlocks()) { 396 if (!bb.isPausable() || bb.startFrame==null) continue; 397 // No prelude needed for Task.getCurrentTask(). 398 if (bb.isGetCurrentTask()) continue; 399 CallWeaver cw = new CallWeaver(this, detector, bb); 400 callWeavers.add(cw); 401 } 402 } 403 404 /** 405 * 406 * Say there are two invocations to two pausable methods obj.f(int) 407 * (virtual) and fs(double) (a static call) ; load fiber from last arg, and 408 * save it in a fresh register ; lest it gets stomped on. This is because we 409 * only patch locally, and don't change the other instructions. 410 * 411 * <pre> 412 * aload lastVar 413 * dup 414 * astore fiberVar 415 * switch (fiber.pc) { 416 * default: 0: START 417 * 1: F_PASS_DOWN 418 * 2: FS_PASS_DOWN 419 * } 420 * </pre> 421 */ 422 private void genPrelude(MethodVisitor mv) { 423 if (!methodFlow.isPausable()) return; 424 if (callWeavers.size() == 0 && (!hasGetCurrentTask())) { 425 // Method has been marked pausable, but does not call any pausable methods, nor Task.getCurrentTask. 426 // Prelude is not needed at all. 427 return; 428 } 429 430 MethodFlow mf = methodFlow; 431 // load fiber from last var 432 int lastVar = getFiberArgVar(); 433 434 mv.visitVarInsn(ALOAD, lastVar); 435 if (lastVar < fiberVar) { 436 if (callWeavers.size() > 0) { 437 mv.visitInsn(DUP); // for storing into fiberVar 438 } 439 mv.visitVarInsn(ASTORE, getFiberVar()); 440 } 441 442 if (callWeavers.size() == 0) { 443 // No pausable method calls, but Task.getCurrentTask() is present. 444 // We don't need the rest of the prelude. 445 return; 446 } 447 448 mv.visitFieldInsn(GETFIELD, FIBER_CLASS, "pc", D_INT); 449 // The prelude doesn't need more than two words in the stack. 450 // The callweaver gen* methods may need more. 451 ensureMaxStack(2); 452 453 // switch stmt 454 LabelNode startLabel = mf.getOrCreateLabelAtPos(0); 455 LabelNode errLabel = new LabelNode(); 456 457 LabelNode[] labels = new LabelNode[callWeavers.size() + 1]; 458 labels[0] = startLabel; 459 for (int i = 0; i < callWeavers.size(); i++) { 460 labels[i + 1] = new LabelNode(); 461 } 462 new TableSwitchInsnNode(0, callWeavers.size(), errLabel, labels).accept(mv); 463 464 errLabel.accept(mv); 465 mv.visitVarInsn(ALOAD, getFiberVar()); 466 mv.visitMethodInsn(INVOKEVIRTUAL, FIBER_CLASS, "wrongPC", "()V", false); 467 // Generate pass through down code, one for each pausable method 468 // invocation 469 int last = callWeavers.size() - 1; 470 for (int i = 0; i <= last; i++) { 471 CallWeaver cw = callWeavers.get(i); 472 labels[i+1].accept(mv); 473 cw.genRewind(mv); 474 } 475 startLabel.accept(mv); 476 } 477 478 boolean isStatic() { 479 return methodFlow.isStatic(); 480 } 481 482 int getFiberArgVar() { 483 int lastVar = getNumWordsInSig(); 484 if (!isStatic()) { 485 lastVar++; 486 } 487 return lastVar; 488 } 489 490 /* 491 * The number of words in the argument; doubles/longs occupy 492 * two local vars. 493 */ 494 int getNumWordsInSig() { 495 if (numWordsInSig != -1) { 496 String[]args = TypeDesc.getArgumentTypes(methodFlow.desc); 497 int size = 0; 498 for (int i = 0; i < args.length; i++) { 499 size += TypeDesc.isDoubleWord(args[i]) ? 2 : 1; 500 } 501 numWordsInSig = size; 502 } 503 return numWordsInSig; 504 } 505 506 /** 507 * Generate code for only those catch blocks that are reachable 508 * from one or more pausable blocks. fiber.pc tells us which 509 * nested call possibly caused an exception, fiber.status tells us 510 * whether there is any state that needs to be restored, and 511 * fiber.curState gives us access to that state. 512 * 513 * ; Figure out which pausable method could have caused this. 514 * 515 * switch (fiber.upEx()) { 516 * 0: goto NORMAL_EXCEPTION_HANDLING; 517 * 2: goto RESTORE_F 518 * } 519 * RESTORE_F: 520 * if (fiber.curStatus == HAS_STATE) { 521 * restore variables from the state. don't restore stack 522 * goto NORMAL_EXCEPTION_HANDLING 523 * } 524 * ... other RESTOREs 525 * 526 * NORMAL_EXCEPTION_HANDLING: 527 */ 528 private void genException(MethodVisitor mv, BasicBlock bb, List<CallWeaver> cwList) { 529 bb.startLabel.accept(mv); 530 LabelNode resumeLabel = new LabelNode(); 531 VMType.loadVar(mv, VMType.TOBJECT, getFiberVar()); 532 mv.visitMethodInsn(INVOKEVIRTUAL, FIBER_CLASS, "upEx", "()I", false); 533 // fiber.pc is on stack 534 LabelNode[] labels = new LabelNode[cwList.size()]; 535 int[] keys = new int[cwList.size()]; 536 for (int i = 0; i < cwList.size(); i++) { 537 labels[i] = new LabelNode(); 538 keys[i] = callWeavers.indexOf(cwList.get(i)) + 1; 539 } 540 541 new LookupSwitchInsnNode(resumeLabel, keys, labels).accept(mv); 542 int i = 0; 543 for (CallWeaver cw: cwList) { 544 if (i > 0) { 545 // This is the jump (to normal exception handling) for the previous 546 // switch case. 547 mv.visitJumpInsn(GOTO, resumeLabel.getLabel()); 548 } 549 labels[i].accept(mv); 550 cw.genRestoreEx(mv, labels[i]); 551 i++; 552 } 553 554 // Consume the first instruction because we have already consumed the 555 // corresponding label. (The standard visitInstructions code does a 556 // visitLabel before visiting the instruction itself) 557 resumeLabel.accept(mv); 558 bb.getInstruction(bb.startPos).accept(mv); 559 } 560 561 int getFiberVar() { 562 return fiberVar; // The first available slot 563 } 564 565 void visitTryCatchBlocks(MethodVisitor mv) { 566 MethodFlow mf = methodFlow; 567 ArrayList<BasicBlock> bbs = mf.getBasicBlocks(); 568 ArrayList<Handler> allHandlers = new ArrayList<Handler>(bbs.size() * 2); 569 for (BasicBlock bb : bbs) { 570 allHandlers.addAll(bb.handlers); 571 } 572 allHandlers = Handler.consolidate(allHandlers); 573 for (Handler h : allHandlers) { 574 new TryCatchBlockNode(mf.getLabelAt(h.from), mf.getOrCreateLabelAtPos(h.to+1), h.catchBB.startLabel, h.type).accept(mv); 575 } 576 } 577 578 void ensureMaxVars(int numVars) { 579 if (numVars > maxVars) { 580 maxVars = numVars; 581 } 582 } 583 584 void ensureMaxStack(int numStack) { 585 if (numStack > maxStack) { 586 maxStack = numStack; 587 } 588 } 589 590 int getPC(CallWeaver weaver) { 591 for (int i = 0; i < callWeavers.size(); i++) { 592 if (callWeavers.get(i) == weaver) 593 return i + 1; 594 } 595 assert false : " No weaver found"; 596 return 0; 597 } 598 599 public String createStateClass(ValInfoList valInfoList) { 600 return classWeaver.createStateClass(valInfoList); 601 } 602 603 void makeNotWovenMethod(ClassVisitor cv, MethodFlow mf, boolean isSAM) { 604 if (classWeaver.classFlow.isJava7() && classWeaver.isInterface()) { 605 MethodVisitor mv = cv.visitMethod(mf.access, mf.name, mf.desc, 606 mf.signature, ClassWeaver.toStringArray(mf.exceptions)); 607 visitAttrs(mv); 608 mv.visitEnd(); 609 } else { 610 // Turn of abstract modifier 611 int access = mf.access; 612 access &= ~Constants.ACC_ABSTRACT; 613 String desc = isSAM ? mf.desc.replace(")", Constants.D_FIBER_LAST_ARG) : mf.desc; 614 MethodVisitor mv = cv.visitMethod(access, mf.name, desc, 615 mf.signature, ClassWeaver.toStringArray(mf.exceptions)); 616 mv.visitCode(); 617 visitAttrs(mv); 618 boolean isInterface = classWeaver.isInterface() && !isSAM; 619 mv.visitMethodInsn(INVOKESTATIC, TASK_CLASS, "errNotWoven", "()V", isInterface); 620 621 String rdesc = TypeDesc.getReturnTypeDesc(mf.desc); 622 // stack size depends on return type, because we want to load 623 // a constant of the appropriate size on the stack for 624 // the corresponding xreturn instruction. 625 int stacksize = 0; 626 if (rdesc != D_VOID) { 627 // ICONST_0; IRETURN or ACONST_NULL; ARETURN etc. 628 stacksize = TypeDesc.isDoubleWord(rdesc) ? 2 : 1; 629 int vmt = VMType.toVmType(rdesc); 630 mv.visitInsn(VMType.constInsn[vmt]); 631 mv.visitInsn(VMType.retInsn[vmt]); 632 } else { 633 mv.visitInsn(RETURN); 634 } 635 636 int numlocals; 637 if ((mf.access & Constants.ACC_ABSTRACT) != 0) { 638 // The abstract method doesn't contain the number of locals required to hold the 639 // args, so we need to calculate it. 640 numlocals = getNumWordsInSig() + 1 /* fiber */; 641 if (!mf.isStatic()) numlocals++; 642 } else { 643 numlocals = mf.maxLocals + 1; 644 } 645 mv.visitMaxs(stacksize, numlocals); 646 mv.visitEnd(); 647 } 648 649 } 650 ClassWeaver getClassWeaver() { 651 return this.classWeaver; 652 } 653 654 MethodFlow getMethodFlow() { 655 return this.methodFlow; 656 } 657} 658 659