2 * Copyright (C) 2007 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.dx.cf.code;
19 import com.android.dx.rop.cst.CstType;
20 import com.android.dx.rop.type.StdTypeList;
21 import com.android.dx.rop.type.Type;
22 import com.android.dx.util.ExceptionWithContext;
23 import com.android.dx.util.IntList;
26 * Representation of a Java method execution frame. A frame consists
27 * of a set of locals and a value stack, and it can be told to act on
28 * them to load and store values between them and an "arguments /
31 public final class Frame {
32 /** {@code non-null;} the locals */
33 private final LocalsArray locals;
35 /** {@code non-null;} the stack */
36 private final ExecutionStack stack;
38 /** {@code null-ok;} stack of labels of subroutines that this block is nested in */
39 private final IntList subroutines;
42 * Constructs an instance.
44 * @param locals {@code non-null;} the locals array to use
45 * @param stack {@code non-null;} the execution stack to use
47 private Frame(LocalsArray locals, ExecutionStack stack) {
48 this(locals, stack, IntList.EMPTY);
52 * Constructs an instance.
54 * @param locals {@code non-null;} the locals array to use
55 * @param stack {@code non-null;} the execution stack to use
56 * @param subroutines {@code non-null;} list of subroutine start labels for
57 * subroutines this frame is nested in
59 private Frame(LocalsArray locals,
60 ExecutionStack stack, IntList subroutines) {
62 throw new NullPointerException("locals == null");
66 throw new NullPointerException("stack == null");
69 subroutines.throwIfMutable();
73 this.subroutines = subroutines;
77 * Constructs an instance. The locals array initially consists of
78 * all-uninitialized values (represented as {@code null}s) and
79 * the stack starts out empty.
81 * @param maxLocals {@code >= 0;} the maximum number of locals this instance
83 * @param maxStack {@code >= 0;} the maximum size of the stack for this
86 public Frame(int maxLocals, int maxStack) {
87 this(new OneLocalsArray(maxLocals), new ExecutionStack(maxStack));
91 * Makes and returns a mutable copy of this instance. The copy
92 * contains copies of the locals and stack (that is, it doesn't
93 * share them with the original).
95 * @return {@code non-null;} the copy
98 return new Frame(locals.copy(), stack.copy(), subroutines);
102 * Makes this instance immutable.
104 public void setImmutable() {
105 locals.setImmutable();
106 stack.setImmutable();
107 // "subroutines" is always immutable
111 * Replaces all the occurrences of the given uninitialized type in
112 * this frame with its initialized equivalent.
114 * @param type {@code non-null;} type to replace
116 public void makeInitialized(Type type) {
117 locals.makeInitialized(type);
118 stack.makeInitialized(type);
122 * Gets the locals array for this instance.
124 * @return {@code non-null;} the locals array
126 public LocalsArray getLocals() {
131 * Gets the execution stack for this instance.
133 * @return {@code non-null;} the execution stack
135 public ExecutionStack getStack() {
140 * Returns the largest subroutine nesting this block may be in. An
141 * empty list is returned if this block is not in any subroutine.
142 * Subroutines are identified by the label of their start block. The
143 * list is ordered such that the deepest nesting (the actual subroutine
144 * this block is in) is the last label in the list.
146 * @return {@code non-null;} list as noted above
148 public IntList getSubroutines() {
153 * Initialize this frame with the method's parameters. Used for the first
156 * @param params Type list of method parameters.
158 public void initializeWithParameters(StdTypeList params) {
160 int sz = params.size();
162 for (int i = 0; i < sz; i++) {
163 Type one = params.get(i);
165 at += one.getCategory();
170 * Returns a Frame instance representing the frame state that should
171 * be used when returning from a subroutine. The stack state of all
172 * subroutine invocations is identical, but the locals state may differ.
174 * @param startLabel {@code >=0;} The label of the returning subroutine's
176 * @param subLabel {@code >=0;} A calling label of a subroutine
177 * @return {@code null-ok;} an appropriatly-constructed instance, or null
178 * if label is not in the set
180 public Frame subFrameForLabel(int startLabel, int subLabel) {
181 LocalsArray subLocals = null;
183 if (locals instanceof LocalsArraySet) {
184 subLocals = ((LocalsArraySet)locals).subArrayForLabel(subLabel);
187 IntList newSubroutines;
189 newSubroutines = subroutines.mutableCopy();
191 if (newSubroutines.pop() != startLabel) {
192 throw new RuntimeException("returning from invalid subroutine");
194 newSubroutines.setImmutable();
195 } catch (IndexOutOfBoundsException ex) {
196 throw new RuntimeException("returning from invalid subroutine");
197 } catch (NullPointerException ex) {
198 throw new NullPointerException("can't return from non-subroutine");
201 return (subLocals == null) ? null
202 : new Frame(subLocals, stack, newSubroutines);
206 * Merges two frames. If the merged result is the same as this frame,
207 * then this instance is returned.
209 * @param other {@code non-null;} another frame
210 * @return {@code non-null;} the result of merging the two frames
212 public Frame mergeWith(Frame other) {
213 LocalsArray resultLocals;
214 ExecutionStack resultStack;
215 IntList resultSubroutines;
217 resultLocals = getLocals().merge(other.getLocals());
218 resultStack = getStack().merge(other.getStack());
219 resultSubroutines = mergeSubroutineLists(other.subroutines);
221 resultLocals = adjustLocalsForSubroutines(
222 resultLocals, resultSubroutines);
224 if ((resultLocals == getLocals())
225 && (resultStack == getStack())
226 && subroutines == resultSubroutines) {
230 return new Frame(resultLocals, resultStack, resultSubroutines);
234 * Merges this frame's subroutine lists with another. The result
235 * is the deepest common nesting (effectively, the common prefix of the
238 * @param otherSubroutines label list of subroutine start blocks, from
239 * least-nested to most-nested.
240 * @return {@code non-null;} merged subroutine nest list as described above
242 private IntList mergeSubroutineLists(IntList otherSubroutines) {
243 if (subroutines.equals(otherSubroutines)) {
247 IntList resultSubroutines = new IntList();
249 int szSubroutines = subroutines.size();
250 int szOthers = otherSubroutines.size();
251 for (int i = 0; i < szSubroutines && i < szOthers
252 && (subroutines.get(i) == otherSubroutines.get(i)); i++) {
253 resultSubroutines.add(i);
256 resultSubroutines.setImmutable();
258 return resultSubroutines;
262 * Adjusts a locals array to account for a merged subroutines list.
263 * If a frame merge results in, effectively, a subroutine return through
264 * a throw then the current locals will be a LocalsArraySet that will
265 * need to be trimmed of all OneLocalsArray elements that relevent to
266 * the subroutine that is returning.
268 * @param locals {@code non-null;} LocalsArray from before a merge
269 * @param subroutines {@code non-null;} a label list of subroutine start blocks
270 * representing the subroutine nesting of the block being merged into.
271 * @return {@code non-null;} locals set appropriate for merge
273 private static LocalsArray adjustLocalsForSubroutines(
274 LocalsArray locals, IntList subroutines) {
275 if (! (locals instanceof LocalsArraySet)) {
276 // nothing to see here
280 LocalsArraySet laSet = (LocalsArraySet)locals;
282 if (subroutines.size() == 0) {
284 * We've merged from a subroutine context to a non-subroutine
285 * context, likely via a throw. Our successor will only need
286 * to consider the primary locals state, not the state of
287 * all possible subroutine paths.
290 return laSet.getPrimary();
294 * It's unclear to me if the locals set needs to be trimmed here.
295 * If it does, then I believe it is all of the calling blocks
296 * in the subroutine at the end of "subroutines" passed into
297 * this method that should be removed.
303 * Merges this frame with the frame of a subroutine caller at
304 * {@code predLabel}. Only called on the frame at the first
305 * block of a subroutine.
307 * @param other {@code non-null;} another frame
308 * @param subLabel label of subroutine start block
309 * @param predLabel label of calling block
310 * @return {@code non-null;} the result of merging the two frames
312 public Frame mergeWithSubroutineCaller(Frame other, int subLabel,
314 LocalsArray resultLocals;
315 ExecutionStack resultStack;
317 resultLocals = getLocals().mergeWithSubroutineCaller(
318 other.getLocals(), predLabel);
319 resultStack = getStack().merge(other.getStack());
321 IntList newOtherSubroutines = other.subroutines.mutableCopy();
322 newOtherSubroutines.add(subLabel);
323 newOtherSubroutines.setImmutable();
325 if ((resultLocals == getLocals())
326 && (resultStack == getStack())
327 && subroutines.equals(newOtherSubroutines)) {
331 IntList resultSubroutines;
333 if (subroutines.equals(newOtherSubroutines)) {
334 resultSubroutines = subroutines;
337 * The new subroutines list should be the deepest of the two
338 * lists being merged, but the postfix of the resultant list
339 * must be equal to the shorter list.
341 IntList nonResultSubroutines;
343 if (subroutines.size() > newOtherSubroutines.size()) {
344 resultSubroutines = subroutines;
345 nonResultSubroutines = newOtherSubroutines;
347 resultSubroutines = newOtherSubroutines;
348 nonResultSubroutines = subroutines;
351 int szResult = resultSubroutines.size();
352 int szNonResult = nonResultSubroutines.size();
354 for (int i = szNonResult - 1; i >=0; i-- ) {
355 if (nonResultSubroutines.get(i)
356 != resultSubroutines.get(
357 i + (szResult - szNonResult))) {
359 RuntimeException("Incompatible merged subroutines");
365 return new Frame(resultLocals, resultStack, resultSubroutines);
369 * Makes a frame for a subroutine start block, given that this is the
370 * ending frame of one of the subroutine's calling blocks. Subroutine
371 * calls may be nested and thus may have nested locals state, so we
372 * start with an initial state as seen by the subroutine, but keep track
373 * of the individual locals states that will be expected when the individual
374 * subroutine calls return.
376 * @param subLabel label of subroutine start block
377 * @param callerLabel {@code >=0;} label of the caller block where this frame
379 * @return a new instance to begin a called subroutine.
381 public Frame makeNewSubroutineStartFrame(int subLabel, int callerLabel) {
382 IntList newSubroutines = subroutines.mutableCopy();
383 newSubroutines.add(subLabel);
384 Frame newFrame = new Frame(locals.getPrimary(), stack,
385 IntList.makeImmutable(subLabel));
386 return newFrame.mergeWithSubroutineCaller(this, subLabel, callerLabel);
390 * Makes a new frame for an exception handler block invoked from this
393 * @param exceptionClass exception that the handler block will handle
396 public Frame makeExceptionHandlerStartFrame(CstType exceptionClass) {
397 ExecutionStack newStack = getStack().copy();
400 newStack.push(exceptionClass);
402 return new Frame(getLocals(), newStack, subroutines);
406 * Annotates (adds context to) the given exception with information
409 * @param ex {@code non-null;} the exception to annotate
411 public void annotate(ExceptionWithContext ex) {