CodeItem.java revision f730ada9829a6fb092ed435dcbd38af6fdf0f162
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    public CodeItem(final DexFile dexFile, int offset) {
58        super(offset);
59
60        instructionList = new ArrayList<InstructionField>();
61
62        fields = new Field[] {
63                registersCountField = new ShortIntegerField("registers_size"),
64                inArgumentCountField = new ShortIntegerField("ins_size"),
65                outArgumentCountField = new ShortIntegerField("outs_size"),
66                triesCountField = new ListSizeField(tryItems, new ShortIntegerField("tries_size")),
67                debugInfoReferenceField = new OffsettedItemReference<DebugInfoItem>(dexFile.DebugInfoItemsSection,
68                        new IntegerField(null), "debug_off"),
69                instructionsSizeField = new IntegerField("insns_size"),
70                instructionListField = new InstructionListField(dexFile),
71                paddingField = new PaddingField(),
72                triesListField = new FieldListField<TryItem>(tryItems, "try_item") {
73                    protected TryItem make() {
74                        return new TryItem(catchHandlersListField);
75                    }
76                },
77
78                catchHandlersListField = new EncodedCatchHandlerList(dexFile)
79        };
80    }
81
82    public CodeItem(final DexFile dexFile,
83                    int registersCount,
84                    int inArguments,
85                    List<InstructionField> instructions,
86                    DebugInfoItem debugInfo,
87                    List<TryItem> tries,
88                    List<EncodedCatchHandler> handlers) {
89        this(dexFile, 0);
90
91        instructionList.addAll(instructions);
92        instructionsSizeField.cacheValue(instructionListField.getInstructionWordCount());
93
94        if (tries != null) {
95            tryItems.addAll(tries);
96            if (handlers == null) {
97                throw new RuntimeException("The handlers parameter cannot be null if tries parameter is not null");
98            }
99            catchHandlerList.addAll(handlers);
100        } else if (handlers != null) {
101            throw new RuntimeException("The handlers parameter must be null if the tries parameter is null");
102        }
103
104        registersCountField.cacheValue(registersCount);
105        inArgumentCountField.cacheValue(inArguments);
106        outArgumentCountField.cacheValue(instructionListField.getOutArguments());
107        debugInfoReferenceField.setReference(debugInfo);
108    }
109
110    protected int getAlignment() {
111        return 4;
112    }
113
114    public ItemType getItemType() {
115        return ItemType.TYPE_CODE_ITEM;
116    }
117
118    public int getRegisterCount() {
119        return registersCountField.getCachedValue();
120    }
121
122    public List<InstructionField> getInstructions() {
123        return Collections.unmodifiableList(instructionList);
124    }
125
126    public List<TryItem> getTries() {
127        return Collections.unmodifiableList(tryItems);
128    }
129
130    public DebugInfoItem getDebugInfo() {
131        return debugInfoReferenceField.getReference();
132    }
133
134    public void copyTo(DexFile dexFile, CodeItem copy)
135    {
136        for (int i = 0; i < fields.length-2; i++) {
137            fields[i].copyTo(dexFile, copy.fields[i]);
138        }
139        //we need to do this in reverse order, so when the tries are copied,
140        //the catchHandler copies will already exist
141        catchHandlersListField.copyTo(dexFile, copy.catchHandlersListField);
142        triesListField.copyTo(dexFile, copy.triesListField);
143    }
144
145    public String getConciseIdentity() {
146        //TODO: should mention the method name here
147        return "code_item @0x" + Integer.toHexString(getOffset());
148    }
149
150    public static class TryItem extends CompositeField<TryItem> {
151        private final IntegerField startAddr;
152        private final ShortIntegerField insnCount;
153        private final EncodedCatchHandlerReference encodedCatchHandlerReference;
154
155        public TryItem(EncodedCatchHandlerList encodedCatchHandlerList) {
156            super("try_item");
157            fields = new Field[] {
158                    startAddr = new IntegerField("start_addr"),
159                    insnCount = new ShortIntegerField("insn_count"),
160                    encodedCatchHandlerReference = new EncodedCatchHandlerReference(encodedCatchHandlerList)
161            };
162        }
163
164        public TryItem(int startAddr, int insnCount, EncodedCatchHandler encodedCatchHandler) {
165            super("try_item");
166            fields = new Field[] {
167                    this.startAddr = new IntegerField(startAddr, "start_addr"),
168                    this.insnCount = new ShortIntegerField(insnCount, "insn_count"),
169                    this.encodedCatchHandlerReference = new EncodedCatchHandlerReference(encodedCatchHandler)
170            };
171        }
172
173        public int getStartAddress() {
174            return startAddr.getCachedValue();
175        }
176
177        public int getEndAddress() {
178            return startAddr.getCachedValue() + insnCount.getCachedValue();
179        }
180
181        public EncodedCatchHandler getHandler() {
182            return encodedCatchHandlerReference.getReference();
183        }
184    }
185
186    public static class EncodedCatchHandlerReference extends ShortIntegerField {
187        private final EncodedCatchHandlerList encodedCatchHandlerList;
188        private EncodedCatchHandler encodedCatchHandler;
189
190        public EncodedCatchHandlerReference(EncodedCatchHandlerList encodedCatchHandlerList) {
191            super("encoded_catch_handler");
192            this.encodedCatchHandlerList = encodedCatchHandlerList;
193        }
194
195        public EncodedCatchHandlerReference(EncodedCatchHandler encodedCatchHandler) {
196            super("encoded_catch_handler");
197            this.encodedCatchHandlerList = null;
198            this.encodedCatchHandler = encodedCatchHandler;
199        }
200
201        public EncodedCatchHandlerList getEncodedCatchHandlerList() {
202            return encodedCatchHandlerList;
203        }
204
205        private void setReference(EncodedCatchHandler encodedCatchHandler) {
206            this.encodedCatchHandler = encodedCatchHandler;
207        }
208
209        public EncodedCatchHandler getReference() {
210            return encodedCatchHandler;
211        }
212
213        public void copyTo(DexFile dexFile, CachedIntegerValueField _copy) {
214            EncodedCatchHandlerReference copy = (EncodedCatchHandlerReference)_copy;
215            EncodedCatchHandler copiedItem = copy.getEncodedCatchHandlerList().intern(encodedCatchHandler);
216            copy.setReference(copiedItem);
217        }
218
219        public void writeTo(AnnotatedOutput out) {
220            cacheValue(encodedCatchHandler.getOffsetInList());
221
222            super.writeTo(out);
223        }
224
225        public void readFrom(Input in) {
226            super.readFrom(in);
227
228            encodedCatchHandler = encodedCatchHandlerList.getByOffset(getCachedValue());
229        }
230
231        public int place(int offset) {
232            cacheValue(encodedCatchHandler.getOffsetInList());
233            return super.place(offset);
234        }
235    }
236
237    public class EncodedCatchHandlerList extends CompositeField<EncodedCatchHandlerList> {
238        private boolean fieldPresent = false;
239        //this field is only valid when reading a dex file in
240        protected HashMap<Integer, EncodedCatchHandler> itemsByOffset =
241                new HashMap<Integer, EncodedCatchHandler>();
242
243        protected HashMap<EncodedCatchHandler, EncodedCatchHandler> uniqueItems = null;
244
245        private final DexFile dexFile;
246
247        public EncodedCatchHandler getByOffset(int offset) {
248            EncodedCatchHandler encodedCatchHandler = itemsByOffset.get(offset);
249            if (encodedCatchHandler == null) {
250                encodedCatchHandler = new EncodedCatchHandler(dexFile, offset);
251                itemsByOffset.put(offset, encodedCatchHandler);
252            }
253            return encodedCatchHandler;
254        }
255
256        public EncodedCatchHandler intern(EncodedCatchHandler item) {
257            if (uniqueItems == null) {
258                buildInternedItemMap();
259            }
260            EncodedCatchHandler encodedCatchHandler = uniqueItems.get(item);
261            if (encodedCatchHandler == null) {
262                encodedCatchHandler = new EncodedCatchHandler(dexFile, -1);
263                catchHandlerList.add(encodedCatchHandler);
264                item.copyTo(dexFile, encodedCatchHandler);
265                uniqueItems.put(encodedCatchHandler, encodedCatchHandler);
266            }
267            return encodedCatchHandler;
268        }
269
270        private void buildInternedItemMap() {
271            uniqueItems = new HashMap<EncodedCatchHandler, EncodedCatchHandler>();
272            for (EncodedCatchHandler item: catchHandlerList) {
273                uniqueItems.put(item, item);
274            }
275        }
276
277        public EncodedCatchHandlerList(final DexFile dexFile) {
278            super("encoded_catch_handler_list");
279            this.dexFile = dexFile;
280
281            fields = new Field[] {
282                sizeField = new ListSizeField(catchHandlerList, new Leb128Field("size")),
283                listField = new FieldListField<EncodedCatchHandler>(catchHandlerList, "encoded_catch_handler") {
284                    protected EncodedCatchHandler make() {
285                        return new EncodedCatchHandler(dexFile, 0);
286                    }
287
288                    public void readFrom(Input in) {
289                        int currentOffset = sizeField.place(0);
290
291                        for (int i = 0; i < list.size(); i++) {
292                                EncodedCatchHandler field = list.get(i);
293
294                                if (field == null) {
295                                    field = itemsByOffset.get(currentOffset);
296                                    if (field == null) {
297                                        field = new EncodedCatchHandler(dexFile, currentOffset);
298                                    }
299                                    list.set(i, field);
300                                }
301                                int savedOffset = in.getCursor();
302                                field.readFrom(in);
303                                currentOffset += in.getCursor() - savedOffset;
304                            }
305                        }
306                   }
307             };
308        }
309
310        private final ListSizeField sizeField;
311        private final FieldListField<EncodedCatchHandler> listField;
312
313        public void readFrom(Input in) {
314            if (tryItems.size() > 0) {
315                fieldPresent = true;
316                super.readFrom(in);
317            }
318        }
319
320        public void writeTo(AnnotatedOutput out) {
321            if (fieldPresent) {
322                super.writeTo(out);
323            }
324        }
325
326        public int place(int offset) {
327            for (EncodedCatchHandler encodedCatchHandler: listField.list) {
328                encodedCatchHandler.setBaseOffset(offset);
329            }
330            if (tryItems.size() > 0) {
331                fieldPresent = true;
332                return super.place(offset);
333            } else {
334                return offset;
335            }
336        }
337
338        public void copyTo(DexFile dexFile, EncodedCatchHandlerList copy) {
339            super.copyTo(dexFile, copy);
340            copy.fieldPresent = fieldPresent;
341            copy.itemsByOffset.clear();
342            int offset = 0;
343            for (EncodedCatchHandler encodedCatchHandler: copy.listField.list) {
344                copy.itemsByOffset.put(encodedCatchHandler.offset, encodedCatchHandler);
345            }
346        }
347    }
348
349    public static class EncodedCatchHandler extends CompositeField<EncodedCatchHandler> {
350        private ArrayList<EncodedTypeAddrPair> list;
351        boolean hasCatchAll = false;
352        private int baseOffset = 0;
353
354        private final ListSizeField size;
355        private final FieldListField<EncodedTypeAddrPair> handlers;
356        private final Leb128Field catchAllAddress;
357
358        private int offset;
359
360        public EncodedCatchHandler(final DexFile dexFile, int offset) {
361            super("encoded_catch_handler");
362            this.offset = offset;
363
364            list = new ArrayList<EncodedTypeAddrPair>();
365            fields = new Field[] {
366                    size = new ListSizeField(list, new SignedLeb128Field("size") {
367                        public void readFrom(Input in) {
368                            super.readFrom(in);
369                            hasCatchAll = (getCachedValue() <= 0);
370                        }
371
372                        public void cacheValue(int value) {
373                            super.cacheValue(value * (hasCatchAll?-1:1));
374                        }})
375                    ,
376                    handlers = new FieldListField<EncodedTypeAddrPair>(list, "encoded_type_addr_pair") {
377                        protected EncodedTypeAddrPair make() {
378                            return new EncodedTypeAddrPair(dexFile);
379                        }
380                    },
381                    catchAllAddress = new Leb128Field("catch_all_addr") {
382                        public void readFrom(Input in) {
383                            if (hasCatchAll) {
384                                super.readFrom(in);
385                            }
386                        }
387
388                        public void writeTo(AnnotatedOutput out) {
389                            if (hasCatchAll) {
390                                super.writeTo(out);
391                            }
392                        }
393
394                        public int place(int offset) {
395                            if (hasCatchAll) {
396                                return super.place(offset);
397                            }
398                            return offset;
399                        }
400                    }
401            };
402        }
403
404        public EncodedCatchHandler(final DexFile dexFile, List<EncodedTypeAddrPair> handlers, int catchAllHandler) {
405            this(dexFile, 0);
406
407            list.addAll(handlers);
408            if (catchAllHandler >= 0) {
409                hasCatchAll = true;
410                catchAllAddress.cacheValue(catchAllHandler);
411            }
412        }
413
414        public int getOffsetInList() {
415            return offset-baseOffset;
416        }
417
418        public void setBaseOffset(int baseOffset) {
419            this.baseOffset = baseOffset;
420        }
421
422        public void copyTo(DexFile dexFile, EncodedCatchHandler copy) {
423            super.copyTo(dexFile, copy);
424            copy.hasCatchAll = hasCatchAll;
425            copy.offset = offset;
426        }
427
428        public int place(int offset) {
429            this.offset = offset;
430            return super.place(offset);
431        }
432
433        public int getCatchAllAddress() {
434            if (hasCatchAll) {
435                return catchAllAddress.getCachedValue();
436            } else {
437                return -1;
438            }
439        }
440
441        public List<EncodedTypeAddrPair> getHandlers() {
442            return Collections.unmodifiableList(list);
443        }
444    }
445
446    public static class EncodedTypeAddrPair extends CompositeField<EncodedTypeAddrPair> {
447        public final IndexedItemReference<TypeIdItem> typeReferenceField;
448        public final Leb128Field handlerAddressField;
449
450        public EncodedTypeAddrPair(DexFile dexFile) {
451            super("encoded_type_addr_pair");
452            fields = new Field[] {
453                    typeReferenceField = new IndexedItemReference<TypeIdItem>(dexFile.TypeIdsSection,
454                            new Leb128Field(null), "type_idx"),
455                    handlerAddressField = new Leb128Field("addr")
456            };
457        }
458
459        public EncodedTypeAddrPair(DexFile dexFile, TypeIdItem type, int handlerOffset) {
460            this(dexFile);
461            typeReferenceField.setReference(type);
462            handlerAddressField.cacheValue(handlerOffset);
463        }
464
465        public TypeIdItem getTypeReferenceField() {
466            return typeReferenceField.getReference();
467        }
468
469        public int getHandlerAddress() {
470            return handlerAddressField.getCachedValue();
471        }
472    }
473
474    private class InstructionListField implements Field<InstructionListField> {
475        private final DexFile dexFile;
476
477        public InstructionListField(DexFile dexFile) {
478            this.dexFile = dexFile;
479        }
480
481        public void writeTo(AnnotatedOutput out) {
482            int startPosition = out.getCursor();
483            for (InstructionField instruction: instructionList) {
484                instruction.writeTo(out);
485            }
486            if ((out.getCursor() - startPosition) != (instructionsSizeField.getCachedValue() * 2)) {
487                throw new RuntimeException("Did not write the expected amount of bytes");
488            }
489        }
490
491        public void readFrom(Input in) {
492            int numBytes = instructionsSizeField.getCachedValue() * 2;
493            int startPosition = in.getCursor();
494
495            do {
496                InstructionField instruction = new InstructionField(dexFile);
497                instruction.readFrom(in);
498                instructionList.add(instruction);
499            } while (in.getCursor() - startPosition < numBytes);
500
501            if (in.getCursor() - startPosition != numBytes) {
502                throw new RuntimeException("Read past the end of the code section");
503            }
504        }
505
506        public int place(int offset) {
507            return offset + (instructionsSizeField.getCachedValue() * 2);
508        }
509
510        public void copyTo(DexFile dexFile, InstructionListField copy) {
511            ArrayList<InstructionField> copyInstructionList = copy.getInstructionList();
512            copyInstructionList.clear();
513            for (InstructionField instruction: instructionList) {
514                InstructionField instructionCopy = new InstructionField(dexFile);
515                instruction.copyTo(dexFile, instructionCopy);
516                copyInstructionList.add(instructionCopy);
517            }
518        }
519
520        private ArrayList<InstructionField> getInstructionList() {
521            return instructionList;
522        }
523
524        //return the word size of the instruction list
525        public int getInstructionWordCount() {
526            int bytes = 0;
527            for (InstructionField instruction: instructionList) {
528                bytes += instruction.getSize(bytes);
529            }
530            return bytes/2;
531        }
532
533        //return the highest parameter word count of any method invokation
534        public int getOutArguments() {
535            int maxParamWordCount = 0;
536            for (InstructionField instruction: instructionList) {
537                IndexedItem item = instruction.getInstruction().getReferencedItem();
538                if (item instanceof MethodIdItem) {
539                    MethodIdItem methodIdItem = (MethodIdItem)item;
540                    Opcode opcode = instruction.getInstruction().getOpcode();
541
542                    boolean isStatic = false;
543                    if (opcode == Opcode.INVOKE_STATIC || opcode == Opcode.INVOKE_STATIC_RANGE) {
544                        isStatic = true;
545                    }
546                    int paramWordCount = methodIdItem.getParameterRegisterCount(isStatic);
547
548                    if (maxParamWordCount < paramWordCount) {
549                        maxParamWordCount = paramWordCount;
550                    }
551                }
552            }
553            return maxParamWordCount;
554        }
555    }
556
557    private class PaddingField implements Field {
558
559        public PaddingField() {
560        }
561
562        private boolean needsAlign() {
563            return (triesCountField.getCachedValue() > 0) && (instructionsSizeField.getCachedValue() % 2 == 1);
564        }
565
566        public void writeTo(AnnotatedOutput out) {
567            if (needsAlign()) {
568                out.writeShort(0);
569            }
570        }
571
572        public void readFrom(Input in) {
573            if (needsAlign()) {
574                in.skipBytes(2);
575            }
576        }
577
578        public int place(int offset) {
579            if (needsAlign()) {
580                return offset + 2;
581            } else {
582                return offset;
583            }
584        }
585
586        public int hashCode() {
587            return 0;
588        }
589
590        public boolean equals(Object o) {
591            return getClass() == o.getClass();
592        }
593
594        public void copyTo(DexFile dexFile, Field field) {
595        }
596    }
597}
598