MutableMethodImplementation.java revision 135631b11bfa2e52d78ba0d5a1581cf02ab4f7fa
1/*
2 * Copyright 2013, Google Inc.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met:
8 *
9 *     * Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 *     * Redistributions in binary form must reproduce the above
12 * copyright notice, this list of conditions and the following disclaimer
13 * in the documentation and/or other materials provided with the
14 * distribution.
15 *     * Neither the name of Google Inc. nor the names of its
16 * contributors may be used to endorse or promote products derived from
17 * this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 */
31
32package org.jf.dexlib2.builder;
33
34import com.google.common.base.Function;
35import com.google.common.collect.Iterables;
36import com.google.common.collect.Lists;
37import com.google.common.collect.Sets;
38import org.jf.dexlib2.DebugItemType;
39import org.jf.dexlib2.Opcode;
40import org.jf.dexlib2.builder.debug.*;
41import org.jf.dexlib2.builder.instruction.*;
42import org.jf.dexlib2.iface.ExceptionHandler;
43import org.jf.dexlib2.iface.MethodImplementation;
44import org.jf.dexlib2.iface.TryBlock;
45import org.jf.dexlib2.iface.debug.*;
46import org.jf.dexlib2.iface.instruction.Instruction;
47import org.jf.dexlib2.iface.instruction.SwitchElement;
48import org.jf.dexlib2.iface.instruction.formats.*;
49import org.jf.dexlib2.iface.reference.TypeReference;
50import org.jf.util.ExceptionWithContext;
51
52import javax.annotation.Nonnull;
53import javax.annotation.Nullable;
54import java.util.*;
55
56public class MutableMethodImplementation implements MethodImplementation {
57    private final int registerCount;
58    final ArrayList<MethodLocation> instructionList = Lists.newArrayList(new MethodLocation(null, 0, 0));
59    private final ArrayList<BuilderTryBlock> tryBlocks = Lists.newArrayList();
60    private boolean fixInstructions = true;
61
62    public MutableMethodImplementation(@Nonnull MethodImplementation methodImplementation) {
63        this.registerCount = methodImplementation.getRegisterCount();
64
65        int codeAddress = 0;
66        int index = 0;
67
68        for (Instruction instruction: methodImplementation.getInstructions()) {
69            codeAddress += instruction.getCodeUnits();
70            index++;
71
72            instructionList.add(new MethodLocation(null, codeAddress, index));
73        }
74
75        final int[] codeAddressToIndex = new int[codeAddress+1];
76        Arrays.fill(codeAddressToIndex, -1);
77
78        for (int i=0; i<instructionList.size(); i++) {
79            codeAddressToIndex[instructionList.get(i).codeAddress] = i;
80        }
81
82        List<Task> switchPayloadTasks = Lists.newArrayList();
83        index = 0;
84        for (final Instruction instruction: methodImplementation.getInstructions()) {
85            final MethodLocation location = instructionList.get(index);
86            final Opcode opcode = instruction.getOpcode();
87            if (opcode == Opcode.PACKED_SWITCH_PAYLOAD || opcode == Opcode.SPARSE_SWITCH_PAYLOAD) {
88                switchPayloadTasks.add(new Task() {
89                    @Override public void perform() {
90                        convertAndSetInstruction(location, codeAddressToIndex, instruction);
91                    }
92                });
93            } else {
94                convertAndSetInstruction(location, codeAddressToIndex, instruction);
95            }
96            index++;
97        }
98
99        // the switch instructions must be converted last, so that any switch statements that refer to them have
100        // created the referring labels that we look for
101        for (Task switchPayloadTask: switchPayloadTasks) {
102            switchPayloadTask.perform();
103        }
104
105        for (DebugItem debugItem: methodImplementation.getDebugItems()) {
106            int debugCodeAddress = debugItem.getCodeAddress();
107            int locationIndex = mapCodeAddressToIndex(codeAddressToIndex, debugCodeAddress);
108            MethodLocation debugLocation = instructionList.get(locationIndex);
109            BuilderDebugItem builderDebugItem = convertDebugItem(debugItem);
110            debugLocation.getDebugItems().add(builderDebugItem);
111            builderDebugItem.location = debugLocation;
112        }
113
114        for (TryBlock<? extends ExceptionHandler> tryBlock: methodImplementation.getTryBlocks()) {
115            Label startLabel = newLabel(codeAddressToIndex, tryBlock.getStartCodeAddress());
116            Label endLabel = newLabel(codeAddressToIndex, tryBlock.getStartCodeAddress() + tryBlock.getCodeUnitCount());
117
118            for (ExceptionHandler exceptionHandler: tryBlock.getExceptionHandlers()) {
119                tryBlocks.add(new BuilderTryBlock(startLabel, endLabel,
120                        exceptionHandler.getExceptionTypeReference(),
121                        newLabel(codeAddressToIndex, exceptionHandler.getHandlerCodeAddress())));
122            }
123        }
124    }
125
126    private interface Task {
127        void perform();
128    }
129
130    public MutableMethodImplementation(int registerCount) {
131        this.registerCount = registerCount;
132    }
133
134    @Override public int getRegisterCount() {
135        return registerCount;
136    }
137
138    @Nonnull
139    public List<Instruction> getInstructions() {
140        if (fixInstructions) {
141            fixInstructions();
142        }
143
144        return new AbstractList<Instruction>() {
145            @Override public Instruction get(int i) {
146                if (i >= size()) {
147                    throw new IndexOutOfBoundsException();
148                }
149                if (fixInstructions) {
150                    fixInstructions();
151                }
152                return instructionList.get(i).instruction;
153            }
154
155            @Override public int size() {
156                if (fixInstructions) {
157                    fixInstructions();
158                }
159                // don't include the last MethodLocation, which always has a null instruction
160                return instructionList.size() - 1;
161            }
162        };
163    }
164
165    @Nonnull @Override public List<? extends TryBlock<? extends ExceptionHandler>> getTryBlocks() {
166        if (fixInstructions) {
167            fixInstructions();
168        }
169        return Collections.unmodifiableList(tryBlocks);
170    }
171
172    @Nonnull @Override public Iterable<? extends DebugItem> getDebugItems() {
173        if (fixInstructions) {
174            fixInstructions();
175        }
176        return Iterables.concat(
177                Iterables.transform(instructionList, new Function<MethodLocation, Iterable<? extends DebugItem>>() {
178                    @Nullable @Override public Iterable<? extends DebugItem> apply(@Nullable MethodLocation input) {
179                        assert input != null;
180                        if (fixInstructions) {
181                            throw new IllegalStateException("This iterator was invalidated by a change to" +
182                                    " this MutableMethodImplementation.");
183                        }
184                        return input.getDebugItems();
185                    }
186                }));
187    }
188
189    public void addCatch(@Nullable TypeReference type, @Nonnull Label from,
190                         @Nonnull Label to, @Nonnull Label handler) {
191        tryBlocks.add(new BuilderTryBlock(from, to, type, handler));
192    }
193
194    public void addCatch(@Nullable String type, @Nonnull Label from, @Nonnull Label to,
195                         @Nonnull Label handler) {
196        tryBlocks.add(new BuilderTryBlock(from, to, type, handler));
197    }
198
199    public void addCatch(@Nonnull Label from, @Nonnull Label to, @Nonnull Label handler) {
200        tryBlocks.add(new BuilderTryBlock(from, to, handler));
201    }
202
203    public void addInstruction(int index, BuilderInstruction instruction) {
204        // the end check here is intentially >= rather than >, because the list always includes an "empty"
205        // (null instruction) MethodLocation at the end. To add an instruction to the end of the list, the user would
206        // provide the index of this empty item, which would be size() - 1.
207        if (index >= instructionList.size()) {
208            throw new IndexOutOfBoundsException();
209        }
210
211        if (index == instructionList.size() - 1) {
212            addInstruction(instruction);
213            return;
214        }
215        int codeAddress = instructionList.get(index).getCodeAddress();
216
217        instructionList.add(index, new MethodLocation(instruction, codeAddress, index));
218        codeAddress += instruction.getCodeUnits();
219
220        for (int i=index+1; i<instructionList.size(); i++) {
221            MethodLocation location = instructionList.get(i);
222            location.index++;
223            location.codeAddress = codeAddress;
224            if (location.instruction != null) {
225                codeAddress += location.instruction.getCodeUnits();
226            } else {
227                // only the last MethodLocation should have a null instruction
228                assert i == instructionList.size()-1;
229            }
230        }
231
232        this.fixInstructions = true;
233    }
234
235    public void addInstruction(@Nonnull BuilderInstruction instruction) {
236        MethodLocation last = instructionList.get(instructionList.size()-1);
237        last.instruction = instruction;
238        instruction.location = last;
239
240        int nextCodeAddress = last.codeAddress + instruction.getCodeUnits();
241        instructionList.add(new MethodLocation(null, nextCodeAddress, instructionList.size()));
242
243        this.fixInstructions = true;
244    }
245
246    public void replaceInstruction(int index, @Nonnull BuilderInstruction replacementInstruction) {
247        if (index >= instructionList.size() - 1) {
248            throw new IndexOutOfBoundsException();
249        }
250
251        MethodLocation replaceLocation = instructionList.get(index);
252        replacementInstruction.location = replaceLocation;
253        BuilderInstruction old = replaceLocation.instruction;
254        assert old != null;
255        old.location = null;
256        replaceLocation.instruction = replacementInstruction;
257
258        // TODO: factor out index/address fix up loop
259        int codeAddress = replaceLocation.codeAddress + replaceLocation.instruction.getCodeUnits();
260        for (int i=index+1; i<instructionList.size(); i++) {
261            MethodLocation location = instructionList.get(i);
262            location.codeAddress = codeAddress;
263
264            Instruction instruction = location.getInstruction();
265            if (instruction != null) {
266                codeAddress += instruction.getCodeUnits();
267            } else {
268                assert i == instructionList.size() - 1;
269            }
270        }
271
272        this.fixInstructions = true;
273    }
274
275    public void removeInstruction(int index) {
276        if (index >= instructionList.size() - 1) {
277            throw new IndexOutOfBoundsException();
278        }
279
280        MethodLocation toRemove = instructionList.get(index);
281        toRemove.instruction = null;
282        MethodLocation next = instructionList.get(index+1);
283        toRemove.mergeInto(next);
284
285        instructionList.remove(index);
286        int codeAddress = toRemove.codeAddress;
287        for (int i=index; i<instructionList.size(); i++) {
288            MethodLocation location = instructionList.get(i);
289            location.index = i;
290            location.codeAddress = codeAddress;
291
292            Instruction instruction = location.getInstruction();
293            if (instruction != null) {
294                codeAddress += instruction.getCodeUnits();
295            } else {
296                assert i == instructionList.size() - 1;
297            }
298        }
299
300        this.fixInstructions = true;
301    }
302
303    public void swapInstructions(int index1, int index2) {
304        if (index1 >= instructionList.size() - 1 || index2 >= instructionList.size() - 1) {
305            throw new IndexOutOfBoundsException();
306        }
307        MethodLocation first = instructionList.get(index1);
308        MethodLocation second = instructionList.get(index2);
309
310        // only the last MethodLocation may have a null instruction
311        assert first.instruction != null;
312        assert second.instruction != null;
313
314        first.instruction.location = second;
315        second.instruction.location = first;
316
317        {
318            BuilderInstruction tmp = second.instruction;
319            second.instruction = first.instruction;
320            first.instruction = tmp;
321        }
322
323        if (index2 < index1) {
324            int tmp = index2;
325            index2 = index1;
326            index1 = tmp;
327        }
328
329        int codeAddress = first.codeAddress + first.instruction.getCodeUnits();
330        for (int i=index1+1; i<=index2; i++) {
331            MethodLocation location = instructionList.get(i);
332            location.codeAddress = codeAddress;
333
334            Instruction instruction = location.instruction;
335            assert instruction != null;
336            codeAddress += location.instruction.getCodeUnits();
337        }
338
339        this.fixInstructions = true;
340    }
341
342    @Nullable
343    private BuilderInstruction getFirstNonNop(int startIndex) {
344
345        for (int i=startIndex; i<instructionList.size()-1; i++) {
346            BuilderInstruction instruction = instructionList.get(i).instruction;
347            assert instruction != null;
348            if (instruction.getOpcode() != Opcode.NOP) {
349                return instruction;
350            }
351        }
352        return null;
353    }
354
355    private void fixInstructions() {
356        HashSet<MethodLocation> payloadLocations = Sets.newHashSet();
357
358        for (MethodLocation location: instructionList) {
359            BuilderInstruction instruction = location.instruction;
360            if (instruction != null) {
361                switch (instruction.getOpcode()) {
362                    case SPARSE_SWITCH:
363                    case PACKED_SWITCH: {
364                        MethodLocation targetLocation =
365                                ((BuilderOffsetInstruction)instruction).getTarget().getLocation();
366                        BuilderInstruction targetInstruction = targetLocation.instruction;
367                        if (targetInstruction == null) {
368                            throw new IllegalStateException(String.format("Switch instruction at address/index " +
369                                    "0x%x/%d points to the end of the method.", location.codeAddress, location.index));
370                        }
371
372                        if (targetInstruction.getOpcode() == Opcode.NOP) {
373                            targetInstruction = getFirstNonNop(targetLocation.index+1);
374                        }
375                        if (targetInstruction == null || !(targetInstruction instanceof BuilderSwitchPayload)) {
376                            throw new IllegalStateException(String.format("Switch instruction at address/index " +
377                                    "0x%x/%d does not refer to a payload instruction.",
378                                    location.codeAddress, location.index));
379                        }
380                        if ((instruction.opcode == Opcode.PACKED_SWITCH &&
381                                targetInstruction.getOpcode() != Opcode.PACKED_SWITCH_PAYLOAD) ||
382                            (instruction.opcode == Opcode.SPARSE_SWITCH &&
383                                targetInstruction.getOpcode() != Opcode.SPARSE_SWITCH_PAYLOAD)) {
384                            throw new IllegalStateException(String.format("Switch instruction at address/index " +
385                                    "0x%x/%d refers to the wrong type of payload instruction.",
386                                    location.codeAddress, location.index));
387                        }
388
389                        if (!payloadLocations.add(targetLocation)) {
390                            throw new IllegalStateException("Multiple switch instructions refer to the same payload. " +
391                                    "This is not currently supported. Please file a bug :)");
392                        }
393
394                        ((BuilderSwitchPayload)targetInstruction).referrer = location;
395                        break;
396                    }
397                }
398            }
399        }
400
401        boolean madeChanges;
402        do {
403            madeChanges = false;
404
405            for (int index=0; index<instructionList.size(); index++) {
406                MethodLocation location = instructionList.get(index);
407                BuilderInstruction instruction = location.instruction;
408                if (instruction != null) {
409                    switch (instruction.getOpcode()) {
410                        case GOTO: {
411                            int offset = ((BuilderOffsetInstruction)instruction).internalGetCodeOffset();
412                            if (offset < Byte.MIN_VALUE || offset > Byte.MAX_VALUE) {
413                                BuilderOffsetInstruction replacement;
414                                if (offset < Short.MIN_VALUE || offset > Short.MAX_VALUE) {
415                                    replacement = new BuilderInstruction30t(Opcode.GOTO_32,
416                                            ((BuilderOffsetInstruction)instruction).getTarget());
417                                } else {
418                                    replacement = new BuilderInstruction20t(Opcode.GOTO_16,
419                                            ((BuilderOffsetInstruction)instruction).getTarget());
420                                }
421                                replaceInstruction(location.index, replacement);
422                                madeChanges = true;
423                            }
424                            break;
425                        }
426                        case GOTO_16: {
427                            int offset = ((BuilderOffsetInstruction)instruction).internalGetCodeOffset();
428                            if (offset < Short.MIN_VALUE || offset > Short.MAX_VALUE) {
429                                BuilderOffsetInstruction replacement =  new BuilderInstruction30t(Opcode.GOTO_32,
430                                            ((BuilderOffsetInstruction)instruction).getTarget());
431                                replaceInstruction(location.index, replacement);
432                                madeChanges = true;
433                            }
434                            break;
435                        }
436                        case SPARSE_SWITCH_PAYLOAD:
437                        case PACKED_SWITCH_PAYLOAD:
438                        case ARRAY_PAYLOAD: {
439                            if ((location.codeAddress & 0x01) != 0) {
440                                int previousIndex = location.index - 1;
441                                MethodLocation previousLocation = instructionList.get(previousIndex);
442                                Instruction previousInstruction = previousLocation.instruction;
443                                assert previousInstruction != null;
444                                if (previousInstruction.getOpcode() == Opcode.NOP) {
445                                    removeInstruction(previousIndex);
446                                    index--;
447                                } else {
448                                    addInstruction(location.index, new BuilderInstruction10x(Opcode.NOP));
449                                    index++;
450                                }
451                                madeChanges = true;
452                            }
453                            break;
454                        }
455                    }
456                }
457            }
458        } while (madeChanges);
459
460        fixInstructions = false;
461    }
462
463    private int mapCodeAddressToIndex(@Nonnull int[] codeAddressToIndex, int codeAddress) {
464        int index;
465        do {
466            index = codeAddressToIndex[codeAddress];
467            if (index < 0) {
468                codeAddress--;
469            } else {
470                return index;
471            }
472        } while (true);
473    }
474
475    @Nonnull
476    private Label newLabel(@Nonnull int[] codeAddressToIndex, int codeAddress) {
477        MethodLocation referent = instructionList.get(mapCodeAddressToIndex(codeAddressToIndex, codeAddress));
478        return referent.addNewLabel();
479    }
480
481    private static class SwitchPayloadReferenceLabel extends Label {
482        @Nonnull public MethodLocation switchLocation;
483    }
484
485    @Nonnull
486    public Label newSwitchPayloadReferenceLabel(@Nonnull int[] codeAddressToIndex, int codeAddress) {
487        MethodLocation referent = instructionList.get(mapCodeAddressToIndex(codeAddressToIndex, codeAddress));
488        Label label = new SwitchPayloadReferenceLabel();
489        referent.getLabels().add(label);
490        return label;
491    }
492
493    private void setInstruction(@Nonnull MethodLocation location, @Nonnull BuilderInstruction instruction) {
494        location.instruction = instruction;
495        instruction.location = location;
496    }
497
498    private void convertAndSetInstruction(@Nonnull MethodLocation location, int[] codeAddressToIndex,
499                                          @Nonnull Instruction instruction) {
500        switch (instruction.getOpcode().format) {
501            case Format10t:
502                setInstruction(location, newBuilderInstruction10t(location.codeAddress, codeAddressToIndex,
503                        (Instruction10t)instruction));
504                return;
505            case Format10x:
506                setInstruction(location, newBuilderInstruction10x((Instruction10x)instruction));
507                return;
508            case Format11n:
509                setInstruction(location, newBuilderInstruction11n((Instruction11n)instruction));
510                return;
511            case Format11x:
512                setInstruction(location, newBuilderInstruction11x((Instruction11x)instruction));
513                return;
514            case Format12x:
515                setInstruction(location, newBuilderInstruction12x((Instruction12x)instruction));
516                return;
517            case Format20bc:
518                setInstruction(location, newBuilderInstruction20bc((Instruction20bc)instruction));
519                return;
520            case Format20t:
521                setInstruction(location, newBuilderInstruction20t(location.codeAddress, codeAddressToIndex,
522                        (Instruction20t)instruction));
523                return;
524            case Format21c:
525                setInstruction(location, newBuilderInstruction21c((Instruction21c)instruction));
526                return;
527            case Format21ih:
528                setInstruction(location, newBuilderInstruction21ih((Instruction21ih)instruction));
529                return;
530            case Format21lh:
531                setInstruction(location, newBuilderInstruction21lh((Instruction21lh)instruction));
532                return;
533            case Format21s:
534                setInstruction(location, newBuilderInstruction21s((Instruction21s)instruction));
535                return;
536            case Format21t:
537                setInstruction(location, newBuilderInstruction21t(location.codeAddress, codeAddressToIndex,
538                        (Instruction21t)instruction));
539                return;
540            case Format22b:
541                setInstruction(location, newBuilderInstruction22b((Instruction22b)instruction));
542                return;
543            case Format22c:
544                setInstruction(location, newBuilderInstruction22c((Instruction22c)instruction));
545                return;
546            case Format22s:
547                setInstruction(location, newBuilderInstruction22s((Instruction22s)instruction));
548                return;
549            case Format22t:
550                setInstruction(location, newBuilderInstruction22t(location.codeAddress, codeAddressToIndex,
551                        (Instruction22t)instruction));
552                return;
553            case Format22x:
554                setInstruction(location, newBuilderInstruction22x((Instruction22x)instruction));
555                return;
556            case Format23x:
557                setInstruction(location, newBuilderInstruction23x((Instruction23x)instruction));
558                return;
559            case Format30t:
560                setInstruction(location, newBuilderInstruction30t(location.codeAddress, codeAddressToIndex,
561                        (Instruction30t)instruction));
562                return;
563            case Format31c:
564                setInstruction(location, newBuilderInstruction31c((Instruction31c)instruction));
565                return;
566            case Format31i:
567                setInstruction(location, newBuilderInstruction31i((Instruction31i)instruction));
568                return;
569            case Format31t:
570                setInstruction(location, newBuilderInstruction31t(location.codeAddress, codeAddressToIndex,
571                        (Instruction31t)instruction));
572                return;
573            case Format32x:
574                setInstruction(location, newBuilderInstruction32x((Instruction32x)instruction));
575                return;
576            case Format35c:
577                setInstruction(location, newBuilderInstruction35c((Instruction35c)instruction));
578                return;
579            case Format3rc:
580                setInstruction(location, newBuilderInstruction3rc((Instruction3rc)instruction));
581                return;
582            case Format51l:
583                setInstruction(location, newBuilderInstruction51l((Instruction51l)instruction));
584                return;
585            case PackedSwitchPayload:
586                setInstruction(location,
587                        newBuilderPackedSwitchPayload(location, codeAddressToIndex, (PackedSwitchPayload)instruction));
588                return;
589            case SparseSwitchPayload:
590                setInstruction(location,
591                        newBuilderSparseSwitchPayload(location, codeAddressToIndex, (SparseSwitchPayload)instruction));
592                return;
593            case ArrayPayload:
594                setInstruction(location, newBuilderArrayPayload((ArrayPayload)instruction));
595                return;
596            default:
597                throw new ExceptionWithContext("Instruction format %s not supported", instruction.getOpcode().format);
598        }
599    }
600
601    @Nonnull
602    private BuilderInstruction10t newBuilderInstruction10t(int codeAddress, int[] codeAddressToIndex,
603                                                           @Nonnull Instruction10t instruction) {
604        return new BuilderInstruction10t(
605                instruction.getOpcode(),
606                newLabel(codeAddressToIndex, codeAddress + instruction.getCodeOffset()));
607    }
608
609    @Nonnull
610    private BuilderInstruction10x newBuilderInstruction10x(@Nonnull Instruction10x instruction) {
611        return new BuilderInstruction10x(
612                instruction.getOpcode());
613    }
614
615    @Nonnull
616    private BuilderInstruction11n newBuilderInstruction11n(@Nonnull Instruction11n instruction) {
617        return new BuilderInstruction11n(
618                instruction.getOpcode(),
619                instruction.getRegisterA(),
620                instruction.getNarrowLiteral());
621    }
622
623    @Nonnull
624    private BuilderInstruction11x newBuilderInstruction11x(@Nonnull Instruction11x instruction) {
625        return new BuilderInstruction11x(
626                instruction.getOpcode(),
627                instruction.getRegisterA());
628    }
629
630    @Nonnull
631    private BuilderInstruction12x newBuilderInstruction12x(@Nonnull Instruction12x instruction) {
632        return new BuilderInstruction12x(
633                instruction.getOpcode(),
634                instruction.getRegisterA(),
635                instruction.getRegisterB());
636    }
637
638    @Nonnull
639    private BuilderInstruction20bc newBuilderInstruction20bc(@Nonnull Instruction20bc instruction) {
640        return new BuilderInstruction20bc(
641                instruction.getOpcode(),
642                instruction.getVerificationError(),
643                instruction.getReference());
644    }
645
646    @Nonnull
647    private BuilderInstruction20t newBuilderInstruction20t(int codeAddress, int[] codeAddressToIndex,
648                                                           @Nonnull Instruction20t instruction) {
649        return new BuilderInstruction20t(
650                instruction.getOpcode(),
651                newLabel(codeAddressToIndex, codeAddress + instruction.getCodeOffset()));
652    }
653
654    @Nonnull
655    private BuilderInstruction21c newBuilderInstruction21c(@Nonnull Instruction21c instruction) {
656        return new BuilderInstruction21c(
657                instruction.getOpcode(),
658                instruction.getRegisterA(),
659                instruction.getReference());
660    }
661
662    @Nonnull
663    private BuilderInstruction21ih newBuilderInstruction21ih(@Nonnull Instruction21ih instruction) {
664        return new BuilderInstruction21ih(
665                instruction.getOpcode(),
666                instruction.getRegisterA(),
667                instruction.getNarrowLiteral());
668    }
669
670    @Nonnull
671    private BuilderInstruction21lh newBuilderInstruction21lh(@Nonnull Instruction21lh instruction) {
672        return new BuilderInstruction21lh(
673                instruction.getOpcode(),
674                instruction.getRegisterA(),
675                instruction.getWideLiteral());
676    }
677
678    @Nonnull
679    private BuilderInstruction21s newBuilderInstruction21s(@Nonnull Instruction21s instruction) {
680        return new BuilderInstruction21s(
681                instruction.getOpcode(),
682                instruction.getRegisterA(),
683                instruction.getNarrowLiteral());
684    }
685
686    @Nonnull
687    private BuilderInstruction21t newBuilderInstruction21t(int codeAddress, int[] codeAddressToIndex,
688                                                           @Nonnull Instruction21t instruction) {
689        return new BuilderInstruction21t(
690                instruction.getOpcode(),
691                instruction.getRegisterA(),
692                newLabel(codeAddressToIndex, codeAddress + instruction.getCodeOffset()));
693    }
694
695    @Nonnull
696    private BuilderInstruction22b newBuilderInstruction22b(@Nonnull Instruction22b instruction) {
697        return new BuilderInstruction22b(
698                instruction.getOpcode(),
699                instruction.getRegisterA(),
700                instruction.getRegisterB(),
701                instruction.getNarrowLiteral());
702    }
703
704    @Nonnull
705    private BuilderInstruction22c newBuilderInstruction22c(@Nonnull Instruction22c instruction) {
706        return new BuilderInstruction22c(
707                instruction.getOpcode(),
708                instruction.getRegisterA(),
709                instruction.getRegisterB(),
710                instruction.getReference());
711    }
712
713    @Nonnull
714    private BuilderInstruction22s newBuilderInstruction22s(@Nonnull Instruction22s instruction) {
715        return new BuilderInstruction22s(
716                instruction.getOpcode(),
717                instruction.getRegisterA(),
718                instruction.getRegisterB(),
719                instruction.getNarrowLiteral());
720    }
721
722    @Nonnull
723    private BuilderInstruction22t newBuilderInstruction22t(int codeAddress, int[] codeAddressToIndex,
724                                                           @Nonnull Instruction22t instruction) {
725        return new BuilderInstruction22t(
726                instruction.getOpcode(),
727                instruction.getRegisterA(),
728                instruction.getRegisterB(),
729                newLabel(codeAddressToIndex, codeAddress + instruction.getCodeOffset()));
730    }
731
732    @Nonnull
733    private BuilderInstruction22x newBuilderInstruction22x(@Nonnull Instruction22x instruction) {
734        return new BuilderInstruction22x(
735                instruction.getOpcode(),
736                instruction.getRegisterA(),
737                instruction.getRegisterB());
738    }
739
740    @Nonnull
741    private BuilderInstruction23x newBuilderInstruction23x(@Nonnull Instruction23x instruction) {
742        return new BuilderInstruction23x(
743                instruction.getOpcode(),
744                instruction.getRegisterA(),
745                instruction.getRegisterB(),
746                instruction.getRegisterC());
747    }
748
749    @Nonnull
750    private BuilderInstruction30t newBuilderInstruction30t(int codeAddress, int[] codeAddressToIndex,
751                                                           @Nonnull Instruction30t instruction) {
752        return new BuilderInstruction30t(
753                instruction.getOpcode(),
754                newLabel(codeAddressToIndex, codeAddress + instruction.getCodeOffset()));
755    }
756
757    @Nonnull
758    private BuilderInstruction31c newBuilderInstruction31c(@Nonnull Instruction31c instruction) {
759        return new BuilderInstruction31c(
760                instruction.getOpcode(),
761                instruction.getRegisterA(),
762                instruction.getReference());
763    }
764
765    @Nonnull
766    private BuilderInstruction31i newBuilderInstruction31i(@Nonnull Instruction31i instruction) {
767        return new BuilderInstruction31i(
768                instruction.getOpcode(),
769                instruction.getRegisterA(),
770                instruction.getNarrowLiteral());
771    }
772
773    @Nonnull
774    private BuilderInstruction31t newBuilderInstruction31t(int codeAddress, int[] codeAddressToIndex,
775                                                           @Nonnull Instruction31t instruction) {
776        Label newLabel;
777        if (instruction.getOpcode() != Opcode.FILL_ARRAY_DATA) {
778            // if it's a sparse switch or packed switch
779            newLabel = newSwitchPayloadReferenceLabel(codeAddressToIndex, codeAddress + instruction.getCodeOffset());
780        } else {
781            newLabel = newLabel(codeAddressToIndex, codeAddress + instruction.getCodeOffset());
782        }
783        return new BuilderInstruction31t(
784                instruction.getOpcode(),
785                instruction.getRegisterA(),
786                newLabel);
787    }
788
789    @Nonnull
790    private BuilderInstruction32x newBuilderInstruction32x(@Nonnull Instruction32x instruction) {
791        return new BuilderInstruction32x(
792                instruction.getOpcode(),
793                instruction.getRegisterA(),
794                instruction.getRegisterB());
795    }
796
797    @Nonnull
798    private BuilderInstruction35c newBuilderInstruction35c(@Nonnull Instruction35c instruction) {
799        return new BuilderInstruction35c(
800                instruction.getOpcode(),
801                instruction.getRegisterCount(),
802                instruction.getRegisterC(),
803                instruction.getRegisterD(),
804                instruction.getRegisterE(),
805                instruction.getRegisterF(),
806                instruction.getRegisterG(),
807                instruction.getReference());
808    }
809
810    @Nonnull
811    private BuilderInstruction3rc newBuilderInstruction3rc(@Nonnull Instruction3rc instruction) {
812        return new BuilderInstruction3rc(
813                instruction.getOpcode(),
814                instruction.getStartRegister(),
815                instruction.getRegisterCount(),
816                instruction.getReference());
817    }
818
819    @Nonnull
820    private BuilderInstruction51l newBuilderInstruction51l(@Nonnull Instruction51l instruction) {
821        return new BuilderInstruction51l(
822                instruction.getOpcode(),
823                instruction.getRegisterA(),
824                instruction.getWideLiteral());
825    }
826
827    @Nullable
828    private MethodLocation findSwitchForPayload(@Nonnull MethodLocation payloadLocation) {
829        MethodLocation location = payloadLocation;
830        MethodLocation switchLocation = null;
831        do {
832            for (Label label: location.getLabels()) {
833                if (label instanceof SwitchPayloadReferenceLabel) {
834                    if (switchLocation != null) {
835                        throw new IllegalStateException("Multiple switch instructions refer to the same payload. " +
836                                "This is not currently supported. Please file a bug :)");
837                    }
838                    switchLocation = ((SwitchPayloadReferenceLabel)label).switchLocation;
839                }
840            }
841
842            // A switch instruction can refer to the payload instruction itself, or to a nop before the payload
843            // instruction.
844            // We need to search for all occurrences of a switch reference, so we can detect when multiple switch
845            // statements refer to the same payload
846            // TODO: confirm that it could refer to the first NOP in a series of NOPs preceding the payload
847            if (location.index == 0) {
848                return switchLocation;
849            }
850            location = instructionList.get(location.index - 1);
851            if (location.instruction == null || location.instruction.getOpcode() != Opcode.NOP) {
852                return switchLocation;
853            }
854        } while (true);
855    }
856
857    @Nonnull
858    private BuilderPackedSwitchPayload newBuilderPackedSwitchPayload(@Nonnull MethodLocation location,
859                                                                     @Nonnull int[] codeAddressToIndex,
860                                                                     @Nonnull PackedSwitchPayload instruction) {
861        List<? extends SwitchElement> switchElements = instruction.getSwitchElements();
862        if (switchElements.size() == 0) {
863            return new BuilderPackedSwitchPayload(0, null);
864        }
865
866        MethodLocation switchLocation = findSwitchForPayload(location);
867        int baseAddress;
868        if (switchLocation == null) {
869            baseAddress = 0;
870        } else {
871            baseAddress = switchLocation.codeAddress;
872        }
873
874        List<Label> labels = Lists.newArrayList();
875        for (SwitchElement element: switchElements) {
876            labels.add(newLabel(codeAddressToIndex, element.getOffset() - baseAddress));
877        }
878
879        return new BuilderPackedSwitchPayload(switchElements.get(0).getKey(), labels);
880    }
881
882    @Nonnull
883    private BuilderSparseSwitchPayload newBuilderSparseSwitchPayload(@Nonnull MethodLocation location,
884                                                                     @Nonnull int[] codeAddressToIndex,
885                                                                     @Nonnull SparseSwitchPayload instruction) {
886        List<? extends SwitchElement> switchElements = instruction.getSwitchElements();
887        if (switchElements.size() == 0) {
888            return new BuilderSparseSwitchPayload(null);
889        }
890
891        MethodLocation switchLocation = findSwitchForPayload(location);
892        int baseAddress;
893        if (switchLocation == null) {
894            baseAddress = 0;
895        } else {
896            baseAddress = switchLocation.codeAddress;
897        }
898
899        List<SwitchLabelElement> labelElements = Lists.newArrayList();
900        for (SwitchElement element: switchElements) {
901            labelElements.add(new SwitchLabelElement(element.getKey(),
902                    newLabel(codeAddressToIndex, element.getOffset() - baseAddress)));
903        }
904
905        return new BuilderSparseSwitchPayload(labelElements);
906    }
907
908    @Nonnull
909    private BuilderArrayPayload newBuilderArrayPayload(@Nonnull ArrayPayload instruction) {
910        return new BuilderArrayPayload(instruction.getElementWidth(), instruction.getArrayElements());
911    }
912
913    @Nonnull
914    private BuilderDebugItem convertDebugItem(@Nonnull DebugItem debugItem) {
915        switch (debugItem.getDebugItemType()) {
916            case DebugItemType.START_LOCAL: {
917                StartLocal startLocal = (StartLocal)debugItem;
918                return new BuilderStartLocal(startLocal.getRegister(), startLocal.getNameReference(),
919                        startLocal.getTypeReference(), startLocal.getSignatureReference());
920            }
921            case DebugItemType.END_LOCAL: {
922                EndLocal endLocal = (EndLocal)debugItem;
923                return new BuilderEndLocal(endLocal.getRegister());
924            }
925            case DebugItemType.RESTART_LOCAL: {
926                RestartLocal restartLocal = (RestartLocal)debugItem;
927                return new BuilderRestartLocal(restartLocal.getRegister());
928            }
929            case DebugItemType.PROLOGUE_END:
930                return new BuilderPrologueEnd();
931            case DebugItemType.EPILOGUE_BEGIN:
932                return new BuilderEpilogueBegin();
933            case DebugItemType.LINE_NUMBER: {
934                LineNumber lineNumber = (LineNumber)debugItem;
935                return new BuilderLineNumber(lineNumber.getLineNumber());
936            }
937            case DebugItemType.SET_SOURCE_FILE: {
938                SetSourceFile setSourceFile = (SetSourceFile)debugItem;
939                return new BuilderSetSourceFile(setSourceFile.getSourceFileReference());
940            }
941            default:
942                throw new ExceptionWithContext("Invalid debug item type: " + debugItem.getDebugItemType());
943        }
944    }
945}
946