CodeItem.java revision 281b510a9c2b4ae914ab28b9a4f4d622e5861da6
1/*
2 * [The "BSD licence"]
3 * Copyright (c) 2009 Ben Gruver
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 * 3. The name of the author may not be used to endorse or promote products
15 *    derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29package org.jf.dexlib;
30
31import org.jf.dexlib.Code.InstructionField;
32import org.jf.dexlib.Code.Opcode;
33import org.jf.dexlib.Util.AnnotatedOutput;
34import org.jf.dexlib.Util.Input;
35
36import java.util.ArrayList;
37import java.util.HashMap;
38import java.util.List;
39import java.util.Collections;
40
41public class CodeItem extends OffsettedItem<CodeItem> {
42    private final ArrayList<InstructionField> instructionList;
43    private final ArrayList<TryItem> tryItems = new ArrayList<TryItem>();
44    private final ArrayList<EncodedCatchHandler> catchHandlerList = new ArrayList<EncodedCatchHandler>();
45
46    private final ShortIntegerField registersCountField;
47    private final ShortIntegerField inArgumentCountField;
48    private final ShortIntegerField outArgumentCountField;
49    private final ListSizeField triesCountField;
50    private final OffsettedItemReference<DebugInfoItem> debugInfoReferenceField;
51    private final IntegerField instructionsSizeField;
52    private final InstructionListField instructionListField;
53    private final PaddingField paddingField;
54    private final FieldListField<TryItem> triesListField;
55    private final EncodedCatchHandlerList catchHandlersListField;
56
57    private MethodIdItem parent = null;
58
59    public CodeItem(final DexFile dexFile, int offset) {
60        super(dexFile, offset);
61
62        instructionList = new ArrayList<InstructionField>();
63
64        fields = new Field[] {
65                registersCountField = new ShortIntegerField("registers_size"),
66                inArgumentCountField = new ShortIntegerField("ins_size"),
67                outArgumentCountField = new ShortIntegerField("outs_size"),
68                triesCountField = new ListSizeField(tryItems, new ShortIntegerField("tries_size")),
69                debugInfoReferenceField = new OffsettedItemReference<DebugInfoItem>(dexFile.DebugInfoItemsSection,
70                        new IntegerField(null), "debug_off"),
71                instructionsSizeField = new IntegerField("insns_size"),
72                instructionListField = new InstructionListField(dexFile),
73                paddingField = new PaddingField(),
74                triesListField = new FieldListField<TryItem>(tryItems, "try_item") {
75                    protected TryItem make() {
76                        return new TryItem(catchHandlersListField);
77                    }
78                },
79
80                catchHandlersListField = new EncodedCatchHandlerList(dexFile)
81        };
82    }
83
84    public CodeItem(final DexFile dexFile,
85                    int registersCount,
86                    int inArguments,
87                    List<InstructionField> instructions,
88                    DebugInfoItem debugInfo,
89                    List<TryItem> tries,
90                    List<EncodedCatchHandler> handlers) {
91        this(dexFile, 0);
92
93        instructionList.addAll(instructions);
94        instructionsSizeField.cacheValue(instructionListField.getInstructionWordCount());
95
96        if (tries != null) {
97            tryItems.addAll(tries);
98            if (handlers == null) {
99                throw new RuntimeException("The handlers parameter cannot be null if tries parameter is not null");
100            }
101            catchHandlerList.addAll(handlers);
102        } else if (handlers != null) {
103            throw new RuntimeException("The handlers parameter must be null if the tries parameter is null");
104        }
105
106        registersCountField.cacheValue(registersCount);
107        inArgumentCountField.cacheValue(inArguments);
108        outArgumentCountField.cacheValue(instructionListField.getOutArguments());
109        debugInfoReferenceField.setReference(debugInfo);
110
111        if (debugInfo != null) {
112            debugInfo.setParent(this);
113        }
114    }
115
116    protected int getAlignment() {
117        return 4;
118    }
119
120    public ItemType getItemType() {
121        return ItemType.TYPE_CODE_ITEM;
122    }
123
124    public int getRegisterCount() {
125        return registersCountField.getCachedValue();
126    }
127
128    public List<InstructionField> getInstructions() {
129        return Collections.unmodifiableList(instructionList);
130    }
131
132    public List<TryItem> getTries() {
133        return Collections.unmodifiableList(tryItems);
134    }
135
136    public DebugInfoItem getDebugInfo() {
137        return debugInfoReferenceField.getReference();
138    }
139
140    protected void setParent(MethodIdItem methodIdItem) {
141        this.parent = methodIdItem;
142    }
143
144    public void copyTo(DexFile dexFile, CodeItem copy)
145    {
146        for (int i = 0; i < fields.length-2; i++) {
147            fields[i].copyTo(dexFile, copy.fields[i]);
148        }
149        //we need to do this in reverse order, so when the tries are copied,
150        //the catchHandler copies will already exist
151        catchHandlersListField.copyTo(dexFile, copy.catchHandlersListField);
152        triesListField.copyTo(dexFile, copy.triesListField);
153
154        DebugInfoItem copyDebugInfo = copy.getDebugInfo();
155        if (copy != null) {
156            copyDebugInfo.setParent(copy);
157        }
158    }
159
160    public void readFrom(Input in, int index) {
161        super.readFrom(in, index);
162
163        DebugInfoItem debugInfoItem = debugInfoReferenceField.getReference();
164        if (debugInfoItem != null) {
165            debugInfoItem.setParent(this);
166        }
167    }
168
169    public String getConciseIdentity() {
170        //TODO: should mention the method name here
171        return "code_item @0x" + Integer.toHexString(getOffset());
172    }
173
174    public int compareTo(CodeItem other) {
175        if (parent == null) {
176            if (other.parent == null) {
177                return 0;
178            }
179            return -1;
180        }
181        if (other.parent == null) {
182            return 1;
183        }
184        return parent.compareTo(other.parent);
185    }
186
187    public static class TryItem extends CompositeField<TryItem> {
188        private final IntegerField startAddr;
189        private final ShortIntegerField insnCount;
190        private final EncodedCatchHandlerReference encodedCatchHandlerReference;
191
192        public TryItem(EncodedCatchHandlerList encodedCatchHandlerList) {
193            super("try_item");
194            fields = new Field[] {
195                    startAddr = new IntegerField("start_addr"),
196                    insnCount = new ShortIntegerField("insn_count"),
197                    encodedCatchHandlerReference = new EncodedCatchHandlerReference(encodedCatchHandlerList)
198            };
199        }
200
201        public TryItem(int startAddr, int insnCount, EncodedCatchHandler encodedCatchHandler) {
202            super("try_item");
203            fields = new Field[] {
204                    this.startAddr = new IntegerField(startAddr, "start_addr"),
205                    this.insnCount = new ShortIntegerField(insnCount, "insn_count"),
206                    this.encodedCatchHandlerReference = new EncodedCatchHandlerReference(encodedCatchHandler)
207            };
208        }
209
210        public int getStartAddress() {
211            return startAddr.getCachedValue();
212        }
213
214        public int getEndAddress() {
215            return startAddr.getCachedValue() + insnCount.getCachedValue();
216        }
217
218        public EncodedCatchHandler getHandler() {
219            return encodedCatchHandlerReference.getReference();
220        }
221    }
222
223    public static class EncodedCatchHandlerReference extends ShortIntegerField {
224        private final EncodedCatchHandlerList encodedCatchHandlerList;
225        private EncodedCatchHandler encodedCatchHandler;
226
227        public EncodedCatchHandlerReference(EncodedCatchHandlerList encodedCatchHandlerList) {
228            super("encoded_catch_handler");
229            this.encodedCatchHandlerList = encodedCatchHandlerList;
230        }
231
232        public EncodedCatchHandlerReference(EncodedCatchHandler encodedCatchHandler) {
233            super("encoded_catch_handler");
234            this.encodedCatchHandlerList = null;
235            this.encodedCatchHandler = encodedCatchHandler;
236        }
237
238        public EncodedCatchHandlerList getEncodedCatchHandlerList() {
239            return encodedCatchHandlerList;
240        }
241
242        private void setReference(EncodedCatchHandler encodedCatchHandler) {
243            this.encodedCatchHandler = encodedCatchHandler;
244        }
245
246        public EncodedCatchHandler getReference() {
247            return encodedCatchHandler;
248        }
249
250        public void copyTo(DexFile dexFile, CachedIntegerValueField _copy) {
251            EncodedCatchHandlerReference copy = (EncodedCatchHandlerReference)_copy;
252            EncodedCatchHandler copiedItem = copy.getEncodedCatchHandlerList().intern(encodedCatchHandler);
253            copy.setReference(copiedItem);
254        }
255
256        public void writeTo(AnnotatedOutput out) {
257            cacheValue(encodedCatchHandler.getOffsetInList());
258
259            super.writeTo(out);
260        }
261
262        public void readFrom(Input in) {
263            super.readFrom(in);
264
265            encodedCatchHandler = encodedCatchHandlerList.getByOffset(getCachedValue());
266        }
267
268        public int place(int offset) {
269            cacheValue(encodedCatchHandler.getOffsetInList());
270            return super.place(offset);
271        }
272    }
273
274    public class EncodedCatchHandlerList extends CompositeField<EncodedCatchHandlerList> {
275        private boolean fieldPresent = false;
276        //this field is only valid when reading a dex file in
277        protected HashMap<Integer, EncodedCatchHandler> itemsByOffset =
278                new HashMap<Integer, EncodedCatchHandler>();
279
280        protected HashMap<EncodedCatchHandler, EncodedCatchHandler> uniqueItems = null;
281
282        private final DexFile dexFile;
283
284        public EncodedCatchHandler getByOffset(int offset) {
285            EncodedCatchHandler encodedCatchHandler = itemsByOffset.get(offset);
286            if (encodedCatchHandler == null) {
287                encodedCatchHandler = new EncodedCatchHandler(dexFile, offset);
288                itemsByOffset.put(offset, encodedCatchHandler);
289            }
290            return encodedCatchHandler;
291        }
292
293        public EncodedCatchHandler intern(EncodedCatchHandler item) {
294            if (uniqueItems == null) {
295                buildInternedItemMap();
296            }
297            EncodedCatchHandler encodedCatchHandler = uniqueItems.get(item);
298            if (encodedCatchHandler == null) {
299                encodedCatchHandler = new EncodedCatchHandler(dexFile, -1);
300                catchHandlerList.add(encodedCatchHandler);
301                item.copyTo(dexFile, encodedCatchHandler);
302                uniqueItems.put(encodedCatchHandler, encodedCatchHandler);
303            }
304            return encodedCatchHandler;
305        }
306
307        private void buildInternedItemMap() {
308            uniqueItems = new HashMap<EncodedCatchHandler, EncodedCatchHandler>();
309            for (EncodedCatchHandler item: catchHandlerList) {
310                uniqueItems.put(item, item);
311            }
312        }
313
314        public EncodedCatchHandlerList(final DexFile dexFile) {
315            super("encoded_catch_handler_list");
316            this.dexFile = dexFile;
317
318            fields = new Field[] {
319                sizeField = new ListSizeField(catchHandlerList, new Leb128Field("size")),
320                listField = new FieldListField<EncodedCatchHandler>(catchHandlerList, "encoded_catch_handler") {
321                    protected EncodedCatchHandler make() {
322                        return new EncodedCatchHandler(dexFile, 0);
323                    }
324
325                    public void readFrom(Input in) {
326                        int currentOffset = sizeField.place(0);
327
328                        for (int i = 0; i < list.size(); i++) {
329                                EncodedCatchHandler field = list.get(i);
330
331                                if (field == null) {
332                                    field = itemsByOffset.get(currentOffset);
333                                    if (field == null) {
334                                        field = new EncodedCatchHandler(dexFile, currentOffset);
335                                    }
336                                    list.set(i, field);
337                                }
338                                int savedOffset = in.getCursor();
339                                field.readFrom(in);
340                                currentOffset += in.getCursor() - savedOffset;
341                            }
342                        }
343                   }
344             };
345        }
346
347        private final ListSizeField sizeField;
348        private final FieldListField<EncodedCatchHandler> listField;
349
350        public void readFrom(Input in) {
351            if (tryItems.size() > 0) {
352                fieldPresent = true;
353                super.readFrom(in);
354            }
355        }
356
357        public void writeTo(AnnotatedOutput out) {
358            if (fieldPresent) {
359                super.writeTo(out);
360            }
361        }
362
363        public int place(int offset) {
364            for (EncodedCatchHandler encodedCatchHandler: listField.list) {
365                encodedCatchHandler.setBaseOffset(offset);
366            }
367            if (tryItems.size() > 0) {
368                fieldPresent = true;
369                return super.place(offset);
370            } else {
371                return offset;
372            }
373        }
374
375        public void copyTo(DexFile dexFile, EncodedCatchHandlerList copy) {
376            super.copyTo(dexFile, copy);
377            copy.fieldPresent = fieldPresent;
378            copy.itemsByOffset.clear();
379            int offset = 0;
380            for (EncodedCatchHandler encodedCatchHandler: copy.listField.list) {
381                copy.itemsByOffset.put(encodedCatchHandler.offset, encodedCatchHandler);
382            }
383        }
384    }
385
386    public static class EncodedCatchHandler extends CompositeField<EncodedCatchHandler> {
387        private ArrayList<EncodedTypeAddrPair> list;
388        boolean hasCatchAll = false;
389        private int baseOffset = 0;
390
391        private final ListSizeField size;
392        private final FieldListField<EncodedTypeAddrPair> handlers;
393        private final Leb128Field catchAllAddress;
394
395        private int offset;
396
397        public EncodedCatchHandler(final DexFile dexFile, int offset) {
398            super("encoded_catch_handler");
399            this.offset = offset;
400
401            list = new ArrayList<EncodedTypeAddrPair>();
402            fields = new Field[] {
403                    size = new ListSizeField(list, new SignedLeb128Field("size") {
404                        public void readFrom(Input in) {
405                            super.readFrom(in);
406                            hasCatchAll = (getCachedValue() <= 0);
407                        }
408
409                        public void cacheValue(int value) {
410                            super.cacheValue(value * (hasCatchAll?-1:1));
411                        }})
412                    ,
413                    handlers = new FieldListField<EncodedTypeAddrPair>(list, "encoded_type_addr_pair") {
414                        protected EncodedTypeAddrPair make() {
415                            return new EncodedTypeAddrPair(dexFile);
416                        }
417                    },
418                    catchAllAddress = new Leb128Field("catch_all_addr") {
419                        public void readFrom(Input in) {
420                            if (hasCatchAll) {
421                                super.readFrom(in);
422                            }
423                        }
424
425                        public void writeTo(AnnotatedOutput out) {
426                            if (hasCatchAll) {
427                                super.writeTo(out);
428                            }
429                        }
430
431                        public int place(int offset) {
432                            if (hasCatchAll) {
433                                return super.place(offset);
434                            }
435                            return offset;
436                        }
437                    }
438            };
439        }
440
441        public EncodedCatchHandler(final DexFile dexFile, List<EncodedTypeAddrPair> handlers, int catchAllHandler) {
442            this(dexFile, 0);
443
444            list.addAll(handlers);
445            if (catchAllHandler >= 0) {
446                hasCatchAll = true;
447                catchAllAddress.cacheValue(catchAllHandler);
448            }
449        }
450
451        public int getOffsetInList() {
452            return offset-baseOffset;
453        }
454
455        public void setBaseOffset(int baseOffset) {
456            this.baseOffset = baseOffset;
457        }
458
459        public void copyTo(DexFile dexFile, EncodedCatchHandler copy) {
460            super.copyTo(dexFile, copy);
461            copy.hasCatchAll = hasCatchAll;
462            copy.offset = offset;
463        }
464
465        public int place(int offset) {
466            this.offset = offset;
467            return super.place(offset);
468        }
469
470        public int getCatchAllAddress() {
471            if (hasCatchAll) {
472                return catchAllAddress.getCachedValue();
473            } else {
474                return -1;
475            }
476        }
477
478        public List<EncodedTypeAddrPair> getHandlers() {
479            return Collections.unmodifiableList(list);
480        }
481    }
482
483    public static class EncodedTypeAddrPair extends CompositeField<EncodedTypeAddrPair> {
484        public final IndexedItemReference<TypeIdItem> typeReferenceField;
485        public final Leb128Field handlerAddressField;
486
487        public EncodedTypeAddrPair(DexFile dexFile) {
488            super("encoded_type_addr_pair");
489            fields = new Field[] {
490                    typeReferenceField = new IndexedItemReference<TypeIdItem>(dexFile.TypeIdsSection,
491                            new Leb128Field(null), "type_idx"),
492                    handlerAddressField = new Leb128Field("addr")
493            };
494        }
495
496        public EncodedTypeAddrPair(DexFile dexFile, TypeIdItem type, int handlerOffset) {
497            this(dexFile);
498            typeReferenceField.setReference(type);
499            handlerAddressField.cacheValue(handlerOffset);
500        }
501
502        public TypeIdItem getTypeReferenceField() {
503            return typeReferenceField.getReference();
504        }
505
506        public int getHandlerAddress() {
507            return handlerAddressField.getCachedValue();
508        }
509    }
510
511    private class InstructionListField implements Field<InstructionListField> {
512        private final DexFile dexFile;
513
514        public InstructionListField(DexFile dexFile) {
515            this.dexFile = dexFile;
516        }
517
518        public void writeTo(AnnotatedOutput out) {
519            int startPosition = out.getCursor();
520            for (InstructionField instruction: instructionList) {
521                instruction.writeTo(out);
522            }
523            if ((out.getCursor() - startPosition) != (instructionsSizeField.getCachedValue() * 2)) {
524                throw new RuntimeException("Did not write the expected amount of bytes");
525            }
526        }
527
528        public void readFrom(Input in) {
529            int numBytes = instructionsSizeField.getCachedValue() * 2;
530            int startPosition = in.getCursor();
531
532            do {
533                InstructionField instruction = new InstructionField(dexFile);
534                instruction.readFrom(in);
535                instructionList.add(instruction);
536            } while (in.getCursor() - startPosition < numBytes);
537
538            if (in.getCursor() - startPosition != numBytes) {
539                throw new RuntimeException("Read past the end of the code section");
540            }
541        }
542
543        public int place(int offset) {
544            return offset + (instructionsSizeField.getCachedValue() * 2);
545        }
546
547        public void copyTo(DexFile dexFile, InstructionListField copy) {
548            ArrayList<InstructionField> copyInstructionList = copy.getInstructionList();
549            copyInstructionList.clear();
550            for (InstructionField instruction: instructionList) {
551                InstructionField instructionCopy = new InstructionField(dexFile);
552                instruction.copyTo(dexFile, instructionCopy);
553                copyInstructionList.add(instructionCopy);
554            }
555        }
556
557        private ArrayList<InstructionField> getInstructionList() {
558            return instructionList;
559        }
560
561        //return the word size of the instruction list
562        public int getInstructionWordCount() {
563            int bytes = 0;
564            for (InstructionField instruction: instructionList) {
565                bytes += instruction.getSize(bytes);
566            }
567            return bytes/2;
568        }
569
570        //return the highest parameter word count of any method invokation
571        public int getOutArguments() {
572            int maxParamWordCount = 0;
573            for (InstructionField instruction: instructionList) {
574                IndexedItem item = instruction.getInstruction().getReferencedItem();
575                if (item instanceof MethodIdItem) {
576                    MethodIdItem methodIdItem = (MethodIdItem)item;
577                    Opcode opcode = instruction.getInstruction().getOpcode();
578
579                    boolean isStatic = false;
580                    if (opcode == Opcode.INVOKE_STATIC || opcode == Opcode.INVOKE_STATIC_RANGE) {
581                        isStatic = true;
582                    }
583                    int paramWordCount = methodIdItem.getParameterRegisterCount(isStatic);
584
585                    if (maxParamWordCount < paramWordCount) {
586                        maxParamWordCount = paramWordCount;
587                    }
588                }
589            }
590            return maxParamWordCount;
591        }
592    }
593
594    private class PaddingField implements Field {
595
596        public PaddingField() {
597        }
598
599        private boolean needsAlign() {
600            return (triesCountField.getCachedValue() > 0) && (instructionsSizeField.getCachedValue() % 2 == 1);
601        }
602
603        public void writeTo(AnnotatedOutput out) {
604            if (needsAlign()) {
605                out.writeShort(0);
606            }
607        }
608
609        public void readFrom(Input in) {
610            if (needsAlign()) {
611                in.skipBytes(2);
612            }
613        }
614
615        public int place(int offset) {
616            if (needsAlign()) {
617                return offset + 2;
618            } else {
619                return offset;
620            }
621        }
622
623        public int hashCode() {
624            return 0;
625        }
626
627        public boolean equals(Object o) {
628            return getClass() == o.getClass();
629        }
630
631        public void copyTo(DexFile dexFile, Field field) {
632        }
633    }
634}
635