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