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 
17 package com.android.dx.cf.code;
18 
19 import com.android.dex.util.ExceptionWithContext;
20 import com.android.dx.rop.cst.CstType;
21 import com.android.dx.rop.type.StdTypeList;
22 import com.android.dx.rop.type.Type;
23 import com.android.dx.util.IntList;
24 
25 /**
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 /
29  * results" area.
30  */
31 public final class Frame {
32     /** {@code non-null;} the locals */
33     private final LocalsArray locals;
34 
35     /** {@code non-null;} the stack */
36     private final ExecutionStack stack;
37 
38     /** {@code null-ok;} stack of labels of subroutines that this block is nested in */
39     private final IntList subroutines;
40 
41     /**
42      * Constructs an instance.
43      *
44      * @param locals {@code non-null;} the locals array to use
45      * @param stack {@code non-null;} the execution stack to use
46      */
Frame(LocalsArray locals, ExecutionStack stack)47     private Frame(LocalsArray locals, ExecutionStack stack) {
48         this(locals, stack, IntList.EMPTY);
49     }
50 
51     /**
52      * Constructs an instance.
53      *
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
58      */
Frame(LocalsArray locals, ExecutionStack stack, IntList subroutines)59     private Frame(LocalsArray locals,
60             ExecutionStack stack, IntList subroutines) {
61         if (locals == null) {
62             throw new NullPointerException("locals == null");
63         }
64 
65         if (stack == null) {
66             throw new NullPointerException("stack == null");
67         }
68 
69         subroutines.throwIfMutable();
70 
71         this.locals = locals;
72         this.stack = stack;
73         this.subroutines = subroutines;
74     }
75 
76     /**
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.
80      *
81      * @param maxLocals {@code >= 0;} the maximum number of locals this instance
82      * can refer to
83      * @param maxStack {@code >= 0;} the maximum size of the stack for this
84      * instance
85      */
Frame(int maxLocals, int maxStack)86     public Frame(int maxLocals, int maxStack) {
87         this(new OneLocalsArray(maxLocals), new ExecutionStack(maxStack));
88     }
89 
90     /**
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).
94      *
95      * @return {@code non-null;} the copy
96      */
copy()97     public Frame copy() {
98         return new Frame(locals.copy(), stack.copy(), subroutines);
99     }
100 
101     /**
102      * Makes this instance immutable.
103      */
setImmutable()104     public void setImmutable() {
105         locals.setImmutable();
106         stack.setImmutable();
107         // "subroutines" is always immutable
108     }
109 
110     /**
111      * Replaces all the occurrences of the given uninitialized type in
112      * this frame with its initialized equivalent.
113      *
114      * @param type {@code non-null;} type to replace
115      */
makeInitialized(Type type)116     public void makeInitialized(Type type) {
117         locals.makeInitialized(type);
118         stack.makeInitialized(type);
119     }
120 
121     /**
122      * Gets the locals array for this instance.
123      *
124      * @return {@code non-null;} the locals array
125      */
getLocals()126     public LocalsArray getLocals() {
127         return locals;
128     }
129 
130     /**
131      * Gets the execution stack for this instance.
132      *
133      * @return {@code non-null;} the execution stack
134      */
getStack()135     public ExecutionStack getStack() {
136         return stack;
137     }
138 
139     /**
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.
145      *
146      * @return {@code non-null;} list as noted above
147      */
getSubroutines()148     public IntList getSubroutines() {
149         return subroutines;
150     }
151 
152     /**
153      * Initialize this frame with the method's parameters. Used for the first
154      * frame.
155      *
156      * @param params Type list of method parameters.
157      */
initializeWithParameters(StdTypeList params)158     public void initializeWithParameters(StdTypeList params) {
159         int at = 0;
160         int sz = params.size();
161 
162         for (int i = 0; i < sz; i++) {
163              Type one = params.get(i);
164              locals.set(at, one);
165              at += one.getCategory();
166         }
167     }
168 
169     /**
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.
173      *
174      * @param startLabel {@code >=0;} The label of the returning subroutine's
175      * start block
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
179      */
subFrameForLabel(int startLabel, int subLabel)180     public Frame subFrameForLabel(int startLabel, int subLabel) {
181         LocalsArray subLocals = null;
182 
183         if (locals instanceof LocalsArraySet) {
184             subLocals = ((LocalsArraySet)locals).subArrayForLabel(subLabel);
185         }
186 
187         IntList newSubroutines;
188         try {
189             newSubroutines = subroutines.mutableCopy();
190 
191             if (newSubroutines.pop() != startLabel) {
192                 throw new RuntimeException("returning from invalid subroutine");
193             }
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");
199         }
200 
201         return (subLocals == null) ? null
202                 : new Frame(subLocals, stack, newSubroutines);
203     }
204 
205     /**
206      * Merges two frames. If the merged result is the same as this frame,
207      * then this instance is returned.
208      *
209      * @param other {@code non-null;} another frame
210      * @return {@code non-null;} the result of merging the two frames
211      */
mergeWith(Frame other)212     public Frame mergeWith(Frame other) {
213         LocalsArray resultLocals;
214         ExecutionStack resultStack;
215         IntList resultSubroutines;
216 
217         resultLocals = getLocals().merge(other.getLocals());
218         resultStack = getStack().merge(other.getStack());
219         resultSubroutines = mergeSubroutineLists(other.subroutines);
220 
221         resultLocals = adjustLocalsForSubroutines(
222                 resultLocals, resultSubroutines);
223 
224         if ((resultLocals == getLocals())
225                 && (resultStack == getStack())
226                 && subroutines == resultSubroutines) {
227             return this;
228         }
229 
230         return new Frame(resultLocals, resultStack, resultSubroutines);
231     }
232 
233     /**
234      * Merges this frame's subroutine lists with another. The result
235      * is the deepest common nesting (effectively, the common prefix of the
236      * two lists).
237      *
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
241      */
mergeSubroutineLists(IntList otherSubroutines)242     private IntList mergeSubroutineLists(IntList otherSubroutines) {
243         if (subroutines.equals(otherSubroutines)) {
244             return subroutines;
245         }
246 
247         IntList resultSubroutines = new IntList();
248 
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);
254         }
255 
256         resultSubroutines.setImmutable();
257 
258         return resultSubroutines;
259     }
260 
261     /**
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.
267      *
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
272      */
adjustLocalsForSubroutines( LocalsArray locals, IntList subroutines)273     private static LocalsArray adjustLocalsForSubroutines(
274             LocalsArray locals, IntList subroutines) {
275         if (! (locals instanceof LocalsArraySet)) {
276             // nothing to see here
277             return locals;
278         }
279 
280         LocalsArraySet laSet = (LocalsArraySet)locals;
281 
282         if (subroutines.size() == 0) {
283             /*
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.
288              */
289 
290             return laSet.getPrimary();
291         }
292 
293         /*
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.
298          */
299         return laSet;
300     }
301 
302     /**
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.
306      *
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
311      */
mergeWithSubroutineCaller(Frame other, int subLabel, int predLabel)312     public Frame mergeWithSubroutineCaller(Frame other, int subLabel,
313             int predLabel) {
314         LocalsArray resultLocals;
315         ExecutionStack resultStack;
316 
317         resultLocals = getLocals().mergeWithSubroutineCaller(
318                 other.getLocals(), predLabel);
319         resultStack = getStack().merge(other.getStack());
320 
321         IntList newOtherSubroutines = other.subroutines.mutableCopy();
322         newOtherSubroutines.add(subLabel);
323         newOtherSubroutines.setImmutable();
324 
325         if ((resultLocals == getLocals())
326                 && (resultStack == getStack())
327                 && subroutines.equals(newOtherSubroutines)) {
328             return this;
329         }
330 
331         IntList resultSubroutines;
332 
333         if (subroutines.equals(newOtherSubroutines)) {
334             resultSubroutines = subroutines;
335         } else {
336             /*
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.
340              */
341             IntList nonResultSubroutines;
342 
343             if (subroutines.size() > newOtherSubroutines.size()) {
344                 resultSubroutines = subroutines;
345                 nonResultSubroutines = newOtherSubroutines;
346             } else {
347                 resultSubroutines = newOtherSubroutines;
348                 nonResultSubroutines = subroutines;
349             }
350 
351             int szResult = resultSubroutines.size();
352             int szNonResult = nonResultSubroutines.size();
353 
354             for (int i = szNonResult - 1; i >=0; i-- ) {
355                 if (nonResultSubroutines.get(i)
356                         != resultSubroutines.get(
357                         i + (szResult - szNonResult))) {
358                     throw new
359                             RuntimeException("Incompatible merged subroutines");
360                 }
361             }
362 
363         }
364 
365         return new Frame(resultLocals, resultStack, resultSubroutines);
366     }
367 
368     /**
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.
375      *
376      * @param subLabel label of subroutine start block
377      * @param callerLabel {@code >=0;} label of the caller block where this frame
378      * came from.
379      * @return a new instance to begin a called subroutine.
380      */
makeNewSubroutineStartFrame(int subLabel, int callerLabel)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);
387     }
388 
389     /**
390      * Makes a new frame for an exception handler block invoked from this
391      * frame.
392      *
393      * @param exceptionClass exception that the handler block will handle
394      * @return new frame
395      */
makeExceptionHandlerStartFrame(CstType exceptionClass)396     public Frame makeExceptionHandlerStartFrame(CstType exceptionClass) {
397         ExecutionStack newStack = getStack().copy();
398 
399         newStack.clear();
400         newStack.push(exceptionClass);
401 
402         return new Frame(getLocals(), newStack, subroutines);
403     }
404 
405     /**
406      * Annotates (adds context to) the given exception with information
407      * about this frame.
408      *
409      * @param ex {@code non-null;} the exception to annotate
410      */
annotate(ExceptionWithContext ex)411     public void annotate(ExceptionWithContext ex) {
412         locals.annotate(ex);
413         stack.annotate(ex);
414     }
415 }
416