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
17package com.android.dx.dex.code;
18
19import com.android.dx.rop.code.RegisterSpec;
20import com.android.dx.rop.code.RegisterSpecSet;
21import com.android.dx.rop.cst.CstString;
22import com.android.dx.rop.cst.CstType;
23import com.android.dx.rop.type.Type;
24import com.android.dx.util.FixedSizeList;
25import java.io.PrintStream;
26import java.util.ArrayList;
27import 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 */
34public 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     */
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     */
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     */
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     */
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         */
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
167        public String toString() {
168            return Integer.toHexString(address) + " " + disposition + " " +
169                spec;
170        }
171
172        /** {@inheritDoc} */
173        @Override
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
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         */
213        public int getAddress() {
214            return address;
215        }
216
217        /**
218         * Gets the disposition.
219         *
220         * @return {@code non-null;} the disposition
221         */
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         */
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         */
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         */
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         */
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         */
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         */
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         */
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         */
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         */
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     */
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     */
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     */
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         */
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         */
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         */
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         */
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         */
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         */
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         */
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         */
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         */
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         */
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         */
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