You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Analyzer.java 15KB

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