ExecutionStack.java revision 99409883d9c4c0ffb49b070ce307bb33a9dfe9f1
1/*
2 * Copyright (C) 2007 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.dx.cf.code;
18
19import com.android.dx.rop.type.Type;
20import com.android.dx.rop.type.TypeBearer;
21import com.android.dx.util.ExceptionWithContext;
22import com.android.dx.util.Hex;
23import com.android.dx.util.MutabilityControl;
24
25/**
26 * Representation of a Java method execution stack.
27 *
28 * <p><b>Note:</b> For the most part, the documentation for this class
29 * ignores the distinction between {@link Type} and {@link
30 * TypeBearer}.</p>
31 */
32public final class ExecutionStack extends MutabilityControl {
33    /** {@code non-null;} array of stack contents */
34    private final TypeBearer[] stack;
35
36    /**
37     * {@code >= 0;} stack pointer (points one past the end) / current stack
38     * size
39     */
40    private int stackPtr;
41
42    /**
43     * Constructs an instance.
44     *
45     * @param maxStack {@code >= 0;} the maximum size of the stack for this
46     * instance
47     */
48    public ExecutionStack(int maxStack) {
49        super(maxStack != 0);
50        stack = new TypeBearer[maxStack];
51        stackPtr = 0;
52    }
53
54    /**
55     * Makes and returns a mutable copy of this instance.
56     *
57     * @return {@code non-null;} the copy
58     */
59    public ExecutionStack copy() {
60        ExecutionStack result = new ExecutionStack(stack.length);
61
62        System.arraycopy(stack, 0, result.stack, 0, stack.length);
63        result.stackPtr = stackPtr;
64
65        return result;
66    }
67
68    /**
69     * Annotates (adds context to) the given exception with information
70     * about this instance.
71     *
72     * @param ex {@code non-null;} the exception to annotate
73     */
74    public void annotate(ExceptionWithContext ex) {
75        int limit = stackPtr - 1;
76
77        for (int i = 0; i <= limit; i++) {
78            String idx = (i == limit) ? "top0" : Hex.u2(limit - i);
79
80            ex.addContext("stack[" + idx + "]: " +
81                          stackElementString(stack[i]));
82        }
83    }
84
85    /**
86     * Replaces all the occurrences of the given uninitialized type in
87     * this stack with its initialized equivalent.
88     *
89     * @param type {@code non-null;} type to replace
90     */
91    public void makeInitialized(Type type) {
92        if (stackPtr == 0) {
93            // We have to check for this before checking for immutability.
94            return;
95        }
96
97        throwIfImmutable();
98
99        Type initializedType = type.getInitializedType();
100
101        for (int i = 0; i < stackPtr; i++) {
102            if (stack[i] == type) {
103                stack[i] = initializedType;
104            }
105        }
106    }
107
108    /**
109     * Gets the maximum stack size for this instance.
110     *
111     * @return {@code >= 0;} the max stack size
112     */
113    public int getMaxStack() {
114        return stack.length;
115    }
116
117    /**
118     * Gets the current stack size.
119     *
120     * @return {@code >= 0, < getMaxStack();} the current stack size
121     */
122    public int size() {
123        return stackPtr;
124    }
125
126    /**
127     * Clears the stack. (That is, this method pops everything off.)
128     */
129    public void clear() {
130        throwIfImmutable();
131
132        for (int i = 0; i < stackPtr; i++) {
133            stack[i] = null;
134        }
135
136        stackPtr = 0;
137    }
138
139    /**
140     * Pushes a value of the given type onto the stack.
141     *
142     * @param type {@code non-null;} type of the value
143     * @throws SimException thrown if there is insufficient room on the
144     * stack for the value
145     */
146    public void push(TypeBearer type) {
147        throwIfImmutable();
148
149        int category;
150
151        try {
152            type = type.getFrameType();
153            category = type.getType().getCategory();
154        } catch (NullPointerException ex) {
155            // Elucidate the exception.
156            throw new NullPointerException("type == null");
157        }
158
159        if ((stackPtr + category) > stack.length) {
160            throwSimException("overflow");
161            return;
162        }
163
164        if (category == 2) {
165            stack[stackPtr] = null;
166            stackPtr++;
167        }
168
169        stack[stackPtr] = type;
170        stackPtr++;
171    }
172
173    /**
174     * Peeks at the {@code n}th element down from the top of the stack.
175     * {@code n == 0} means to peek at the top of the stack. Note that
176     * this will return {@code null} if the indicated element is the
177     * deeper half of a category-2 value.
178     *
179     * @param n {@code >= 0;} which element to peek at
180     * @return {@code null-ok;} the type of value stored at that element
181     * @throws SimException thrown if {@code n >= size()}
182     */
183    public TypeBearer peek(int n) {
184        if (n < 0) {
185            throw new IllegalArgumentException("n < 0");
186        }
187
188        if (n >= stackPtr) {
189            return throwSimException("underflow");
190        }
191
192        return stack[stackPtr - n - 1];
193    }
194
195    /**
196     * Peeks at the {@code n}th element down from the top of the
197     * stack, returning the type per se, as opposed to the
198     * <i>type-bearer</i>.  This method is just a convenient shorthand
199     * for {@code peek(n).getType()}.
200     *
201     * @see #peek
202     */
203    public Type peekType(int n) {
204        return peek(n).getType();
205    }
206
207    /**
208     * Pops the top element off of the stack.
209     *
210     * @return {@code non-null;} the type formerly on the top of the stack
211     * @throws SimException thrown if the stack is empty
212     */
213    public TypeBearer pop() {
214        throwIfImmutable();
215
216        TypeBearer result = peek(0);
217
218        stack[stackPtr - 1] = null;
219        stackPtr -= result.getType().getCategory();
220
221        return result;
222    }
223
224    /**
225     * Changes an element already on a stack. This method is useful in limited
226     * contexts, particularly when merging two instances. As such, it places
227     * the following restriction on its behavior: You may only replace
228     * values with other values of the same category.
229     *
230     * @param n {@code >= 0;} which element to change, where {@code 0} is
231     * the top element of the stack
232     * @param type {@code non-null;} type of the new value
233     * @throws SimException thrown if {@code n >= size()} or
234     * the action is otherwise prohibited
235     */
236    public void change(int n, TypeBearer type) {
237        throwIfImmutable();
238
239        try {
240            type = type.getFrameType();
241        } catch (NullPointerException ex) {
242            // Elucidate the exception.
243            throw new NullPointerException("type == null");
244        }
245
246        int idx = stackPtr - n - 1;
247        TypeBearer orig = stack[idx];
248
249        if ((orig == null) ||
250            (orig.getType().getCategory() != type.getType().getCategory())) {
251            throwSimException("incompatible substitution: " +
252                              stackElementString(orig) + " -> " +
253                              stackElementString(type));
254        }
255
256        stack[idx] = type;
257    }
258
259    /**
260     * Merges this stack with another stack. A new instance is returned if
261     * this merge results in a change. If no change results, this instance is
262     * returned.  See {@link Merger#mergeStack(ExecutionStack,ExecutionStack)
263     * Merger.mergeStack()}
264     *
265     * @param other {@code non-null;} a stack to merge with
266     * @return {@code non-null;} the result of the merge
267     */
268    public ExecutionStack merge(ExecutionStack other) {
269        try {
270            return Merger.mergeStack(this, other);
271        } catch (SimException ex) {
272            ex.addContext("underlay stack:");
273            this.annotate(ex);
274            ex.addContext("overlay stack:");
275            other.annotate(ex);
276            throw ex;
277        }
278    }
279
280    /**
281     * Gets the string form for a stack element. This is the same as
282     * {@code toString()} except that {@code null} is converted
283     * to {@code "<invalid>"}.
284     *
285     * @param type {@code null-ok;} the stack element
286     * @return {@code non-null;} the string form
287     */
288    private static String stackElementString(TypeBearer type) {
289        if (type == null) {
290            return "<invalid>";
291        }
292
293        return type.toString();
294    }
295
296    /**
297     * Throws a properly-formatted exception.
298     *
299     * @param msg {@code non-null;} useful message
300     * @return never (keeps compiler happy)
301     */
302    private static TypeBearer throwSimException(String msg) {
303        throw new SimException("stack: " + msg);
304    }
305}
306