1/*** 2 * ASM: a very small and fast Java bytecode manipulation framework 3 * Copyright (c) 2000-2007 INRIA, France Telecom 4 * All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 3. Neither the name of the copyright holders nor the names of its 15 * contributors may be used to endorse or promote products derived from 16 * this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 22 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 28 * THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30package org.mockito.asm.tree.analysis; 31 32import java.util.ArrayList; 33import java.util.HashMap; 34import java.util.List; 35import java.util.Map; 36 37import org.mockito.asm.Opcodes; 38import org.mockito.asm.Type; 39import org.mockito.asm.tree.AbstractInsnNode; 40import org.mockito.asm.tree.IincInsnNode; 41import org.mockito.asm.tree.InsnList; 42import org.mockito.asm.tree.JumpInsnNode; 43import org.mockito.asm.tree.LabelNode; 44import org.mockito.asm.tree.LookupSwitchInsnNode; 45import org.mockito.asm.tree.MethodNode; 46import org.mockito.asm.tree.TableSwitchInsnNode; 47import org.mockito.asm.tree.TryCatchBlockNode; 48import org.mockito.asm.tree.VarInsnNode; 49 50/** 51 * A semantic bytecode analyzer. <i>This class does not fully check that JSR and 52 * RET instructions are valid.</i> 53 * 54 * @author Eric Bruneton 55 */ 56public class Analyzer implements Opcodes { 57 58 private final Interpreter interpreter; 59 60 private int n; 61 62 private InsnList insns; 63 64 private List[] handlers; 65 66 private Frame[] frames; 67 68 private Subroutine[] subroutines; 69 70 private boolean[] queued; 71 72 private int[] queue; 73 74 private int top; 75 76 /** 77 * Constructs a new {@link Analyzer}. 78 * 79 * @param interpreter the interpreter to be used to symbolically interpret 80 * the bytecode instructions. 81 */ 82 public Analyzer(final Interpreter interpreter) { 83 this.interpreter = interpreter; 84 } 85 86 /** 87 * Analyzes the given method. 88 * 89 * @param owner the internal name of the class to which the method belongs. 90 * @param m the method to be analyzed. 91 * @return the symbolic state of the execution stack frame at each bytecode 92 * instruction of the method. The size of the returned array is 93 * equal to the number of instructions (and labels) of the method. A 94 * given frame is <tt>null</tt> if and only if the corresponding 95 * instruction cannot be reached (dead code). 96 * @throws AnalyzerException if a problem occurs during the analysis. 97 */ 98 public Frame[] analyze(final String owner, final MethodNode m) 99 throws AnalyzerException 100 { 101 if ((m.access & (ACC_ABSTRACT | ACC_NATIVE)) != 0) { 102 frames = new Frame[0]; 103 return frames; 104 } 105 n = m.instructions.size(); 106 insns = m.instructions; 107 handlers = new List[n]; 108 frames = new Frame[n]; 109 subroutines = new Subroutine[n]; 110 queued = new boolean[n]; 111 queue = new int[n]; 112 top = 0; 113 114 // computes exception handlers for each instruction 115 for (int i = 0; i < m.tryCatchBlocks.size(); ++i) { 116 TryCatchBlockNode tcb = (TryCatchBlockNode) m.tryCatchBlocks.get(i); 117 int begin = insns.indexOf(tcb.start); 118 int end = insns.indexOf(tcb.end); 119 for (int j = begin; j < end; ++j) { 120 List insnHandlers = handlers[j]; 121 if (insnHandlers == null) { 122 insnHandlers = new ArrayList(); 123 handlers[j] = insnHandlers; 124 } 125 insnHandlers.add(tcb); 126 } 127 } 128 129 // computes the subroutine for each instruction: 130 Subroutine main = new Subroutine(null, m.maxLocals, null); 131 List subroutineCalls = new ArrayList(); 132 Map subroutineHeads = new HashMap(); 133 findSubroutine(0, main, subroutineCalls); 134 while (!subroutineCalls.isEmpty()) { 135 JumpInsnNode jsr = (JumpInsnNode) subroutineCalls.remove(0); 136 Subroutine sub = (Subroutine) subroutineHeads.get(jsr.label); 137 if (sub == null) { 138 sub = new Subroutine(jsr.label, m.maxLocals, jsr); 139 subroutineHeads.put(jsr.label, sub); 140 findSubroutine(insns.indexOf(jsr.label), sub, subroutineCalls); 141 } else { 142 sub.callers.add(jsr); 143 } 144 } 145 for (int i = 0; i < n; ++i) { 146 if (subroutines[i] != null && subroutines[i].start == null) { 147 subroutines[i] = null; 148 } 149 } 150 151 // initializes the data structures for the control flow analysis 152 Frame current = newFrame(m.maxLocals, m.maxStack); 153 Frame handler = newFrame(m.maxLocals, m.maxStack); 154 Type[] args = Type.getArgumentTypes(m.desc); 155 int local = 0; 156 if ((m.access & ACC_STATIC) == 0) { 157 Type ctype = Type.getObjectType(owner); 158 current.setLocal(local++, interpreter.newValue(ctype)); 159 } 160 for (int i = 0; i < args.length; ++i) { 161 current.setLocal(local++, interpreter.newValue(args[i])); 162 if (args[i].getSize() == 2) { 163 current.setLocal(local++, interpreter.newValue(null)); 164 } 165 } 166 while (local < m.maxLocals) { 167 current.setLocal(local++, interpreter.newValue(null)); 168 } 169 merge(0, current, null); 170 171 // control flow analysis 172 while (top > 0) { 173 int insn = queue[--top]; 174 Frame f = frames[insn]; 175 Subroutine subroutine = subroutines[insn]; 176 queued[insn] = false; 177 178 try { 179 AbstractInsnNode insnNode = m.instructions.get(insn); 180 int insnOpcode = insnNode.getOpcode(); 181 int insnType = insnNode.getType(); 182 183 if (insnType == AbstractInsnNode.LABEL 184 || insnType == AbstractInsnNode.LINE 185 || insnType == AbstractInsnNode.FRAME) 186 { 187 merge(insn + 1, f, subroutine); 188 newControlFlowEdge(insn, insn + 1); 189 } else { 190 current.init(f).execute(insnNode, interpreter); 191 subroutine = subroutine == null ? null : subroutine.copy(); 192 193 if (insnNode instanceof JumpInsnNode) { 194 JumpInsnNode j = (JumpInsnNode) insnNode; 195 if (insnOpcode != GOTO && insnOpcode != JSR) { 196 merge(insn + 1, current, subroutine); 197 newControlFlowEdge(insn, insn + 1); 198 } 199 int jump = insns.indexOf(j.label); 200 if (insnOpcode == JSR) { 201 merge(jump, current, new Subroutine(j.label, 202 m.maxLocals, 203 j)); 204 } else { 205 merge(jump, current, subroutine); 206 } 207 newControlFlowEdge(insn, jump); 208 } else if (insnNode instanceof LookupSwitchInsnNode) { 209 LookupSwitchInsnNode lsi = (LookupSwitchInsnNode) insnNode; 210 int jump = insns.indexOf(lsi.dflt); 211 merge(jump, current, subroutine); 212 newControlFlowEdge(insn, jump); 213 for (int j = 0; j < lsi.labels.size(); ++j) { 214 LabelNode label = (LabelNode) lsi.labels.get(j); 215 jump = insns.indexOf(label); 216 merge(jump, current, subroutine); 217 newControlFlowEdge(insn, jump); 218 } 219 } else if (insnNode instanceof TableSwitchInsnNode) { 220 TableSwitchInsnNode tsi = (TableSwitchInsnNode) insnNode; 221 int jump = insns.indexOf(tsi.dflt); 222 merge(jump, current, subroutine); 223 newControlFlowEdge(insn, jump); 224 for (int j = 0; j < tsi.labels.size(); ++j) { 225 LabelNode label = (LabelNode) tsi.labels.get(j); 226 jump = insns.indexOf(label); 227 merge(jump, current, subroutine); 228 newControlFlowEdge(insn, jump); 229 } 230 } else if (insnOpcode == RET) { 231 if (subroutine == null) { 232 throw new AnalyzerException("RET instruction outside of a sub routine"); 233 } 234 for (int i = 0; i < subroutine.callers.size(); ++i) { 235 Object caller = subroutine.callers.get(i); 236 int call = insns.indexOf((AbstractInsnNode) caller); 237 if (frames[call] != null) { 238 merge(call + 1, 239 frames[call], 240 current, 241 subroutines[call], 242 subroutine.access); 243 newControlFlowEdge(insn, call + 1); 244 } 245 } 246 } else if (insnOpcode != ATHROW 247 && (insnOpcode < IRETURN || insnOpcode > RETURN)) 248 { 249 if (subroutine != null) { 250 if (insnNode instanceof VarInsnNode) { 251 int var = ((VarInsnNode) insnNode).var; 252 subroutine.access[var] = true; 253 if (insnOpcode == LLOAD || insnOpcode == DLOAD 254 || insnOpcode == LSTORE 255 || insnOpcode == DSTORE) 256 { 257 subroutine.access[var + 1] = true; 258 } 259 } else if (insnNode instanceof IincInsnNode) { 260 int var = ((IincInsnNode) insnNode).var; 261 subroutine.access[var] = true; 262 } 263 } 264 merge(insn + 1, current, subroutine); 265 newControlFlowEdge(insn, insn + 1); 266 } 267 } 268 269 List insnHandlers = handlers[insn]; 270 if (insnHandlers != null) { 271 for (int i = 0; i < insnHandlers.size(); ++i) { 272 TryCatchBlockNode tcb = (TryCatchBlockNode) insnHandlers.get(i); 273 Type type; 274 if (tcb.type == null) { 275 type = Type.getObjectType("java/lang/Throwable"); 276 } else { 277 type = Type.getObjectType(tcb.type); 278 } 279 int jump = insns.indexOf(tcb.handler); 280 if (newControlFlowExceptionEdge(insn, jump)) { 281 handler.init(f); 282 handler.clearStack(); 283 handler.push(interpreter.newValue(type)); 284 merge(jump, handler, subroutine); 285 } 286 } 287 } 288 } catch (AnalyzerException e) { 289 throw new AnalyzerException("Error at instruction " + insn 290 + ": " + e.getMessage(), e); 291 } catch (Exception e) { 292 throw new AnalyzerException("Error at instruction " + insn 293 + ": " + e.getMessage(), e); 294 } 295 } 296 297 return frames; 298 } 299 300 private void findSubroutine(int insn, final Subroutine sub, final List calls) 301 throws AnalyzerException 302 { 303 while (true) { 304 if (insn < 0 || insn >= n) { 305 throw new AnalyzerException("Execution can fall off end of the code"); 306 } 307 if (subroutines[insn] != null) { 308 return; 309 } 310 subroutines[insn] = sub.copy(); 311 AbstractInsnNode node = insns.get(insn); 312 313 // calls findSubroutine recursively on normal successors 314 if (node instanceof JumpInsnNode) { 315 if (node.getOpcode() == JSR) { 316 // do not follow a JSR, it leads to another subroutine! 317 calls.add(node); 318 } else { 319 JumpInsnNode jnode = (JumpInsnNode) node; 320 findSubroutine(insns.indexOf(jnode.label), sub, calls); 321 } 322 } else if (node instanceof TableSwitchInsnNode) { 323 TableSwitchInsnNode tsnode = (TableSwitchInsnNode) node; 324 findSubroutine(insns.indexOf(tsnode.dflt), sub, calls); 325 for (int i = tsnode.labels.size() - 1; i >= 0; --i) { 326 LabelNode l = (LabelNode) tsnode.labels.get(i); 327 findSubroutine(insns.indexOf(l), sub, calls); 328 } 329 } else if (node instanceof LookupSwitchInsnNode) { 330 LookupSwitchInsnNode lsnode = (LookupSwitchInsnNode) node; 331 findSubroutine(insns.indexOf(lsnode.dflt), sub, calls); 332 for (int i = lsnode.labels.size() - 1; i >= 0; --i) { 333 LabelNode l = (LabelNode) lsnode.labels.get(i); 334 findSubroutine(insns.indexOf(l), sub, calls); 335 } 336 } 337 338 // calls findSubroutine recursively on exception handler successors 339 List insnHandlers = handlers[insn]; 340 if (insnHandlers != null) { 341 for (int i = 0; i < insnHandlers.size(); ++i) { 342 TryCatchBlockNode tcb = (TryCatchBlockNode) insnHandlers.get(i); 343 findSubroutine(insns.indexOf(tcb.handler), sub, calls); 344 } 345 } 346 347 // if insn does not falls through to the next instruction, return. 348 switch (node.getOpcode()) { 349 case GOTO: 350 case RET: 351 case TABLESWITCH: 352 case LOOKUPSWITCH: 353 case IRETURN: 354 case LRETURN: 355 case FRETURN: 356 case DRETURN: 357 case ARETURN: 358 case RETURN: 359 case ATHROW: 360 return; 361 } 362 insn++; 363 } 364 } 365 366 /** 367 * Returns the symbolic stack frame for each instruction of the last 368 * recently analyzed method. 369 * 370 * @return the symbolic state of the execution stack frame at each bytecode 371 * instruction of the method. The size of the returned array is 372 * equal to the number of instructions (and labels) of the method. A 373 * given frame is <tt>null</tt> if the corresponding instruction 374 * cannot be reached, or if an error occured during the analysis of 375 * the method. 376 */ 377 public Frame[] getFrames() { 378 return frames; 379 } 380 381 /** 382 * Returns the exception handlers for the given instruction. 383 * 384 * @param insn the index of an instruction of the last recently analyzed 385 * method. 386 * @return a list of {@link TryCatchBlockNode} objects. 387 */ 388 public List getHandlers(final int insn) { 389 return handlers[insn]; 390 } 391 392 /** 393 * Constructs a new frame with the given size. 394 * 395 * @param nLocals the maximum number of local variables of the frame. 396 * @param nStack the maximum stack size of the frame. 397 * @return the created frame. 398 */ 399 protected Frame newFrame(final int nLocals, final int nStack) { 400 return new Frame(nLocals, nStack); 401 } 402 403 /** 404 * Constructs a new frame that is identical to the given frame. 405 * 406 * @param src a frame. 407 * @return the created frame. 408 */ 409 protected Frame newFrame(final Frame src) { 410 return new Frame(src); 411 } 412 413 /** 414 * Creates a control flow graph edge. The default implementation of this 415 * method does nothing. It can be overriden in order to construct the 416 * control flow graph of a method (this method is called by the 417 * {@link #analyze analyze} method during its visit of the method's code). 418 * 419 * @param insn an instruction index. 420 * @param successor index of a successor instruction. 421 */ 422 protected void newControlFlowEdge(final int insn, final int successor) { 423 } 424 425 /** 426 * Creates a control flow graph edge corresponding to an exception handler. 427 * The default implementation of this method does nothing. It can be 428 * overriden in order to construct the control flow graph of a method (this 429 * method is called by the {@link #analyze analyze} method during its visit 430 * of the method's code). 431 * 432 * @param insn an instruction index. 433 * @param successor index of a successor instruction. 434 * @return true if this edge must be considered in the data flow analysis 435 * performed by this analyzer, or false otherwise. The default 436 * implementation of this method always returns true. 437 */ 438 protected boolean newControlFlowExceptionEdge( 439 final int insn, 440 final int successor) 441 { 442 return true; 443 } 444 445 // ------------------------------------------------------------------------- 446 447 private void merge( 448 final int insn, 449 final Frame frame, 450 final Subroutine subroutine) throws AnalyzerException 451 { 452 Frame oldFrame = frames[insn]; 453 Subroutine oldSubroutine = subroutines[insn]; 454 boolean changes; 455 456 if (oldFrame == null) { 457 frames[insn] = newFrame(frame); 458 changes = true; 459 } else { 460 changes = oldFrame.merge(frame, interpreter); 461 } 462 463 if (oldSubroutine == null) { 464 if (subroutine != null) { 465 subroutines[insn] = subroutine.copy(); 466 changes = true; 467 } 468 } else { 469 if (subroutine != null) { 470 changes |= oldSubroutine.merge(subroutine); 471 } 472 } 473 if (changes && !queued[insn]) { 474 queued[insn] = true; 475 queue[top++] = insn; 476 } 477 } 478 479 private void merge( 480 final int insn, 481 final Frame beforeJSR, 482 final Frame afterRET, 483 final Subroutine subroutineBeforeJSR, 484 final boolean[] access) throws AnalyzerException 485 { 486 Frame oldFrame = frames[insn]; 487 Subroutine oldSubroutine = subroutines[insn]; 488 boolean changes; 489 490 afterRET.merge(beforeJSR, access); 491 492 if (oldFrame == null) { 493 frames[insn] = newFrame(afterRET); 494 changes = true; 495 } else { 496 changes = oldFrame.merge(afterRET, access); 497 } 498 499 if (oldSubroutine != null && subroutineBeforeJSR != null) { 500 changes |= oldSubroutine.merge(subroutineBeforeJSR); 501 } 502 if (changes && !queued[insn]) { 503 queued[insn] = true; 504 queue[top++] = insn; 505 } 506 } 507} 508