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
17
18/**
19 * Represents the stack frame and local variable table at a particular point in time.
20 *
21 * @author Jason T. Greene
22 */
23public class Frame {
24    private Type[] locals;
25    private Type[] stack;
26    private int top;
27    private boolean jsrMerged;
28    private boolean retMerged;
29
30    /**
31     * Create a new frame with the specified local variable table size, and max stack size
32     *
33     * @param locals the number of local variable table entries
34     * @param stack the maximum stack size
35     */
36    public Frame(int locals, int stack) {
37        this.locals = new Type[locals];
38        this.stack = new Type[stack];
39    }
40
41    /**
42     * Returns the local varaible table entry at index.
43     *
44     * @param index the position in the table
45     * @return the type if one exists, or null if the position is empty
46     */
47    public Type getLocal(int index) {
48        return locals[index];
49    }
50
51    /**
52     * Sets the local variable table entry at index to a type.
53     *
54     * @param index the position in the table
55     * @param type the type to set at the position
56     */
57    public void setLocal(int index, Type type) {
58        locals[index] = type;
59    }
60
61
62    /**
63     * Returns the type on the stack at the specified index.
64     *
65     * @param index the position on the stack
66     * @return the type of the stack position
67     */
68    public Type getStack(int index) {
69        return stack[index];
70    }
71
72    /**
73     * Sets the type of the stack position
74     *
75     * @param index the position on the stack
76     * @param type the type to set
77     */
78    public void setStack(int index, Type type) {
79        stack[index] = type;
80    }
81
82    /**
83     * Empties the stack
84     */
85    public void clearStack() {
86        top = 0;
87    }
88
89    /**
90     * Gets the index of the type sitting at the top of the stack.
91     * This is not to be confused with a length operation which
92     * would return the number of elements, not the position of
93     * the last element.
94     *
95     * @return the position of the element at the top of the stack
96     */
97    public int getTopIndex() {
98        return top - 1;
99    }
100
101    /**
102     * Returns the number of local variable table entries, specified
103     * at construction.
104     *
105     * @return the number of local variable table entries
106     */
107    public int localsLength() {
108        return locals.length;
109    }
110
111    /**
112     * Gets the top of the stack without altering it
113     *
114     * @return the top of the stack
115     */
116    public Type peek() {
117        if (top < 1)
118            throw new IndexOutOfBoundsException("Stack is empty");
119
120        return stack[top - 1];
121    }
122
123    /**
124     * Alters the stack to contain one less element and return it.
125     *
126     * @return the element popped from the stack
127     */
128    public Type pop() {
129        if (top < 1)
130            throw new IndexOutOfBoundsException("Stack is empty");
131        return stack[--top];
132    }
133
134    /**
135     * Alters the stack by placing the passed type on the top
136     *
137     * @param type the type to add to the top
138     */
139    public void push(Type type) {
140        stack[top++] = type;
141    }
142
143
144    /**
145     * Makes a shallow copy of this frame, i.e. the type instances will
146     * remain the same.
147     *
148     * @return the shallow copy
149     */
150    public Frame copy() {
151        Frame frame = new Frame(locals.length, stack.length);
152        System.arraycopy(locals, 0, frame.locals, 0, locals.length);
153        System.arraycopy(stack, 0, frame.stack, 0, stack.length);
154        frame.top = top;
155        return frame;
156    }
157
158    /**
159     * Makes a shallow copy of the stack portion of this frame. The local
160     * variable table size will be copied, but its contents will be empty.
161     *
162     * @return the shallow copy of the stack
163     */
164    public Frame copyStack() {
165        Frame frame = new Frame(locals.length, stack.length);
166        System.arraycopy(stack, 0, frame.stack, 0, stack.length);
167        frame.top = top;
168        return frame;
169    }
170
171    /**
172     * Merges all types on the stack of this frame instance with that of the specified frame.
173     * The local variable table is left untouched.
174     *
175     * @param frame the frame to merge the stack from
176     * @return true if any changes where made
177     */
178    public boolean mergeStack(Frame frame) {
179        boolean changed = false;
180        if (top != frame.top)
181            throw new RuntimeException("Operand stacks could not be merged, they are different sizes!");
182
183        for (int i = 0; i < top; i++) {
184            if (stack[i] != null) {
185                Type prev = stack[i];
186                Type merged = prev.merge(frame.stack[i]);
187                if (merged == Type.BOGUS)
188                    throw new RuntimeException("Operand stacks could not be merged due to differing primitive types: pos = " + i);
189
190                stack[i] = merged;
191                // always replace the instance in case a multi-interface type changes to a normal Type
192                if ((! merged.equals(prev)) || merged.popChanged()) {
193                    changed = true;
194                }
195            }
196        }
197
198        return changed;
199    }
200
201    /**
202     * Merges all types on the stack and local variable table of this frame with that of the specified
203     * type.
204     *
205     * @param frame the frame to merge with
206     * @return true if any changes to this frame where made by this merge
207     */
208    public boolean merge(Frame frame) {
209        boolean changed = false;
210
211        // Local variable table
212        for (int i = 0; i < locals.length; i++) {
213            if (locals[i] != null) {
214                Type prev = locals[i];
215                Type merged = prev.merge(frame.locals[i]);
216                // always replace the instance in case a multi-interface type changes to a normal Type
217                locals[i] = merged;
218                if (! merged.equals(prev) || merged.popChanged()) {
219                    changed = true;
220                }
221            } else if (frame.locals[i] != null) {
222                locals[i] = frame.locals[i];
223                changed = true;
224            }
225        }
226
227        changed |= mergeStack(frame);
228        return changed;
229    }
230
231    public String toString() {
232        StringBuffer buffer = new StringBuffer();
233
234        buffer.append("locals = [");
235        for (int i = 0; i < locals.length; i++) {
236            buffer.append(locals[i] == null ? "empty" : locals[i].toString());
237            if (i < locals.length - 1)
238                buffer.append(", ");
239        }
240        buffer.append("] stack = [");
241        for (int i = 0; i < top; i++) {
242            buffer.append(stack[i]);
243            if (i < top - 1)
244                buffer.append(", ");
245        }
246        buffer.append("]");
247
248        return buffer.toString();
249    }
250
251    /**
252     * Whether or not state from the source JSR instruction has been merged
253     *
254     * @return true if JSR state has been merged
255     */
256    boolean isJsrMerged() {
257        return jsrMerged;
258    }
259
260    /**
261     * Sets whether of not the state from the source JSR instruction has been merged
262     *
263     * @param jsrMerged true if merged, otherwise false
264     */
265    void setJsrMerged(boolean jsrMerged) {
266        this.jsrMerged = jsrMerged;
267    }
268
269    /**
270     * Whether or not state from the RET instruction, of the subroutine that was jumped
271     * to has been merged.
272     *
273     * @return true if RET state has been merged
274     */
275    boolean isRetMerged() {
276        return retMerged;
277    }
278
279    /**
280     * Sets whether or not state from the RET instruction, of the subroutine that was jumped
281     * to has been merged.
282     *
283     * @param retMerged true if RET state has been merged
284     */
285    void setRetMerged(boolean retMerged) {
286        this.retMerged = retMerged;
287    }
288}
289