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