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