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