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.dexgen.dex.code; 18 19 import com.android.dexgen.rop.code.RegisterSpec; 20 import com.android.dexgen.rop.code.RegisterSpecSet; 21 import com.android.dexgen.rop.cst.CstType; 22 import com.android.dexgen.rop.cst.CstUtf8; 23 import com.android.dexgen.rop.type.Type; 24 import com.android.dexgen.util.FixedSizeList; 25 26 import java.io.PrintStream; 27 import java.util.ArrayList; 28 import java.util.Arrays; 29 30 /** 31 * List of local variables. Each local variable entry indicates a 32 * range of code which it is valid for, a register number, a name, 33 * and a type. 34 */ 35 public final class LocalList extends FixedSizeList { 36 /** {@code non-null;} empty instance */ 37 public static final LocalList EMPTY = new LocalList(0); 38 39 /** whether to run the self-check code */ 40 private static final boolean DEBUG = false; 41 42 /** 43 * Constructs an instance. All indices initially contain {@code null}. 44 * 45 * @param size {@code >= 0;} the size of the list 46 */ LocalList(int size)47 public LocalList(int size) { 48 super(size); 49 } 50 51 /** 52 * Gets the element at the given index. It is an error to call 53 * this with the index for an element which was never set; if you 54 * do that, this will throw {@code NullPointerException}. 55 * 56 * @param n {@code >= 0, < size();} which index 57 * @return {@code non-null;} element at that index 58 */ get(int n)59 public Entry get(int n) { 60 return (Entry) get0(n); 61 } 62 63 /** 64 * Sets the entry at the given index. 65 * 66 * @param n {@code >= 0, < size();} which index 67 * @param entry {@code non-null;} the entry to set at {@code n} 68 */ set(int n, Entry entry)69 public void set(int n, Entry entry) { 70 set0(n, entry); 71 } 72 73 /** 74 * Does a human-friendly dump of this instance. 75 * 76 * @param out {@code non-null;} where to dump 77 * @param prefix {@code non-null;} prefix to attach to each line of output 78 */ debugPrint(PrintStream out, String prefix)79 public void debugPrint(PrintStream out, String prefix) { 80 int sz = size(); 81 82 for (int i = 0; i < sz; i++) { 83 out.print(prefix); 84 out.println(get(i)); 85 } 86 } 87 88 /** 89 * Disposition of a local entry. 90 */ 91 public static enum Disposition { 92 /** local started (introduced) */ 93 START, 94 95 /** local ended without being replaced */ 96 END_SIMPLY, 97 98 /** local ended because it was directly replaced */ 99 END_REPLACED, 100 101 /** local ended because it was moved to a different register */ 102 END_MOVED, 103 104 /** 105 * local ended because the previous local clobbered this one 106 * (because it is category-2) 107 */ 108 END_CLOBBERED_BY_PREV, 109 110 /** 111 * local ended because the next local clobbered this one 112 * (because this one is a category-2) 113 */ 114 END_CLOBBERED_BY_NEXT; 115 } 116 117 /** 118 * Entry in a local list. 119 */ 120 public static class Entry implements Comparable<Entry> { 121 /** {@code >= 0;} address */ 122 private final int address; 123 124 /** {@code non-null;} disposition of the local */ 125 private final Disposition disposition; 126 127 /** {@code non-null;} register spec representing the variable */ 128 private final RegisterSpec spec; 129 130 /** {@code non-null;} variable type (derived from {@code spec}) */ 131 private final CstType type; 132 133 /** 134 * Constructs an instance. 135 * 136 * @param address {@code >= 0;} address 137 * @param disposition {@code non-null;} disposition of the local 138 * @param spec {@code non-null;} register spec representing 139 * the variable 140 */ Entry(int address, Disposition disposition, RegisterSpec spec)141 public Entry(int address, Disposition disposition, RegisterSpec spec) { 142 if (address < 0) { 143 throw new IllegalArgumentException("address < 0"); 144 } 145 146 if (disposition == null) { 147 throw new NullPointerException("disposition == null"); 148 } 149 150 try { 151 if (spec.getLocalItem() == null) { 152 throw new NullPointerException( 153 "spec.getLocalItem() == null"); 154 } 155 } catch (NullPointerException ex) { 156 // Elucidate the exception. 157 throw new NullPointerException("spec == null"); 158 } 159 160 this.address = address; 161 this.disposition = disposition; 162 this.spec = spec; 163 this.type = CstType.intern(spec.getType()); 164 } 165 166 /** {@inheritDoc} */ toString()167 public String toString() { 168 return Integer.toHexString(address) + " " + disposition + " " + 169 spec; 170 } 171 172 /** {@inheritDoc} */ equals(Object other)173 public boolean equals(Object other) { 174 if (!(other instanceof Entry)) { 175 return false; 176 } 177 178 return (compareTo((Entry) other) == 0); 179 } 180 181 /** 182 * Compares by (in priority order) address, end then start 183 * disposition (variants of end are all consistered 184 * equivalent), and spec. 185 * 186 * @param other {@code non-null;} entry to compare to 187 * @return {@code -1..1;} standard result of comparison 188 */ compareTo(Entry other)189 public int compareTo(Entry other) { 190 if (address < other.address) { 191 return -1; 192 } else if (address > other.address) { 193 return 1; 194 } 195 196 boolean thisIsStart = isStart(); 197 boolean otherIsStart = other.isStart(); 198 199 if (thisIsStart != otherIsStart) { 200 return thisIsStart ? 1 : -1; 201 } 202 203 return spec.compareTo(other.spec); 204 } 205 206 /** 207 * Gets the address. 208 * 209 * @return {@code >= 0;} the address 210 */ getAddress()211 public int getAddress() { 212 return address; 213 } 214 215 /** 216 * Gets the disposition. 217 * 218 * @return {@code non-null;} the disposition 219 */ getDisposition()220 public Disposition getDisposition() { 221 return disposition; 222 } 223 224 /** 225 * Gets whether this is a local start. This is just shorthand for 226 * {@code getDisposition() == Disposition.START}. 227 * 228 * @return {@code true} iff this is a start 229 */ isStart()230 public boolean isStart() { 231 return disposition == Disposition.START; 232 } 233 234 /** 235 * Gets the variable name. 236 * 237 * @return {@code null-ok;} the variable name 238 */ getName()239 public CstUtf8 getName() { 240 return spec.getLocalItem().getName(); 241 } 242 243 /** 244 * Gets the variable signature. 245 * 246 * @return {@code null-ok;} the variable signature 247 */ getSignature()248 public CstUtf8 getSignature() { 249 return spec.getLocalItem().getSignature(); 250 } 251 252 /** 253 * Gets the variable's type. 254 * 255 * @return {@code non-null;} the type 256 */ getType()257 public CstType getType() { 258 return type; 259 } 260 261 /** 262 * Gets the number of the register holding the variable. 263 * 264 * @return {@code >= 0;} the number of the register holding 265 * the variable 266 */ getRegister()267 public int getRegister() { 268 return spec.getReg(); 269 } 270 271 /** 272 * Gets the RegisterSpec of the register holding the variable. 273 * 274 * @return {@code non-null;} RegisterSpec of the holding register. 275 */ getRegisterSpec()276 public RegisterSpec getRegisterSpec() { 277 return spec; 278 } 279 280 /** 281 * Returns whether or not this instance matches the given spec. 282 * 283 * @param otherSpec {@code non-null;} the spec in question 284 * @return {@code true} iff this instance matches 285 * {@code spec} 286 */ matches(RegisterSpec otherSpec)287 public boolean matches(RegisterSpec otherSpec) { 288 return spec.equalsUsingSimpleType(otherSpec); 289 } 290 291 /** 292 * Returns whether or not this instance matches the spec in 293 * the given instance. 294 * 295 * @param other {@code non-null;} another entry 296 * @return {@code true} iff this instance's spec matches 297 * {@code other} 298 */ matches(Entry other)299 public boolean matches(Entry other) { 300 return matches(other.spec); 301 } 302 303 /** 304 * Returns an instance just like this one but with the disposition 305 * set as given. 306 * 307 * @param disposition {@code non-null;} the new disposition 308 * @return {@code non-null;} an appropriately-constructed instance 309 */ withDisposition(Disposition disposition)310 public Entry withDisposition(Disposition disposition) { 311 if (disposition == this.disposition) { 312 return this; 313 } 314 315 return new Entry(address, disposition, spec); 316 } 317 } 318 319 /** 320 * Constructs an instance for the given method, based on the given 321 * block order and intermediate local information. 322 * 323 * @param insns {@code non-null;} instructions to convert 324 * @return {@code non-null;} the constructed list 325 */ make(DalvInsnList insns)326 public static LocalList make(DalvInsnList insns) { 327 int sz = insns.size(); 328 329 /* 330 * Go through the insn list, looking for all the local 331 * variable pseudoinstructions, splitting out LocalSnapshots 332 * into separate per-variable starts, adding explicit ends 333 * wherever a variable is replaced or moved, and collecting 334 * these and all the other local variable "activity" 335 * together into an output list (without the other insns). 336 * 337 * Note: As of this writing, this method won't be handed any 338 * insn lists that contain local ends, but I (danfuzz) expect 339 * that to change at some point, when we start feeding that 340 * info explicitly into the rop layer rather than only trying 341 * to infer it. So, given that expectation, this code is 342 * written to deal with them. 343 */ 344 345 MakeState state = new MakeState(sz); 346 347 for (int i = 0; i < sz; i++) { 348 DalvInsn insn = insns.get(i); 349 350 if (insn instanceof LocalSnapshot) { 351 RegisterSpecSet snapshot = 352 ((LocalSnapshot) insn).getLocals(); 353 state.snapshot(insn.getAddress(), snapshot); 354 } else if (insn instanceof LocalStart) { 355 RegisterSpec local = ((LocalStart) insn).getLocal(); 356 state.startLocal(insn.getAddress(), local); 357 } else if (insn instanceof LocalEnd) { 358 RegisterSpec local = ((LocalEnd) insn).getLocal(); 359 state.endLocal(insn.getAddress(), local); 360 } 361 } 362 363 LocalList result = state.finish(); 364 365 if (DEBUG) { 366 debugVerify(result); 367 } 368 369 return result; 370 } 371 372 /** 373 * Debugging helper that verifies the constraint that a list doesn't 374 * contain any redundant local starts and that local ends that are 375 * due to replacements are properly annotated. 376 */ debugVerify(LocalList locals)377 private static void debugVerify(LocalList locals) { 378 try { 379 debugVerify0(locals); 380 } catch (RuntimeException ex) { 381 int sz = locals.size(); 382 for (int i = 0; i < sz; i++) { 383 System.err.println(locals.get(i)); 384 } 385 throw ex; 386 } 387 388 } 389 390 /** 391 * Helper for {@link #debugVerify} which does most of the work. 392 */ debugVerify0(LocalList locals)393 private static void debugVerify0(LocalList locals) { 394 int sz = locals.size(); 395 Entry[] active = new Entry[65536]; 396 397 for (int i = 0; i < sz; i++) { 398 Entry e = locals.get(i); 399 int reg = e.getRegister(); 400 401 if (e.isStart()) { 402 Entry already = active[reg]; 403 404 if ((already != null) && e.matches(already)) { 405 throw new RuntimeException("redundant start at " + 406 Integer.toHexString(e.getAddress()) + ": got " + 407 e + "; had " + already); 408 } 409 410 active[reg] = e; 411 } else { 412 if (active[reg] == null) { 413 throw new RuntimeException("redundant end at " + 414 Integer.toHexString(e.getAddress())); 415 } 416 417 int addr = e.getAddress(); 418 boolean foundStart = false; 419 420 for (int j = i + 1; j < sz; j++) { 421 Entry test = locals.get(j); 422 if (test.getAddress() != addr) { 423 break; 424 } 425 if (test.getRegisterSpec().getReg() == reg) { 426 if (test.isStart()) { 427 if (e.getDisposition() 428 != Disposition.END_REPLACED) { 429 throw new RuntimeException( 430 "improperly marked end at " + 431 Integer.toHexString(addr)); 432 } 433 foundStart = true; 434 } else { 435 throw new RuntimeException( 436 "redundant end at " + 437 Integer.toHexString(addr)); 438 } 439 } 440 } 441 442 if (!foundStart && 443 (e.getDisposition() == Disposition.END_REPLACED)) { 444 throw new RuntimeException( 445 "improper end replacement claim at " + 446 Integer.toHexString(addr)); 447 } 448 449 active[reg] = null; 450 } 451 } 452 } 453 454 /** 455 * Intermediate state when constructing a local list. 456 */ 457 public static class MakeState { 458 /** {@code non-null;} result being collected */ 459 private final ArrayList<Entry> result; 460 461 /** 462 * {@code >= 0;} running count of nulled result entries, to help with 463 * sizing the final list 464 */ 465 private int nullResultCount; 466 467 /** {@code null-ok;} current register mappings */ 468 private RegisterSpecSet regs; 469 470 /** {@code null-ok;} result indices where local ends are stored */ 471 private int[] endIndices; 472 473 /** {@code >= 0;} last address seen */ 474 private int lastAddress; 475 476 /** 477 * Constructs an instance. 478 */ MakeState(int initialSize)479 public MakeState(int initialSize) { 480 result = new ArrayList<Entry>(initialSize); 481 nullResultCount = 0; 482 regs = null; 483 endIndices = null; 484 lastAddress = 0; 485 } 486 487 /** 488 * Checks the address and other vitals as a prerequisite to 489 * further processing. 490 * 491 * @param address {@code >= 0;} address about to be processed 492 * @param reg {@code >= 0;} register number about to be processed 493 */ aboutToProcess(int address, int reg)494 private void aboutToProcess(int address, int reg) { 495 boolean first = (endIndices == null); 496 497 if ((address == lastAddress) && !first) { 498 return; 499 } 500 501 if (address < lastAddress) { 502 throw new RuntimeException("shouldn't happen"); 503 } 504 505 if (first || (reg >= endIndices.length)) { 506 /* 507 * This is the first allocation of the state set and 508 * index array, or we need to grow. (The latter doesn't 509 * happen much; in fact, we have only ever observed 510 * it happening in test cases, never in "real" code.) 511 */ 512 int newSz = reg + 1; 513 RegisterSpecSet newRegs = new RegisterSpecSet(newSz); 514 int[] newEnds = new int[newSz]; 515 Arrays.fill(newEnds, -1); 516 517 if (!first) { 518 newRegs.putAll(regs); 519 System.arraycopy(endIndices, 0, newEnds, 0, 520 endIndices.length); 521 } 522 523 regs = newRegs; 524 endIndices = newEnds; 525 } 526 } 527 528 /** 529 * Sets the local state at the given address to the given snapshot. 530 * The first call on this instance must be to this method, so that 531 * the register state can be properly sized. 532 * 533 * @param address {@code >= 0;} the address 534 * @param specs {@code non-null;} spec set representing the locals 535 */ snapshot(int address, RegisterSpecSet specs)536 public void snapshot(int address, RegisterSpecSet specs) { 537 if (DEBUG) { 538 System.err.printf("%04x snapshot %s\n", address, specs); 539 } 540 541 int sz = specs.getMaxSize(); 542 aboutToProcess(address, sz - 1); 543 544 for (int i = 0; i < sz; i++) { 545 RegisterSpec oldSpec = regs.get(i); 546 RegisterSpec newSpec = filterSpec(specs.get(i)); 547 548 if (oldSpec == null) { 549 if (newSpec != null) { 550 startLocal(address, newSpec); 551 } 552 } else if (newSpec == null) { 553 endLocal(address, oldSpec); 554 } else if (! newSpec.equalsUsingSimpleType(oldSpec)) { 555 endLocal(address, oldSpec); 556 startLocal(address, newSpec); 557 } 558 } 559 560 if (DEBUG) { 561 System.err.printf("%04x snapshot done\n", address); 562 } 563 } 564 565 /** 566 * Starts a local at the given address. 567 * 568 * @param address {@code >= 0;} the address 569 * @param startedLocal {@code non-null;} spec representing the 570 * started local 571 */ startLocal(int address, RegisterSpec startedLocal)572 public void startLocal(int address, RegisterSpec startedLocal) { 573 if (DEBUG) { 574 System.err.printf("%04x start %s\n", address, startedLocal); 575 } 576 577 int regNum = startedLocal.getReg(); 578 579 startedLocal = filterSpec(startedLocal); 580 aboutToProcess(address, regNum); 581 582 RegisterSpec existingLocal = regs.get(regNum); 583 584 if (startedLocal.equalsUsingSimpleType(existingLocal)) { 585 // Silently ignore a redundant start. 586 return; 587 } 588 589 RegisterSpec movedLocal = regs.findMatchingLocal(startedLocal); 590 if (movedLocal != null) { 591 /* 592 * The same variable was moved from one register to another. 593 * So add an end for its old location. 594 */ 595 addOrUpdateEnd(address, Disposition.END_MOVED, movedLocal); 596 } 597 598 int endAt = endIndices[regNum]; 599 600 if (existingLocal != null) { 601 /* 602 * There is an existing (but non-matching) local. 603 * Add an explicit end for it. 604 */ 605 add(address, Disposition.END_REPLACED, existingLocal); 606 } else if (endAt >= 0) { 607 /* 608 * Look for an end local for the same register at the 609 * same address. If found, then update it or delete 610 * it, depending on whether or not it represents the 611 * same variable as the one being started. 612 */ 613 Entry endEntry = result.get(endAt); 614 if (endEntry.getAddress() == address) { 615 if (endEntry.matches(startedLocal)) { 616 /* 617 * There was already an end local for the same 618 * variable at the same address. This turns 619 * out to be superfluous, as we are starting 620 * up the exact same local. This situation can 621 * happen when a single local variable got 622 * somehow "split up" during intermediate 623 * processing. In any case, rather than represent 624 * the end-then-start, just remove the old end. 625 */ 626 result.set(endAt, null); 627 nullResultCount++; 628 regs.put(startedLocal); 629 endIndices[regNum] = -1; 630 return; 631 } else { 632 /* 633 * There was a different variable ended at the 634 * same address. Update it to indicate that 635 * it was ended due to a replacement (rather than 636 * ending for no particular reason). 637 */ 638 endEntry = endEntry.withDisposition( 639 Disposition.END_REPLACED); 640 result.set(endAt, endEntry); 641 } 642 } 643 } 644 645 /* 646 * The code above didn't find and remove an unnecessary 647 * local end, so we now have to add one or more entries to 648 * the output to capture the transition. 649 */ 650 651 /* 652 * If the local just below (in the register set at reg-1) 653 * is of category-2, then it is ended by this new start. 654 */ 655 if (regNum > 0) { 656 RegisterSpec justBelow = regs.get(regNum - 1); 657 if ((justBelow != null) && justBelow.isCategory2()) { 658 addOrUpdateEnd(address, 659 Disposition.END_CLOBBERED_BY_NEXT, 660 justBelow); 661 } 662 } 663 664 /* 665 * Similarly, if this local is category-2, then the local 666 * just above (if any) is ended by the start now being 667 * emitted. 668 */ 669 if (startedLocal.isCategory2()) { 670 RegisterSpec justAbove = regs.get(regNum + 1); 671 if (justAbove != null) { 672 addOrUpdateEnd(address, 673 Disposition.END_CLOBBERED_BY_PREV, 674 justAbove); 675 } 676 } 677 678 /* 679 * TODO: Add an end for the same local in a different reg, 680 * if any (that is, if the local migrates from vX to vY, 681 * we should note that as a local end in vX). 682 */ 683 684 add(address, Disposition.START, startedLocal); 685 } 686 687 /** 688 * Ends a local at the given address, using the disposition 689 * {@code END_SIMPLY}. 690 * 691 * @param address {@code >= 0;} the address 692 * @param endedLocal {@code non-null;} spec representing the 693 * local being ended 694 */ endLocal(int address, RegisterSpec endedLocal)695 public void endLocal(int address, RegisterSpec endedLocal) { 696 endLocal(address, endedLocal, Disposition.END_SIMPLY); 697 } 698 699 /** 700 * Ends a local at the given address. 701 * 702 * @param address {@code >= 0;} the address 703 * @param endedLocal {@code non-null;} spec representing the 704 * local being ended 705 * @param disposition reason for the end 706 */ endLocal(int address, RegisterSpec endedLocal, Disposition disposition)707 public void endLocal(int address, RegisterSpec endedLocal, 708 Disposition disposition) { 709 if (DEBUG) { 710 System.err.printf("%04x end %s\n", address, endedLocal); 711 } 712 713 int regNum = endedLocal.getReg(); 714 715 endedLocal = filterSpec(endedLocal); 716 aboutToProcess(address, regNum); 717 718 int endAt = endIndices[regNum]; 719 720 if (endAt >= 0) { 721 /* 722 * The local in the given register is already ended. 723 * Silently return without adding anything to the result. 724 */ 725 return; 726 } 727 728 // Check for start and end at the same address. 729 if (checkForEmptyRange(address, endedLocal)) { 730 return; 731 } 732 733 add(address, disposition, endedLocal); 734 } 735 736 /** 737 * Helper for {@link #endLocal}, which handles the cases where 738 * and end local is issued at the same address as a start local 739 * for the same register. If this case is found, then this 740 * method will remove the start (as the local was never actually 741 * active), update the {@link #endIndices} to be accurate, and 742 * if needed update the newly-active end to reflect an altered 743 * disposition. 744 * 745 * @param address {@code >= 0;} the address 746 * @param endedLocal {@code non-null;} spec representing the 747 * local being ended 748 * @return {@code true} iff this method found the case in question 749 * and adjusted things accordingly 750 */ checkForEmptyRange(int address, RegisterSpec endedLocal)751 private boolean checkForEmptyRange(int address, 752 RegisterSpec endedLocal) { 753 int at = result.size() - 1; 754 Entry entry; 755 756 // Look for a previous entry at the same address. 757 for (/*at*/; at >= 0; at--) { 758 entry = result.get(at); 759 760 if (entry == null) { 761 continue; 762 } 763 764 if (entry.getAddress() != address) { 765 // We didn't find any match at the same address. 766 return false; 767 } 768 769 if (entry.matches(endedLocal)) { 770 break; 771 } 772 } 773 774 /* 775 * In fact, we found that the endedLocal had started at the 776 * same address, so do all the requisite cleanup. 777 */ 778 779 regs.remove(endedLocal); 780 result.set(at, null); 781 nullResultCount++; 782 783 int regNum = endedLocal.getReg(); 784 boolean found = false; 785 entry = null; 786 787 // Now look back further to update where the register ended. 788 for (at--; at >= 0; at--) { 789 entry = result.get(at); 790 791 if (entry == null) { 792 continue; 793 } 794 795 if (entry.getRegisterSpec().getReg() == regNum) { 796 found = true; 797 break; 798 } 799 } 800 801 if (found) { 802 // We found an end for the same register. 803 endIndices[regNum] = at; 804 805 if (entry.getAddress() == address) { 806 /* 807 * It's still the same address, so update the 808 * disposition. 809 */ 810 result.set(at, 811 entry.withDisposition(Disposition.END_SIMPLY)); 812 } 813 } 814 815 return true; 816 } 817 818 /** 819 * Converts a given spec into the form acceptable for use in a 820 * local list. This, in particular, transforms the "known 821 * null" type into simply {@code Object}. This method needs to 822 * be called for any spec that is on its way into a locals 823 * list. 824 * 825 * <p>This isn't necessarily the cleanest way to achieve the 826 * goal of not representing known nulls in a locals list, but 827 * it gets the job done.</p> 828 * 829 * @param orig {@code null-ok;} the original spec 830 * @return {@code null-ok;} an appropriately modified spec, or the 831 * original if nothing needs to be done 832 */ filterSpec(RegisterSpec orig)833 private static RegisterSpec filterSpec(RegisterSpec orig) { 834 if ((orig != null) && (orig.getType() == Type.KNOWN_NULL)) { 835 return orig.withType(Type.OBJECT); 836 } 837 838 return orig; 839 } 840 841 /** 842 * Adds an entry to the result, updating the adjunct tables 843 * accordingly. 844 * 845 * @param address {@code >= 0;} the address 846 * @param disposition {@code non-null;} the disposition 847 * @param spec {@code non-null;} spec representing the local 848 */ add(int address, Disposition disposition, RegisterSpec spec)849 private void add(int address, Disposition disposition, 850 RegisterSpec spec) { 851 int regNum = spec.getReg(); 852 853 result.add(new Entry(address, disposition, spec)); 854 855 if (disposition == Disposition.START) { 856 regs.put(spec); 857 endIndices[regNum] = -1; 858 } else { 859 regs.remove(spec); 860 endIndices[regNum] = result.size() - 1; 861 } 862 } 863 864 /** 865 * Adds or updates an end local (changing its disposition). If 866 * this would cause an empty range for a local, this instead 867 * removes the local entirely. 868 * 869 * @param address {@code >= 0;} the address 870 * @param disposition {@code non-null;} the disposition 871 * @param spec {@code non-null;} spec representing the local 872 */ addOrUpdateEnd(int address, Disposition disposition, RegisterSpec spec)873 private void addOrUpdateEnd(int address, Disposition disposition, 874 RegisterSpec spec) { 875 if (disposition == Disposition.START) { 876 throw new RuntimeException("shouldn't happen"); 877 } 878 879 int regNum = spec.getReg(); 880 int endAt = endIndices[regNum]; 881 882 if (endAt >= 0) { 883 // There is a previous end. 884 Entry endEntry = result.get(endAt); 885 if ((endEntry.getAddress() == address) && 886 endEntry.getRegisterSpec().equals(spec)) { 887 /* 888 * The end is for the right address and variable, so 889 * update it. 890 */ 891 result.set(endAt, endEntry.withDisposition(disposition)); 892 regs.remove(spec); // TODO: Is this line superfluous? 893 return; 894 } 895 } 896 897 endLocal(address, spec, disposition); 898 } 899 900 /** 901 * Finishes processing altogether and gets the result. 902 * 903 * @return {@code non-null;} the result list 904 */ finish()905 public LocalList finish() { 906 aboutToProcess(Integer.MAX_VALUE, 0); 907 908 int resultSz = result.size(); 909 int finalSz = resultSz - nullResultCount; 910 911 if (finalSz == 0) { 912 return EMPTY; 913 } 914 915 /* 916 * Collect an array of only the non-null entries, and then 917 * sort it to get a consistent order for everything: Local 918 * ends and starts for a given address could come in any 919 * order, but we want ends before starts as well as 920 * registers in order (within ends or starts). 921 */ 922 923 Entry[] resultArr = new Entry[finalSz]; 924 925 if (resultSz == finalSz) { 926 result.toArray(resultArr); 927 } else { 928 int at = 0; 929 for (Entry e : result) { 930 if (e != null) { 931 resultArr[at++] = e; 932 } 933 } 934 } 935 936 Arrays.sort(resultArr); 937 938 LocalList resultList = new LocalList(finalSz); 939 940 for (int i = 0; i < finalSz; i++) { 941 resultList.set(i, resultArr[i]); 942 } 943 944 resultList.setImmutable(); 945 return resultList; 946 } 947 } 948 } 949