MutableMethodImplementation.java revision 9f48c7239037a6d5bbf4d5faebcf7a6df52cafdd
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 MethodLocation switchLocation,
487                                                @Nonnull int[] codeAddressToIndex, int codeAddress) {
488        MethodLocation referent = instructionList.get(mapCodeAddressToIndex(codeAddressToIndex, codeAddress));
489        SwitchPayloadReferenceLabel label = new SwitchPayloadReferenceLabel();
490        label.switchLocation = switchLocation;
491        referent.getLabels().add(label);
492        return label;
493    }
494
495    private void setInstruction(@Nonnull MethodLocation location, @Nonnull BuilderInstruction instruction) {
496        location.instruction = instruction;
497        instruction.location = location;
498    }
499
500    private void convertAndSetInstruction(@Nonnull MethodLocation location, int[] codeAddressToIndex,
501                                          @Nonnull Instruction instruction) {
502        switch (instruction.getOpcode().format) {
503            case Format10t:
504                setInstruction(location, newBuilderInstruction10t(location.codeAddress, codeAddressToIndex,
505                        (Instruction10t)instruction));
506                return;
507            case Format10x:
508                setInstruction(location, newBuilderInstruction10x((Instruction10x)instruction));
509                return;
510            case Format11n:
511                setInstruction(location, newBuilderInstruction11n((Instruction11n)instruction));
512                return;
513            case Format11x:
514                setInstruction(location, newBuilderInstruction11x((Instruction11x)instruction));
515                return;
516            case Format12x:
517                setInstruction(location, newBuilderInstruction12x((Instruction12x)instruction));
518                return;
519            case Format20bc:
520                setInstruction(location, newBuilderInstruction20bc((Instruction20bc)instruction));
521                return;
522            case Format20t:
523                setInstruction(location, newBuilderInstruction20t(location.codeAddress, codeAddressToIndex,
524                        (Instruction20t)instruction));
525                return;
526            case Format21c:
527                setInstruction(location, newBuilderInstruction21c((Instruction21c)instruction));
528                return;
529            case Format21ih:
530                setInstruction(location, newBuilderInstruction21ih((Instruction21ih)instruction));
531                return;
532            case Format21lh:
533                setInstruction(location, newBuilderInstruction21lh((Instruction21lh)instruction));
534                return;
535            case Format21s:
536                setInstruction(location, newBuilderInstruction21s((Instruction21s)instruction));
537                return;
538            case Format21t:
539                setInstruction(location, newBuilderInstruction21t(location.codeAddress, codeAddressToIndex,
540                        (Instruction21t)instruction));
541                return;
542            case Format22b:
543                setInstruction(location, newBuilderInstruction22b((Instruction22b)instruction));
544                return;
545            case Format22c:
546                setInstruction(location, newBuilderInstruction22c((Instruction22c)instruction));
547                return;
548            case Format22s:
549                setInstruction(location, newBuilderInstruction22s((Instruction22s)instruction));
550                return;
551            case Format22t:
552                setInstruction(location, newBuilderInstruction22t(location.codeAddress, codeAddressToIndex,
553                        (Instruction22t)instruction));
554                return;
555            case Format22x:
556                setInstruction(location, newBuilderInstruction22x((Instruction22x)instruction));
557                return;
558            case Format23x:
559                setInstruction(location, newBuilderInstruction23x((Instruction23x)instruction));
560                return;
561            case Format30t:
562                setInstruction(location, newBuilderInstruction30t(location.codeAddress, codeAddressToIndex,
563                        (Instruction30t)instruction));
564                return;
565            case Format31c:
566                setInstruction(location, newBuilderInstruction31c((Instruction31c)instruction));
567                return;
568            case Format31i:
569                setInstruction(location, newBuilderInstruction31i((Instruction31i)instruction));
570                return;
571            case Format31t:
572                setInstruction(location, newBuilderInstruction31t(location, codeAddressToIndex,
573                        (Instruction31t)instruction));
574                return;
575            case Format32x:
576                setInstruction(location, newBuilderInstruction32x((Instruction32x)instruction));
577                return;
578            case Format35c:
579                setInstruction(location, newBuilderInstruction35c((Instruction35c)instruction));
580                return;
581            case Format3rc:
582                setInstruction(location, newBuilderInstruction3rc((Instruction3rc)instruction));
583                return;
584            case Format51l:
585                setInstruction(location, newBuilderInstruction51l((Instruction51l)instruction));
586                return;
587            case PackedSwitchPayload:
588                setInstruction(location,
589                        newBuilderPackedSwitchPayload(location, codeAddressToIndex, (PackedSwitchPayload)instruction));
590                return;
591            case SparseSwitchPayload:
592                setInstruction(location,
593                        newBuilderSparseSwitchPayload(location, codeAddressToIndex, (SparseSwitchPayload)instruction));
594                return;
595            case ArrayPayload:
596                setInstruction(location, newBuilderArrayPayload((ArrayPayload)instruction));
597                return;
598            default:
599                throw new ExceptionWithContext("Instruction format %s not supported", instruction.getOpcode().format);
600        }
601    }
602
603    @Nonnull
604    private BuilderInstruction10t newBuilderInstruction10t(int codeAddress, int[] codeAddressToIndex,
605                                                           @Nonnull Instruction10t instruction) {
606        return new BuilderInstruction10t(
607                instruction.getOpcode(),
608                newLabel(codeAddressToIndex, codeAddress + instruction.getCodeOffset()));
609    }
610
611    @Nonnull
612    private BuilderInstruction10x newBuilderInstruction10x(@Nonnull Instruction10x instruction) {
613        return new BuilderInstruction10x(
614                instruction.getOpcode());
615    }
616
617    @Nonnull
618    private BuilderInstruction11n newBuilderInstruction11n(@Nonnull Instruction11n instruction) {
619        return new BuilderInstruction11n(
620                instruction.getOpcode(),
621                instruction.getRegisterA(),
622                instruction.getNarrowLiteral());
623    }
624
625    @Nonnull
626    private BuilderInstruction11x newBuilderInstruction11x(@Nonnull Instruction11x instruction) {
627        return new BuilderInstruction11x(
628                instruction.getOpcode(),
629                instruction.getRegisterA());
630    }
631
632    @Nonnull
633    private BuilderInstruction12x newBuilderInstruction12x(@Nonnull Instruction12x instruction) {
634        return new BuilderInstruction12x(
635                instruction.getOpcode(),
636                instruction.getRegisterA(),
637                instruction.getRegisterB());
638    }
639
640    @Nonnull
641    private BuilderInstruction20bc newBuilderInstruction20bc(@Nonnull Instruction20bc instruction) {
642        return new BuilderInstruction20bc(
643                instruction.getOpcode(),
644                instruction.getVerificationError(),
645                instruction.getReference());
646    }
647
648    @Nonnull
649    private BuilderInstruction20t newBuilderInstruction20t(int codeAddress, int[] codeAddressToIndex,
650                                                           @Nonnull Instruction20t instruction) {
651        return new BuilderInstruction20t(
652                instruction.getOpcode(),
653                newLabel(codeAddressToIndex, codeAddress + instruction.getCodeOffset()));
654    }
655
656    @Nonnull
657    private BuilderInstruction21c newBuilderInstruction21c(@Nonnull Instruction21c instruction) {
658        return new BuilderInstruction21c(
659                instruction.getOpcode(),
660                instruction.getRegisterA(),
661                instruction.getReference());
662    }
663
664    @Nonnull
665    private BuilderInstruction21ih newBuilderInstruction21ih(@Nonnull Instruction21ih instruction) {
666        return new BuilderInstruction21ih(
667                instruction.getOpcode(),
668                instruction.getRegisterA(),
669                instruction.getNarrowLiteral());
670    }
671
672    @Nonnull
673    private BuilderInstruction21lh newBuilderInstruction21lh(@Nonnull Instruction21lh instruction) {
674        return new BuilderInstruction21lh(
675                instruction.getOpcode(),
676                instruction.getRegisterA(),
677                instruction.getWideLiteral());
678    }
679
680    @Nonnull
681    private BuilderInstruction21s newBuilderInstruction21s(@Nonnull Instruction21s instruction) {
682        return new BuilderInstruction21s(
683                instruction.getOpcode(),
684                instruction.getRegisterA(),
685                instruction.getNarrowLiteral());
686    }
687
688    @Nonnull
689    private BuilderInstruction21t newBuilderInstruction21t(int codeAddress, int[] codeAddressToIndex,
690                                                           @Nonnull Instruction21t instruction) {
691        return new BuilderInstruction21t(
692                instruction.getOpcode(),
693                instruction.getRegisterA(),
694                newLabel(codeAddressToIndex, codeAddress + instruction.getCodeOffset()));
695    }
696
697    @Nonnull
698    private BuilderInstruction22b newBuilderInstruction22b(@Nonnull Instruction22b instruction) {
699        return new BuilderInstruction22b(
700                instruction.getOpcode(),
701                instruction.getRegisterA(),
702                instruction.getRegisterB(),
703                instruction.getNarrowLiteral());
704    }
705
706    @Nonnull
707    private BuilderInstruction22c newBuilderInstruction22c(@Nonnull Instruction22c instruction) {
708        return new BuilderInstruction22c(
709                instruction.getOpcode(),
710                instruction.getRegisterA(),
711                instruction.getRegisterB(),
712                instruction.getReference());
713    }
714
715    @Nonnull
716    private BuilderInstruction22s newBuilderInstruction22s(@Nonnull Instruction22s instruction) {
717        return new BuilderInstruction22s(
718                instruction.getOpcode(),
719                instruction.getRegisterA(),
720                instruction.getRegisterB(),
721                instruction.getNarrowLiteral());
722    }
723
724    @Nonnull
725    private BuilderInstruction22t newBuilderInstruction22t(int codeAddress, int[] codeAddressToIndex,
726                                                           @Nonnull Instruction22t instruction) {
727        return new BuilderInstruction22t(
728                instruction.getOpcode(),
729                instruction.getRegisterA(),
730                instruction.getRegisterB(),
731                newLabel(codeAddressToIndex, codeAddress + instruction.getCodeOffset()));
732    }
733
734    @Nonnull
735    private BuilderInstruction22x newBuilderInstruction22x(@Nonnull Instruction22x instruction) {
736        return new BuilderInstruction22x(
737                instruction.getOpcode(),
738                instruction.getRegisterA(),
739                instruction.getRegisterB());
740    }
741
742    @Nonnull
743    private BuilderInstruction23x newBuilderInstruction23x(@Nonnull Instruction23x instruction) {
744        return new BuilderInstruction23x(
745                instruction.getOpcode(),
746                instruction.getRegisterA(),
747                instruction.getRegisterB(),
748                instruction.getRegisterC());
749    }
750
751    @Nonnull
752    private BuilderInstruction30t newBuilderInstruction30t(int codeAddress, int[] codeAddressToIndex,
753                                                           @Nonnull Instruction30t instruction) {
754        return new BuilderInstruction30t(
755                instruction.getOpcode(),
756                newLabel(codeAddressToIndex, codeAddress + instruction.getCodeOffset()));
757    }
758
759    @Nonnull
760    private BuilderInstruction31c newBuilderInstruction31c(@Nonnull Instruction31c instruction) {
761        return new BuilderInstruction31c(
762                instruction.getOpcode(),
763                instruction.getRegisterA(),
764                instruction.getReference());
765    }
766
767    @Nonnull
768    private BuilderInstruction31i newBuilderInstruction31i(@Nonnull Instruction31i instruction) {
769        return new BuilderInstruction31i(
770                instruction.getOpcode(),
771                instruction.getRegisterA(),
772                instruction.getNarrowLiteral());
773    }
774
775    @Nonnull
776    private BuilderInstruction31t newBuilderInstruction31t(@Nonnull MethodLocation location , int[] codeAddressToIndex,
777                                                           @Nonnull Instruction31t instruction) {
778        int codeAddress = location.getCodeAddress();
779        Label newLabel;
780        if (instruction.getOpcode() != Opcode.FILL_ARRAY_DATA) {
781            // if it's a sparse switch or packed switch
782            newLabel = newSwitchPayloadReferenceLabel(location, codeAddressToIndex, codeAddress + instruction.getCodeOffset());
783        } else {
784            newLabel = newLabel(codeAddressToIndex, codeAddress + instruction.getCodeOffset());
785        }
786        return new BuilderInstruction31t(
787                instruction.getOpcode(),
788                instruction.getRegisterA(),
789                newLabel);
790    }
791
792    @Nonnull
793    private BuilderInstruction32x newBuilderInstruction32x(@Nonnull Instruction32x instruction) {
794        return new BuilderInstruction32x(
795                instruction.getOpcode(),
796                instruction.getRegisterA(),
797                instruction.getRegisterB());
798    }
799
800    @Nonnull
801    private BuilderInstruction35c newBuilderInstruction35c(@Nonnull Instruction35c instruction) {
802        return new BuilderInstruction35c(
803                instruction.getOpcode(),
804                instruction.getRegisterCount(),
805                instruction.getRegisterC(),
806                instruction.getRegisterD(),
807                instruction.getRegisterE(),
808                instruction.getRegisterF(),
809                instruction.getRegisterG(),
810                instruction.getReference());
811    }
812
813    @Nonnull
814    private BuilderInstruction3rc newBuilderInstruction3rc(@Nonnull Instruction3rc instruction) {
815        return new BuilderInstruction3rc(
816                instruction.getOpcode(),
817                instruction.getStartRegister(),
818                instruction.getRegisterCount(),
819                instruction.getReference());
820    }
821
822    @Nonnull
823    private BuilderInstruction51l newBuilderInstruction51l(@Nonnull Instruction51l instruction) {
824        return new BuilderInstruction51l(
825                instruction.getOpcode(),
826                instruction.getRegisterA(),
827                instruction.getWideLiteral());
828    }
829
830    @Nullable
831    private MethodLocation findSwitchForPayload(@Nonnull MethodLocation payloadLocation) {
832        MethodLocation location = payloadLocation;
833        MethodLocation switchLocation = null;
834        do {
835            for (Label label: location.getLabels()) {
836                if (label instanceof SwitchPayloadReferenceLabel) {
837                    if (switchLocation != null) {
838                        throw new IllegalStateException("Multiple switch instructions refer to the same payload. " +
839                                "This is not currently supported. Please file a bug :)");
840                    }
841                    switchLocation = ((SwitchPayloadReferenceLabel)label).switchLocation;
842                }
843            }
844
845            // A switch instruction can refer to the payload instruction itself, or to a nop before the payload
846            // instruction.
847            // We need to search for all occurrences of a switch reference, so we can detect when multiple switch
848            // statements refer to the same payload
849            // TODO: confirm that it could refer to the first NOP in a series of NOPs preceding the payload
850            if (location.index == 0) {
851                return switchLocation;
852            }
853            location = instructionList.get(location.index - 1);
854            if (location.instruction == null || location.instruction.getOpcode() != Opcode.NOP) {
855                return switchLocation;
856            }
857        } while (true);
858    }
859
860    @Nonnull
861    private BuilderPackedSwitchPayload newBuilderPackedSwitchPayload(@Nonnull MethodLocation location,
862                                                                     @Nonnull int[] codeAddressToIndex,
863                                                                     @Nonnull PackedSwitchPayload instruction) {
864        List<? extends SwitchElement> switchElements = instruction.getSwitchElements();
865        if (switchElements.size() == 0) {
866            return new BuilderPackedSwitchPayload(0, null);
867        }
868
869        MethodLocation switchLocation = findSwitchForPayload(location);
870        int baseAddress;
871        if (switchLocation == null) {
872            baseAddress = 0;
873        } else {
874            baseAddress = switchLocation.codeAddress;
875        }
876
877        List<Label> labels = Lists.newArrayList();
878        for (SwitchElement element: switchElements) {
879            labels.add(newLabel(codeAddressToIndex, element.getOffset() + baseAddress));
880        }
881
882        return new BuilderPackedSwitchPayload(switchElements.get(0).getKey(), labels);
883    }
884
885    @Nonnull
886    private BuilderSparseSwitchPayload newBuilderSparseSwitchPayload(@Nonnull MethodLocation location,
887                                                                     @Nonnull int[] codeAddressToIndex,
888                                                                     @Nonnull SparseSwitchPayload instruction) {
889        List<? extends SwitchElement> switchElements = instruction.getSwitchElements();
890        if (switchElements.size() == 0) {
891            return new BuilderSparseSwitchPayload(null);
892        }
893
894        MethodLocation switchLocation = findSwitchForPayload(location);
895        int baseAddress;
896        if (switchLocation == null) {
897            baseAddress = 0;
898        } else {
899            baseAddress = switchLocation.codeAddress;
900        }
901
902        List<SwitchLabelElement> labelElements = Lists.newArrayList();
903        for (SwitchElement element: switchElements) {
904            labelElements.add(new SwitchLabelElement(element.getKey(),
905                    newLabel(codeAddressToIndex, element.getOffset() + baseAddress)));
906        }
907
908        return new BuilderSparseSwitchPayload(labelElements);
909    }
910
911    @Nonnull
912    private BuilderArrayPayload newBuilderArrayPayload(@Nonnull ArrayPayload instruction) {
913        return new BuilderArrayPayload(instruction.getElementWidth(), instruction.getArrayElements());
914    }
915
916    @Nonnull
917    private BuilderDebugItem convertDebugItem(@Nonnull DebugItem debugItem) {
918        switch (debugItem.getDebugItemType()) {
919            case DebugItemType.START_LOCAL: {
920                StartLocal startLocal = (StartLocal)debugItem;
921                return new BuilderStartLocal(startLocal.getRegister(), startLocal.getNameReference(),
922                        startLocal.getTypeReference(), startLocal.getSignatureReference());
923            }
924            case DebugItemType.END_LOCAL: {
925                EndLocal endLocal = (EndLocal)debugItem;
926                return new BuilderEndLocal(endLocal.getRegister());
927            }
928            case DebugItemType.RESTART_LOCAL: {
929                RestartLocal restartLocal = (RestartLocal)debugItem;
930                return new BuilderRestartLocal(restartLocal.getRegister());
931            }
932            case DebugItemType.PROLOGUE_END:
933                return new BuilderPrologueEnd();
934            case DebugItemType.EPILOGUE_BEGIN:
935                return new BuilderEpilogueBegin();
936            case DebugItemType.LINE_NUMBER: {
937                LineNumber lineNumber = (LineNumber)debugItem;
938                return new BuilderLineNumber(lineNumber.getLineNumber());
939            }
940            case DebugItemType.SET_SOURCE_FILE: {
941                SetSourceFile setSourceFile = (SetSourceFile)debugItem;
942                return new BuilderSetSourceFile(setSourceFile.getSourceFileReference());
943            }
944            default:
945                throw new ExceptionWithContext("Invalid debug item type: " + debugItem.getDebugItemType());
946        }
947    }
948}
949