• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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  */
15 package javassist.bytecode.analysis;
16 
17 import java.util.Iterator;
18 
19 import javassist.ClassPool;
20 import javassist.CtClass;
21 import javassist.CtMethod;
22 import javassist.NotFoundException;
23 import javassist.bytecode.AccessFlag;
24 import javassist.bytecode.BadBytecode;
25 import javassist.bytecode.CodeAttribute;
26 import javassist.bytecode.CodeIterator;
27 import javassist.bytecode.ConstPool;
28 import javassist.bytecode.Descriptor;
29 import javassist.bytecode.ExceptionTable;
30 import javassist.bytecode.MethodInfo;
31 import 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  */
85 public 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 
ExceptionInfo(int start, int end, int handler, Type type)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      */
analyze(CtClass clazz, MethodInfo method)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      */
analyze(CtMethod method)163     public Frame[] analyze(CtMethod method) throws BadBytecode {
164         return analyze(method.getDeclaringClass(), method.getMethodInfo2());
165     }
166 
analyzeNextEntry(MethodInfo method, CodeIterator iter, IntQueue queue, Executor executor)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 
buildExceptionInfo(MethodInfo method)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 
firstFrame(MethodInfo method, int maxLocals, int maxStack)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 
getNext(CodeIterator iter, int of, int restore)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 
lookAhead(CodeIterator iter, int pos)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 
merge(IntQueue queue, Frame frame, int target)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 
mergeExceptionHandlers(IntQueue queue, MethodInfo method, int pos, Frame frame)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 
mergeJsr(IntQueue queue, Frame frame, Subroutine sub, int pos, int next)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 
mergeLookupSwitch(IntQueue queue, int pos, CodeIterator iter, Frame frame)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 
mergeRet(IntQueue queue, CodeIterator iter, int pos, Frame frame, Subroutine subroutine)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 
mergeTableSwitch(IntQueue queue, int pos, CodeIterator iter, Frame frame)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 
zeroExtend(Type type)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