1/* 2 * Javassist, a Java-bytecode translator toolkit. 3 * Copyright (C) 1999-2007 Shigeru Chiba, and others. All Rights Reserved. 4 * 5 * The contents of this file are subject to the Mozilla Public License Version 6 * 1.1 (the "License"); you may not use this file except in compliance with 7 * the License. Alternatively, the contents of this file may be used under 8 * the terms of the GNU Lesser General Public License Version 2.1 or later. 9 * 10 * Software distributed under the License is distributed on an "AS IS" basis, 11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 12 * for the specific language governing rights and limitations under the 13 * License. 14 */ 15package javassist.bytecode.analysis; 16 17import java.util.Iterator; 18 19import javassist.ClassPool; 20import javassist.CtClass; 21import javassist.CtMethod; 22import javassist.NotFoundException; 23import javassist.bytecode.AccessFlag; 24import javassist.bytecode.BadBytecode; 25import javassist.bytecode.CodeAttribute; 26import javassist.bytecode.CodeIterator; 27import javassist.bytecode.ConstPool; 28import javassist.bytecode.Descriptor; 29import javassist.bytecode.ExceptionTable; 30import javassist.bytecode.MethodInfo; 31import javassist.bytecode.Opcode; 32 33/** 34 * A data-flow analyzer that determines the type state of the stack and local 35 * variable table at every reachable instruction in a method. During analysis, 36 * bytecode verification is performed in a similar manner to that described 37 * in the JVM specification. 38 * 39 * <p>Example:</p> 40 * 41 * <pre> 42 * // Method to analyze 43 * public Object doSomething(int x) { 44 * Number n; 45 * if (x < 5) { 46 * n = new Double(0); 47 * } else { 48 * n = new Long(0); 49 * } 50 * 51 * return n; 52 * } 53 * 54 * // Which compiles to: 55 * // 0: iload_1 56 * // 1: iconst_5 57 * // 2: if_icmpge 17 58 * // 5: new #18; //class java/lang/Double 59 * // 8: dup 60 * // 9: dconst_0 61 * // 10: invokespecial #44; //Method java/lang/Double."<init>":(D)V 62 * // 13: astore_2 63 * // 14: goto 26 64 * // 17: new #16; //class java/lang/Long 65 * // 20: dup 66 * // 21: lconst_1 67 * // 22: invokespecial #47; //Method java/lang/Long."<init>":(J)V 68 * // 25: astore_2 69 * // 26: aload_2 70 * // 27: areturn 71 * 72 * public void analyzeIt(CtClass clazz, MethodInfo method) { 73 * Analyzer analyzer = new Analyzer(); 74 * Frame[] frames = analyzer.analyze(clazz, method); 75 * frames[0].getLocal(0).getCtClass(); // returns clazz; 76 * frames[0].getLocal(1).getCtClass(); // returns java.lang.String 77 * frames[1].peek(); // returns Type.INTEGER 78 * frames[27].peek().getCtClass(); // returns java.lang.Number 79 * } 80 * </pre> 81 * 82 * @see FramePrinter 83 * @author Jason T. Greene 84 */ 85public class Analyzer implements Opcode { 86 private final SubroutineScanner scanner = new SubroutineScanner(); 87 private CtClass clazz; 88 private ExceptionInfo[] exceptions; 89 private Frame[] frames; 90 private Subroutine[] subroutines; 91 92 private static class ExceptionInfo { 93 private int end; 94 private int handler; 95 private int start; 96 private Type type; 97 98 private ExceptionInfo(int start, int end, int handler, Type type) { 99 this.start = start; 100 this.end = end; 101 this.handler = handler; 102 this.type = type; 103 } 104 } 105 106 /** 107 * Performs data-flow analysis on a method and returns an array, indexed by 108 * instruction position, containing the starting frame state of all reachable 109 * instructions. Non-reachable code, and illegal code offsets are represented 110 * as a null in the frame state array. This can be used to detect dead code. 111 * 112 * If the method does not contain code (it is either native or abstract), null 113 * is returned. 114 * 115 * @param clazz the declaring class of the method 116 * @param method the method to analyze 117 * @return an array, indexed by instruction position, of the starting frame state, 118 * or null if this method doesn't have code 119 * @throws BadBytecode if the bytecode does not comply with the JVM specification 120 */ 121 public Frame[] analyze(CtClass clazz, MethodInfo method) throws BadBytecode { 122 this.clazz = clazz; 123 CodeAttribute codeAttribute = method.getCodeAttribute(); 124 // Native or Abstract 125 if (codeAttribute == null) 126 return null; 127 128 int maxLocals = codeAttribute.getMaxLocals(); 129 int maxStack = codeAttribute.getMaxStack(); 130 int codeLength = codeAttribute.getCodeLength(); 131 132 CodeIterator iter = codeAttribute.iterator(); 133 IntQueue queue = new IntQueue(); 134 135 exceptions = buildExceptionInfo(method); 136 subroutines = scanner.scan(method); 137 138 Executor executor = new Executor(clazz.getClassPool(), method.getConstPool()); 139 frames = new Frame[codeLength]; 140 frames[iter.lookAhead()] = firstFrame(method, maxLocals, maxStack); 141 queue.add(iter.next()); 142 while (!queue.isEmpty()) { 143 analyzeNextEntry(method, iter, queue, executor); 144 } 145 146 return frames; 147 } 148 149 /** 150 * Performs data-flow analysis on a method and returns an array, indexed by 151 * instruction position, containing the starting frame state of all reachable 152 * instructions. Non-reachable code, and illegal code offsets are represented 153 * as a null in the frame state array. This can be used to detect dead code. 154 * 155 * If the method does not contain code (it is either native or abstract), null 156 * is returned. 157 * 158 * @param method the method to analyze 159 * @return an array, indexed by instruction position, of the starting frame state, 160 * or null if this method doesn't have code 161 * @throws BadBytecode if the bytecode does not comply with the JVM specification 162 */ 163 public Frame[] analyze(CtMethod method) throws BadBytecode { 164 return analyze(method.getDeclaringClass(), method.getMethodInfo2()); 165 } 166 167 private void analyzeNextEntry(MethodInfo method, CodeIterator iter, 168 IntQueue queue, Executor executor) throws BadBytecode { 169 int pos = queue.take(); 170 iter.move(pos); 171 iter.next(); 172 173 Frame frame = frames[pos].copy(); 174 Subroutine subroutine = subroutines[pos]; 175 176 try { 177 executor.execute(method, pos, iter, frame, subroutine); 178 } catch (RuntimeException e) { 179 throw new BadBytecode(e.getMessage() + "[pos = " + pos + "]", e); 180 } 181 182 int opcode = iter.byteAt(pos); 183 184 if (opcode == TABLESWITCH) { 185 mergeTableSwitch(queue, pos, iter, frame); 186 } else if (opcode == LOOKUPSWITCH) { 187 mergeLookupSwitch(queue, pos, iter, frame); 188 } else if (opcode == RET) { 189 mergeRet(queue, iter, pos, frame, subroutine); 190 } else if (Util.isJumpInstruction(opcode)) { 191 int target = Util.getJumpTarget(pos, iter); 192 193 if (Util.isJsr(opcode)) { 194 // Merge the state before the jsr into the next instruction 195 mergeJsr(queue, frames[pos], subroutines[target], pos, lookAhead(iter, pos)); 196 } else if (! Util.isGoto(opcode)) { 197 merge(queue, frame, lookAhead(iter, pos)); 198 } 199 200 merge(queue, frame, target); 201 } else if (opcode != ATHROW && ! Util.isReturn(opcode)) { 202 // Can advance to next instruction 203 merge(queue, frame, lookAhead(iter, pos)); 204 } 205 206 // Merge all exceptions that are reachable from this instruction. 207 // The redundancy is intentional, since the state must be based 208 // on the current instruction frame. 209 mergeExceptionHandlers(queue, method, pos, frame); 210 } 211 212 private ExceptionInfo[] buildExceptionInfo(MethodInfo method) { 213 ConstPool constPool = method.getConstPool(); 214 ClassPool classes = clazz.getClassPool(); 215 216 ExceptionTable table = method.getCodeAttribute().getExceptionTable(); 217 ExceptionInfo[] exceptions = new ExceptionInfo[table.size()]; 218 for (int i = 0; i < table.size(); i++) { 219 int index = table.catchType(i); 220 Type type; 221 try { 222 type = index == 0 ? Type.THROWABLE : Type.get(classes.get(constPool.getClassInfo(index))); 223 } catch (NotFoundException e) { 224 throw new IllegalStateException(e.getMessage()); 225 } 226 227 exceptions[i] = new ExceptionInfo(table.startPc(i), table.endPc(i), table.handlerPc(i), type); 228 } 229 230 return exceptions; 231 } 232 233 private Frame firstFrame(MethodInfo method, int maxLocals, int maxStack) { 234 int pos = 0; 235 236 Frame first = new Frame(maxLocals, maxStack); 237 if ((method.getAccessFlags() & AccessFlag.STATIC) == 0) { 238 first.setLocal(pos++, Type.get(clazz)); 239 } 240 241 CtClass[] parameters; 242 try { 243 parameters = Descriptor.getParameterTypes(method.getDescriptor(), clazz.getClassPool()); 244 } catch (NotFoundException e) { 245 throw new RuntimeException(e); 246 } 247 248 for (int i = 0; i < parameters.length; i++) { 249 Type type = zeroExtend(Type.get(parameters[i])); 250 first.setLocal(pos++, type); 251 if (type.getSize() == 2) 252 first.setLocal(pos++, Type.TOP); 253 } 254 255 return first; 256 } 257 258 private int getNext(CodeIterator iter, int of, int restore) throws BadBytecode { 259 iter.move(of); 260 iter.next(); 261 int next = iter.lookAhead(); 262 iter.move(restore); 263 iter.next(); 264 265 return next; 266 } 267 268 private int lookAhead(CodeIterator iter, int pos) throws BadBytecode { 269 if (! iter.hasNext()) 270 throw new BadBytecode("Execution falls off end! [pos = " + pos + "]"); 271 272 return iter.lookAhead(); 273 } 274 275 276 private void merge(IntQueue queue, Frame frame, int target) { 277 Frame old = frames[target]; 278 boolean changed; 279 280 if (old == null) { 281 frames[target] = frame.copy(); 282 changed = true; 283 } else { 284 changed = old.merge(frame); 285 } 286 287 if (changed) { 288 queue.add(target); 289 } 290 } 291 292 private void mergeExceptionHandlers(IntQueue queue, MethodInfo method, int pos, Frame frame) { 293 for (int i = 0; i < exceptions.length; i++) { 294 ExceptionInfo exception = exceptions[i]; 295 296 // Start is inclusive, while end is exclusive! 297 if (pos >= exception.start && pos < exception.end) { 298 Frame newFrame = frame.copy(); 299 newFrame.clearStack(); 300 newFrame.push(exception.type); 301 merge(queue, newFrame, exception.handler); 302 } 303 } 304 } 305 306 private void mergeJsr(IntQueue queue, Frame frame, Subroutine sub, int pos, int next) throws BadBytecode { 307 if (sub == null) 308 throw new BadBytecode("No subroutine at jsr target! [pos = " + pos + "]"); 309 310 Frame old = frames[next]; 311 boolean changed = false; 312 313 if (old == null) { 314 old = frames[next] = frame.copy(); 315 changed = true; 316 } else { 317 for (int i = 0; i < frame.localsLength(); i++) { 318 // Skip everything accessed by a subroutine, mergeRet must handle this 319 if (!sub.isAccessed(i)) { 320 Type oldType = old.getLocal(i); 321 Type newType = frame.getLocal(i); 322 if (oldType == null) { 323 old.setLocal(i, newType); 324 changed = true; 325 continue; 326 } 327 328 newType = oldType.merge(newType); 329 // Always set the type, in case a multi-type switched to a standard type. 330 old.setLocal(i, newType); 331 if (!newType.equals(oldType) || newType.popChanged()) 332 changed = true; 333 } 334 } 335 } 336 337 if (! old.isJsrMerged()) { 338 old.setJsrMerged(true); 339 changed = true; 340 } 341 342 if (changed && old.isRetMerged()) 343 queue.add(next); 344 345 } 346 347 private void mergeLookupSwitch(IntQueue queue, int pos, CodeIterator iter, Frame frame) throws BadBytecode { 348 int index = (pos & ~3) + 4; 349 // default 350 merge(queue, frame, pos + iter.s32bitAt(index)); 351 int npairs = iter.s32bitAt(index += 4); 352 int end = npairs * 8 + (index += 4); 353 354 // skip "match" 355 for (index += 4; index < end; index += 8) { 356 int target = iter.s32bitAt(index) + pos; 357 merge(queue, frame, target); 358 } 359 } 360 361 private void mergeRet(IntQueue queue, CodeIterator iter, int pos, Frame frame, Subroutine subroutine) throws BadBytecode { 362 if (subroutine == null) 363 throw new BadBytecode("Ret on no subroutine! [pos = " + pos + "]"); 364 365 Iterator callerIter = subroutine.callers().iterator(); 366 while (callerIter.hasNext()) { 367 int caller = ((Integer) callerIter.next()).intValue(); 368 int returnLoc = getNext(iter, caller, pos); 369 boolean changed = false; 370 371 Frame old = frames[returnLoc]; 372 if (old == null) { 373 old = frames[returnLoc] = frame.copyStack(); 374 changed = true; 375 } else { 376 changed = old.mergeStack(frame); 377 } 378 379 for (Iterator i = subroutine.accessed().iterator(); i.hasNext(); ) { 380 int index = ((Integer)i.next()).intValue(); 381 Type oldType = old.getLocal(index); 382 Type newType = frame.getLocal(index); 383 if (oldType != newType) { 384 old.setLocal(index, newType); 385 changed = true; 386 } 387 } 388 389 if (! old.isRetMerged()) { 390 old.setRetMerged(true); 391 changed = true; 392 } 393 394 if (changed && old.isJsrMerged()) 395 queue.add(returnLoc); 396 } 397 } 398 399 400 private void mergeTableSwitch(IntQueue queue, int pos, CodeIterator iter, Frame frame) throws BadBytecode { 401 // Skip 4 byte alignment padding 402 int index = (pos & ~3) + 4; 403 // default 404 merge(queue, frame, pos + iter.s32bitAt(index)); 405 int low = iter.s32bitAt(index += 4); 406 int high = iter.s32bitAt(index += 4); 407 int end = (high - low + 1) * 4 + (index += 4); 408 409 // Offset table 410 for (; index < end; index += 4) { 411 int target = iter.s32bitAt(index) + pos; 412 merge(queue, frame, target); 413 } 414 } 415 416 private Type zeroExtend(Type type) { 417 if (type == Type.SHORT || type == Type.BYTE || type == Type.CHAR || type == Type.BOOLEAN) 418 return Type.INTEGER; 419 420 return type; 421 } 422} 423