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