小于1.5编译级别时,如果不显示inline try/catch/finally块,try/catch和any会产生的jsr指令跳转到finally。此处分析kilim如何对这种情况下的jsr指令进行内联:finally块中有pausable则会被拷贝一份;finally块中有pausable则会把jsr/ret指令都替换为goto指令,但是并没有像1.5及以后编译级别那样,拷贝一份代码try/catch代码块编译后的指令块中。下面看下analyze中调用的kilim.analysis.MethodFlow.inlineSubroutines()的实现:
private void inlineSubroutines() throws KilimException { markPausableJSRs();//处理finally块,打标记 while (true) { ArrayList<BasicBlock> newBBs = null; for (BasicBlock bb: basicBlocks) { if (bb.hasFlag(INLINE_CHECKED)) continue; bb.setFlag(INLINE_CHECKED);//所有块都是INLINE_CHECKED if (bb.lastInstruction() == JSR) {//bb是try/catch代码块的最后一个bb,才inline newBBs = bb.inline();//bb内联 if (newBBs != null) { break; } } } if (newBBs == null) { break; } int id = basicBlocks.size(); for (BasicBlock bb: newBBs) { bb.setId(id++); basicBlocks.add(bb); } } // If there are any pausable subroutines, modify the JSRs/RETs to // GOTOs for (BasicBlock bb: basicBlocks) { bb.changeJSR_RET_toGOTOs();//把出现pausable调用的finally块相关的jsr/ret指令换成goto指令来实现 } } private void markPausableJSRs() throws KilimException { for (BasicBlock bb: basicBlocks) { bb.checkPausableJSR();//这里调用了BasicBlock.checkPausableJSR() } } /* * If any BB belonging to a subroutine makes a pausable * block, it taints all the blocks within the subroutine's * purview as PAUSABLE_SUB. 如果subroutine有一个bb是PAUSABLE,那么其所有bbs都是PAUSABLE_SUB的,何用? */ void checkPausableJSR() throws KilimException { BasicBlock sub = getJSRTarget();//如果当前bb的最后一条指令是jsr指令,那么第一个后继结点就是jsr Target,jsr指令所在块也只有一个后继结点,即finally代码块的第一个bb。第一次运行到这里时,如果当前分析的方法没有返回值,则当前块是type为any的异常handler,jvm编译期生成的,following bb里边会把异常抛出;如果方法有返回值,当前块保存了返回值在局部变量表,following bb会return局部变量表内容。sub即为finally块内容 boolean isPausableJSR = false; if (sub != null) { ArrayList<BasicBlock> subBlocks = sub.getSubBlocks();//finally代码块对应的basicBlocks,如果finally代码块中还有try/catch/finally块,会在jsr指令处终止的,即subBlocks不会包含子finally代码块对应的bb for (BasicBlock b: subBlocks) { if (b.hasFlag(PAUSABLE)) {//initialize的时候,pausable方法所在bb是PAUSABLE的 isPausableJSR = true; break; } } if (isPausableJSR) { for (BasicBlock b: subBlocks) {//finally块中有一个是PAUSABLE,整个finally所有subBlocks都是PAUSABLE_SUB b.setFlag(PAUSABLE_SUB); } } } } /* * Invoked on the subroutine entry point's BB. Returns all the BBs * linked to it. */ public ArrayList<BasicBlock> getSubBlocks() throws KilimException { if (subBlocks == null) { if (!hasFlag(IS_SUBROUTINE))//还记得在BasicBlock.initialize的时候我们把jsr指令的目标地址开始的块设置为IS_SUBROUTINE的 return null; subBlocks = new ArrayList<BasicBlock>(10); Stack<BasicBlock> stack = new Stack<BasicBlock>(); this.setFlag(SUB_BLOCK);//IS_SUBROUTINE 也就是 SUB_BLOCK stack.add(this); while (!stack.isEmpty()) {//如果finally语句块的那一堆代码又被划分成了很多块,那么所有块都是SUB_BLOCK BasicBlock b = stack.pop(); subBlocks.add(b); if (b.lastInstruction() == JSR) {//当前块是jsrTarget块,即finally代码块的第一个bb。而在initialize的时候,我们并没有处理过ret指令的目标地址,没有处理过jsr指令的物理following指令,即jsr的物理following指令跟jsr指令是没有关联起来的,所以这里需要关联起来。 // add the following block, but not its target BasicBlock follower = b.getFollowingBlock();//获取bb.endPos+1开始的块,一般来说,jsr块的物理following块通常是load、athrow、return等,这些是jsr的目标块运行完需要ret回来的地址。 if (!follower.hasFlag(SUB_BLOCK)) {//都标记为SUB_BLOCK follower.setFlag(SUB_BLOCK); stack.push(follower); } continue;//意味着我们跳过了jsr指令的successors,即finally块中的finally块是被跳过了 } for (BasicBlock succ : b.successors) {//jsr指令结尾的块也是有successor的,即它的target块。finally块的结尾是有ret指令的,这个指令所在块是没有后继节点的,subBlocks也就到此位置了。finally块中的try/catch/finally块也会被分析 if (succ == this) { thrownew KilimException("JSRs looping back to themselves are not supported"); } if (!succ.hasFlag(SUB_BLOCK)) { succ.setFlag(SUB_BLOCK); stack.push(succ); } } } Collections.sort(subBlocks);//按照指令块的物理顺序排序,在consolidate 块的时候按顺序设置了bb的id的,这个id在bb.compareTo中有用到 } return subBlocks; } /** * This basic block's last instruction is JSR. This method initiates a * subgraph traversal to identify the called subroutine's boundaries and to * make all encountered RET instructions point back to this BB's follower, * in essence turning it to a goto. The reason for not actually turning it * into a GOTO is that if we don't find any pausable methods in a * subroutine, then during code generation we'll simply use the original * code. The duplication is still required for flow analysis. * * The VM spec is fuzzy on what constitutes the boundaries of a subroutine. * We consider the following situations invalid, even though the verifier is * ok with it: (a) looping back to itself (b) encountering xRETURN in a subroutine * * inline() traverses the graph creating copies of BasicBlocks and labels * and keeps a mapping between the old and the new. In the second round, it * copies instructions translating any that have labels (branch and switch * instructions). * * @return mapping of orig basic blocks to new. * */ ArrayList<BasicBlock> inline() throws KilimException { HashMap<BasicBlock, BasicBlock> bbCopyMap = null; HashMap<Label, Label> labelCopyMap = null; BasicBlock targetBB = successors.get(0);//jsr目标块 Label returnToLabel = flow.getOrCreateLabelAtPos(endPos+1);//jsr指令下边的指令,如果jsr在try块中,就是return;如果jsr是在type为any的异常处理块中,就是aload_X athrow; BasicBlock returnToBB = flow.getOrCreateBasicBlock(returnToLabel); boolean isPausableSub = targetBB.hasFlag(PAUSABLE_SUB); if (!targetBB.hasFlag(SUBROUTINE_CLAIMED)) { // This JSR call gets to claim the subroutine's blocks, so no // copying required. If another JSR wants to point to the same // subroutine, it'll copy BBs on demand) targetBB.setFlag(SUBROUTINE_CLAIMED); // Tell the RET blocks about the returnTo address and we are done. for (BasicBlock b : targetBB.getSubBlocks()) { if (b.lastInstruction() == RET) { assert b.successors.size() == 0 : this.toString(); b.addSuccessor(returnToBB);//把jsr的下一条指令开始块作为ret指令结束的块的后继结点,即把ret指令和jsr指令关联起来 } } return null; } bbCopyMap = new HashMap<BasicBlock, BasicBlock>(10); labelCopyMap = new HashMap<Label, Label>(10); successors.clear(); // first pass targetBB.dupBBAndLabels(isPausableSub, bbCopyMap, labelCopyMap, returnToBB); addSuccessor(bbCopyMap.get(targetBB)); // second pass return dupCopyContents(isPausableSub, targetBB, returnToBB, bbCopyMap, labelCopyMap); } //把jsr指令改造成goto指令 void changeJSR_RET_toGOTOs() throws KilimException { int lastInsn = getInstruction(endPos).getOpcode(); if (lastInsn == JSR) {//块的最后一条指令为jsr指令 BasicBlock targetBB = successors.get(0); if (!targetBB.hasFlag(PAUSABLE_SUB)) return;//不是pausable就不处理,因为不会织入,不做改变的。在checkPausableJSR的时候,PAUSABLE bb的所有subBB都是PAUSABLE_SUB的 changeLastInsnToGOTO(targetBB.startLabel);//把当前块最后一个指令替换成goto,goto的目标是targetBB。其实这里就是替换jsr为goto successors.clear();//clear一次有什么用? successors.add(targetBB); // change the first ASTORE instruction in targetBB to a NOP assert targetBB.getInstruction(targetBB.startPos).getOpcode() == ASTORE;//targetBB的第一条指令一定是astroe,存的是jsr后边的指令地址 targetBB.setInstruction(targetBB.startPos, new NopInsn());//把楼上这个存储jsr下一条指令的地址的指令替换成空指令。这个地址原本会被ret指令用到 targetBB.unsetFlag(IS_SUBROUTINE); } else if (lastInsn == RET && hasFlag(PAUSABLE_SUB)) { changeLastInsnToGOTO(successors.get(0).startLabel);//处理jsr targetBB里边的ret指令,改为goto,目标指令是ret的目标块,即jsr的下一条指令所在块。在initialize的时候,ret指令结尾的块是没有successor的,这个在inline的时候关联起来的。 }//这么整个下来,把jsr、ret指令给替换成了goto,把jsr目标块第一条指令给删掉了。 }