diff --git a/src/main/antlr3/org/JesusFreke/smali/smaliLexer.g b/src/main/antlr3/org/JesusFreke/smali/smaliLexer.g index 048221e4..edd15f16 100644 --- a/src/main/antlr3/org/JesusFreke/smali/smaliLexer.g +++ b/src/main/antlr3/org/JesusFreke/smali/smaliLexer.g @@ -98,6 +98,10 @@ it would emit a token for each of its children tokens.*/ lexer grammar smaliLexer; +tokens { + ACCESS_SPEC; +} + @lexer::header { package org.JesusFreke.smali; @@ -192,11 +196,10 @@ throw e; } }*/ - CLASS_PHRASE : CLASS_DIRECTIVE_EMIT WS - (ACCESS_SPEC_EMIT WS)+ + (CLASS_ACCESS_SPEC_EMIT WS)+ CLASS_DESCRIPTOR_EMIT; SUPER_PHRASE @@ -207,7 +210,7 @@ SUPER_PHRASE FIELD_PHRASE : FIELD_DIRECTIVE_EMIT WS - (ACCESS_SPEC_EMIT WS)+ + (FIELD_ACCESS_SPEC_EMIT WS)+ MEMBER_NAME_EMIT WS FIELD_TYPE_DESCRIPTOR_EMITCHILD @@ -217,7 +220,7 @@ FIELD_PHRASE METHOD_PHRASE : METHOD_DIRECTIVE_EMIT WS - (ACCESS_SPEC_EMIT WS)+ + (METHOD_ACCESS_SPEC_EMIT WS)+ MEMBER_NAME_EMIT METHOD_PROTOTYPE_EMITCHILDREN; @@ -450,6 +453,19 @@ SPARSE_SWITCH_PHRASE WSC END_SPARSE_SWITCH_DIRECTIVE_EMIT; +CATCH_PHRASE + : CATCH_DIRECTIVE_EMIT + WS + FIELD_TYPE_DESCRIPTOR_EMITCHILD + WS 'from' WS + (LABEL_EMIT | OFFSET_EMIT) + WS 'to' WS + (LABEL_EMIT | OFFSET_EMIT) + WS 'using' + (LABEL_EMIT | OFFSET_EMIT); + + +//TODO: add support for both relative and absolute offsets? fragment OFFSET_EMIT : OFFSET {emit($OFFSET, OFFSET);}; fragment OFFSET @@ -520,6 +536,11 @@ fragment END_SPARSE_SWITCH_DIRECTIVE_EMIT fragment END_SPARSE_SWITCH_DIRECTIVE : '.end sparse-switch'; +fragment CATCH_DIRECTIVE_EMIT + : CATCH_DIRECTIVE {emit($CATCH_DIRECTIVE, CATCH_DIRECTIVE);}; +fragment CATCH_DIRECTIVE + : '.catch'; + fragment REGISTER_EMIT : REGISTER {emit($REGISTER, REGISTER);}; fragment REGISTER @@ -618,17 +639,51 @@ fragment ARRAY_CHAR_LIST[int maxCount] ; -fragment ACCESS_SPEC_EMIT - : ACCESS_SPEC {emit($ACCESS_SPEC, ACCESS_SPEC);}; +fragment CLASS_ACCESS_SPEC_EMIT + : CLASS_ACCESS_SPEC {emit($CLASS_ACCESS_SPEC, ACCESS_SPEC);}; -fragment ACCESS_SPEC +//TODO: add ACC_ANNOTATION when adding support for annotations +fragment CLASS_ACCESS_SPEC + : 'public' + | 'final' + | 'interface' + | 'abstract' + | 'synthetic' + | 'enum'; + +fragment FIELD_ACCESS_SPEC_EMIT + : FIELD_ACCESS_SPEC {emit($FIELD_ACCESS_SPEC, ACCESS_SPEC);}; + +fragment FIELD_ACCESS_SPEC : 'public' | 'private' + | 'protected' | 'static' + | 'final' + | 'volatile' + | 'transient' + | 'synthetic' + | 'enum'; + +fragment METHOD_ACCESS_SPEC_EMIT + : METHOD_ACCESS_SPEC {emit($METHOD_ACCESS_SPEC, ACCESS_SPEC);}; + +fragment METHOD_ACCESS_SPEC + : 'public' + | 'private' + | 'protected' + | 'static' + | 'final' + | 'synchronized' + | 'bridge' + | 'varargs' + | 'native' + | 'abstract' + | 'strictfp' + | 'synthetic' | 'constructor' - | 'final'; + | 'declared-synchronized'; - fragment MEMBER_NAME_EMIT : MEMBER_NAME {emit($MEMBER_NAME, MEMBER_NAME);}; diff --git a/src/main/antlr3/org/JesusFreke/smali/smaliParser.g b/src/main/antlr3/org/JesusFreke/smali/smaliParser.g index e60d2aac..00c816c6 100644 --- a/src/main/antlr3/org/JesusFreke/smali/smaliParser.g +++ b/src/main/antlr3/org/JesusFreke/smali/smaliParser.g @@ -60,6 +60,9 @@ tokens { I_SPARSE_SWITCH_KEYS; I_SPARSE_SWITCH_TARGET_COUNT; I_SPARSE_SWITCH_TARGETS; + I_CATCH; + I_CATCHES; + I_CATCH_ADDRESS; I_STATEMENTS; I_STATEMENT_FORMAT10t; I_STATEMENT_FORMAT10x; @@ -151,9 +154,14 @@ statements scope {int currentAddress;} : {$statements::currentAddress = 0;} ( instruction {$statements::currentAddress += $instruction.size/2;} + | catch_directive | label)* - -> ^(I_LABELS label*) ^(I_STATEMENTS instruction*); + -> ^(I_LABELS label*) ^(I_STATEMENTS instruction*) ^(I_CATCHES catch_directive*); +catch_directive + : CATCH_DIRECTIVE field_type_descriptor from=offset_or_label to=offset_or_label using=offset_or_label + -> ^(I_CATCH[$start, "I_CATCH"] I_CATCH_ADDRESS[$start, Integer.toString($statements::currentAddress)] field_type_descriptor $from $to $using) + ; label : LABEL -> ^(I_LABEL LABEL {new CommonTree(new CommonToken(INTEGER_LITERAL,Integer.toString($statements::currentAddress)))}); diff --git a/src/main/antlr3/org/JesusFreke/smali/smaliTreeWalker.g b/src/main/antlr3/org/JesusFreke/smali/smaliTreeWalker.g index e98bb409..5ffaa987 100644 --- a/src/main/antlr3/org/JesusFreke/smali/smaliTreeWalker.g +++ b/src/main/antlr3/org/JesusFreke/smali/smaliTreeWalker.g @@ -263,13 +263,15 @@ method returns[ClassDataItem.EncodedMethod encodedMethod] scope { HashMap labels; + TryListBuilder tryList; int currentAddress; } : { $method::labels = new HashMap(); + $method::tryList = new TryListBuilder(); $method::currentAddress = 0; } - ^(I_METHOD method_name_and_prototype access_list registers_directive labels statements) + ^(I_METHOD method_name_and_prototype access_list registers_directive labels statements catches) { MethodIdItem methodIdItem = $method_name_and_prototype.methodIdItem; int registers = $registers_directive.registers; @@ -277,7 +279,11 @@ method returns[ClassDataItem.EncodedMethod encodedMethod] boolean isStatic = (access & AccessFlags.STATIC) != 0; ArrayList instructions = $statements.instructions; - CodeItem codeItem = new CodeItem(dexFile, registers, methodIdItem.getParameterWordCount(isStatic), instructions); + Pair, List> temp = $method::tryList.encodeTries(dexFile); + List tries = temp.first; + List handlers = temp.second; + + CodeItem codeItem = new CodeItem(dexFile, registers, methodIdItem.getParameterWordCount(isStatic), instructions, tries, handlers); $encodedMethod = new ClassDataItem.EncodedMethod(dexFile, methodIdItem, access, codeItem); }; @@ -337,6 +343,19 @@ fully_qualified_field returns[FieldIdItem fieldIdItem] $fieldIdItem = new FieldIdItem(dexFile, classType, fieldName, fieldType); }; +catches : ^(I_CATCHES catch_directive*); + +catch_directive + : ^(I_CATCH I_CATCH_ADDRESS field_type_descriptor from=offset_or_label to=offset_or_label using=offset_or_label) + { + TypeIdItem type = $field_type_descriptor.type; + int startAddress = $from.offsetValue + $method::currentAddress; + int endAddress = $to.offsetValue + $method::currentAddress; + int handlerAddress = $using.offsetValue + $method::currentAddress; + + $method::tryList.addHandler(type, startAddress, endAddress, handlerAddress); + }; + labels : ^(I_LABELS label_def*); diff --git a/src/main/java/org/JesusFreke/dexlib/CodeItem.java b/src/main/java/org/JesusFreke/dexlib/CodeItem.java index f78d91cd..f6714641 100644 --- a/src/main/java/org/JesusFreke/dexlib/CodeItem.java +++ b/src/main/java/org/JesusFreke/dexlib/CodeItem.java @@ -35,6 +35,8 @@ import org.JesusFreke.dexlib.util.Input; import org.JesusFreke.dexlib.util.Output; import java.util.ArrayList; +import java.util.List; +import java.util.HashMap; public class CodeItem extends OffsettedItem { private final Field[] fields; @@ -69,7 +71,7 @@ public class CodeItem extends OffsettedItem { padding = new PaddingField(), tries = new FieldListField(tryItems) { protected TryItem make() { - return new TryItem(); + return new TryItem(catchHandlers); } }, @@ -79,12 +81,22 @@ public class CodeItem extends OffsettedItem { - public CodeItem(final DexFile dexFile, int registersCount, int inArguments, ArrayList instructions) { + public CodeItem(final DexFile dexFile, int registersCount, int inArguments, ArrayList instructions, List tries, List handlers) { super(-1); this.instructionList = new ArrayList(instructions); this.instructionListField = new InstructionListField(dexFile); + if (tries != null) { + tryItems.addAll(tries); + if (handlers == null) { + throw new RuntimeException("The handlers parameter cannot be null if tries parameter is not null"); + } + catchHandlerList.addAll(handlers); + } else if (handlers != null) { + throw new RuntimeException("The handlers parameter must be null if the tries parameter is null"); + } + fields = new Field[] { this.registersCount = new ShortIntegerField(registersCount), this.inArgumentCount = new ShortIntegerField(inArguments), @@ -96,7 +108,7 @@ public class CodeItem extends OffsettedItem { this.padding = new PaddingField(), this.tries = new FieldListField(tryItems) { protected TryItem make() { - return new TryItem(); + return new TryItem(catchHandlers); } }, this.catchHandlers = new EncodedCatchHandlerList(dexFile) @@ -111,43 +123,148 @@ public class CodeItem extends OffsettedItem { return ItemType.TYPE_CODE_ITEM; } + public void copyTo(DexFile dexFile, CodeItem copy) + { + Field[] fields = getFields(); + Field[] fieldsCopy = copy.getFields(); + for (int i = 0; i < fields.length-2; i++) { + fields[i].copyTo(dexFile, fieldsCopy[i]); + } + //we need to do this in reverse order, so when the tries are copied, + //the catchHandler copies will already exist + catchHandlers.copyTo(dexFile, copy.catchHandlers); + tries.copyTo(dexFile, copy.tries); + } + public Field[] getFields() { return fields; } - public class TryItem extends CompositeField { + public static class TryItem extends CompositeField { private final Field[] fields; - public TryItem() { + private final IntegerField startAddr; + private final ShortIntegerField insnCount; + private final EncodedCatchHandlerReference encodedCatchHandlerReference; + + public TryItem(EncodedCatchHandlerList encodedCatchHandlerList) { fields = new Field[] { - new IntegerField(), - new ShortIntegerField(), - new ShortIntegerField() + startAddr = new IntegerField(), + insnCount = new ShortIntegerField(), + encodedCatchHandlerReference = new EncodedCatchHandlerReference(encodedCatchHandlerList) }; } + public TryItem(int startAddr, int insnCount, EncodedCatchHandler encodedCatchHandler) { + fields = new Field[] { + this.startAddr = new IntegerField(startAddr), + this.insnCount = new ShortIntegerField(insnCount), + this.encodedCatchHandlerReference = new EncodedCatchHandlerReference(encodedCatchHandler) + }; + } protected Field[] getFields() { return fields; } } - class EncodedCatchHandlerList extends CompositeField { + public static class EncodedCatchHandlerReference extends ShortIntegerField { + private final EncodedCatchHandlerList encodedCatchHandlerList; + private EncodedCatchHandler encodedCatchHandler; + + public EncodedCatchHandlerReference(EncodedCatchHandlerList encodedCatchHandlerList) { + this.encodedCatchHandlerList = encodedCatchHandlerList; + } + + public EncodedCatchHandlerReference(EncodedCatchHandler encodedCatchHandler) { + this.encodedCatchHandlerList = null; + this.encodedCatchHandler = encodedCatchHandler; + } + + public EncodedCatchHandlerList getEncodedCatchHandlerList() { + return encodedCatchHandlerList; + } + + private void setReference(EncodedCatchHandler encodedCatchHandler) { + this.encodedCatchHandler = encodedCatchHandler; + } + + public void copyTo(DexFile dexFile, CachedIntegerValueField _copy) { + + EncodedCatchHandlerReference copy = (EncodedCatchHandlerReference)_copy; + EncodedCatchHandler copiedItem = copy.getEncodedCatchHandlerList().getByOffset( + encodedCatchHandler.getOffsetInList()); + copy.setReference(copiedItem); + } + + + public void writeTo(Output out) { + cacheValue(encodedCatchHandler.getOffsetInList()); + + super.writeTo(out); + } + + public void readFrom(Input in) { + super.readFrom(in); + + encodedCatchHandler = encodedCatchHandlerList.getByOffset(getCachedValue()); + } + + public int place(int offset) { + cacheValue(encodedCatchHandler.getOffsetInList()); + return super.place(offset); + } + } + + public class EncodedCatchHandlerList extends CompositeField { private boolean fieldPresent = false; + protected HashMap itemsByOffset = + new HashMap(); private final DexFile dexFile; + public EncodedCatchHandler getByOffset(int offset) { + EncodedCatchHandler encodedCatchHandler = itemsByOffset.get(offset); + if (encodedCatchHandler == null) { + encodedCatchHandler = new EncodedCatchHandler(dexFile, offset); + itemsByOffset.put(offset, encodedCatchHandler); + } + return encodedCatchHandler; + } + public EncodedCatchHandlerList(DexFile dexFile) { this.dexFile = dexFile; } + private final ListSizeField sizeField; + private final FieldListField listField; + private final Field[] fields = new Field[] { - new ListSizeField(catchHandlerList, new Leb128Field()), - new FieldListField(catchHandlerList) { + sizeField = new ListSizeField(catchHandlerList, new Leb128Field()), + listField = new FieldListField(catchHandlerList) { protected EncodedCatchHandler make() { - return new EncodedCatchHandler(dexFile); + return new EncodedCatchHandler(dexFile, 0); } - } + + public void readFrom(Input in) { + int currentOffset = sizeField.place(0); + + for (int i = 0; i < list.size(); i++) { + EncodedCatchHandler field = list.get(i); + + if (field == null) { + field = itemsByOffset.get(currentOffset); + if (field == null) { + field = new EncodedCatchHandler(dexFile, currentOffset); + } + list.set(i, field); + } + int savedOffset = in.getCursor(); + field.readFrom(in); + currentOffset += in.getCursor() - savedOffset; + } + } + } }; public void readFrom(Input in) { @@ -164,6 +281,9 @@ public class CodeItem extends OffsettedItem { } public int place(int offset) { + for (EncodedCatchHandler encodedCatchHandler: listField.list) { + encodedCatchHandler.setBaseOffset(offset); + } if (tryItems.size() > 0) { fieldPresent = true; return super.place(offset); @@ -179,17 +299,31 @@ public class CodeItem extends OffsettedItem { public void copyTo(DexFile dexFile, EncodedCatchHandlerList copy) { super.copyTo(dexFile, copy); copy.fieldPresent = fieldPresent; + copy.itemsByOffset.clear(); + for (EncodedCatchHandler encodedCatchHandler: copy.listField.list) { + copy.itemsByOffset.put(encodedCatchHandler.offset, encodedCatchHandler); + } } } - public class EncodedCatchHandler extends CompositeField { + public static class EncodedCatchHandler extends CompositeField { public final Field[] fields; - private ArrayList list = new ArrayList(); - private boolean hasCatchAll = false; + private ArrayList list; + boolean hasCatchAll = false; + private int baseOffset = 0; - public EncodedCatchHandler(final DexFile dexFile) { + private final ListSizeField size; + private final FieldListField handlers; + private final Leb128Field catchAllAddress; + + private int offset; + + public EncodedCatchHandler(final DexFile dexFile, int offset) { + this.offset = offset; + + list = new ArrayList(); fields = new Field[] { - new ListSizeField(list, new SignedLeb128Field() { + size = new ListSizeField(list, new SignedLeb128Field() { public void readFrom(Input in) { super.readFrom(in); hasCatchAll = (getCachedValue() <= 0); @@ -199,12 +333,12 @@ public class CodeItem extends OffsettedItem { super.cacheValue(value * (hasCatchAll?-1:1)); }}) , - new FieldListField(list) { + handlers = new FieldListField(list) { protected EncodedTypeAddrPair make() { return new EncodedTypeAddrPair(dexFile); } }, - new Leb128Field() { + catchAllAddress = new Leb128Field() { public void readFrom(Input in) { if (hasCatchAll) { super.readFrom(in); @@ -227,17 +361,41 @@ public class CodeItem extends OffsettedItem { }; } + public EncodedCatchHandler(final DexFile dexFile, List handlers, int catchAllHandler) { + this(dexFile, 0); + + list.addAll(handlers); + if (catchAllHandler >= 0) { + hasCatchAll = true; + catchAllAddress.cacheValue(catchAllHandler); + } + } + protected Field[] getFields() { return fields; } + public int getOffsetInList() { + return offset-baseOffset; + } + + public void setBaseOffset(int baseOffset) { + this.baseOffset = baseOffset; + } + public void copyTo(DexFile dexFile, EncodedCatchHandler copy) { super.copyTo(dexFile, copy); copy.hasCatchAll = hasCatchAll; + copy.offset = offset; + } + + public int place(int offset) { + this.offset = offset; + return super.place(offset); } } - public class EncodedTypeAddrPair extends CompositeField { + public static class EncodedTypeAddrPair extends CompositeField { public final Field[] fields; public EncodedTypeAddrPair(DexFile dexFile) { @@ -247,6 +405,13 @@ public class CodeItem extends OffsettedItem { }; } + public EncodedTypeAddrPair(DexFile dexFile, TypeIdItem type, int handlerOffset) { + fields = new Field[] { + new IndexedItemReference(dexFile, type, new Leb128Field()), + new Leb128Field(handlerOffset) + }; + } + protected Field[] getFields() { return fields; } diff --git a/src/main/java/org/JesusFreke/dexlib/FieldListField.java b/src/main/java/org/JesusFreke/dexlib/FieldListField.java index fc95a962..e6cf9489 100644 --- a/src/main/java/org/JesusFreke/dexlib/FieldListField.java +++ b/src/main/java/org/JesusFreke/dexlib/FieldListField.java @@ -34,7 +34,7 @@ import org.JesusFreke.dexlib.util.Input; import java.util.ArrayList; public abstract class FieldListField implements Field> { - private final ArrayList list; + final ArrayList list; public FieldListField(ArrayList list) { this.list = list; diff --git a/src/main/java/org/JesusFreke/dexlib/util/AccessFlags.java b/src/main/java/org/JesusFreke/dexlib/util/AccessFlags.java index e7b17166..134c1af3 100644 --- a/src/main/java/org/JesusFreke/dexlib/util/AccessFlags.java +++ b/src/main/java/org/JesusFreke/dexlib/util/AccessFlags.java @@ -34,8 +34,23 @@ public class AccessFlags { public static final int PUBLIC = 0x01; public static final int PRIVATE = 0x02; + public static final int PROTECTED = 0x04; public static final int STATIC = 0x08; - public static final int CONSTRUCTOR = 0x10000; + public static final int FINAL = 0x10; + public static final int SYNCHRONIZED = 0x20; + public static final int VOLATILE = 0x40; + public static final int BRIDGE = 0x40; + public static final int TRANSIENT = 0x80; + public static final int VARARGS = 0x80; + public static final int NATIVE = 0x100; + public static final int INTERFACE = 0x200; + public static final int ABSTRACT = 0x400; + public static final int STRICTFP = 0x800; + public static final int SYNTHETIC = 0x1000; + public static final int ANNOTATION = 0x2000; + public static final int ENUM = 0x4000; + public static final int CONSTRUCTOR = 0x10000; + public static final int DECLARED_SYNCHRONIZED = 0x20000; private static HashMap accessFlagValues; @@ -44,7 +59,21 @@ public class AccessFlags accessFlagValues.put("public", PUBLIC); accessFlagValues.put("private", PRIVATE); accessFlagValues.put("static", STATIC); + accessFlagValues.put("final", FINAL); + accessFlagValues.put("synchronized", SYNCHRONIZED); + accessFlagValues.put("volatile", VOLATILE); + accessFlagValues.put("bridge", BRIDGE); + accessFlagValues.put("transient", TRANSIENT); + accessFlagValues.put("varargs", VARARGS); + accessFlagValues.put("native", NATIVE); + accessFlagValues.put("interface", INTERFACE); + accessFlagValues.put("abstract", ABSTRACT); + accessFlagValues.put("strictfp", STRICTFP); + accessFlagValues.put("synthetic", SYNTHETIC); + accessFlagValues.put("annotation", ANNOTATION); + accessFlagValues.put("enum", ENUM); accessFlagValues.put("constructor", CONSTRUCTOR); + accessFlagValues.put("declared-synchronized", DECLARED_SYNCHRONIZED); } public static int getValueForAccessFlag(String accessFlag) { diff --git a/src/main/java/org/JesusFreke/dexlib/util/Pair.java b/src/main/java/org/JesusFreke/dexlib/util/Pair.java new file mode 100644 index 00000000..149119e1 --- /dev/null +++ b/src/main/java/org/JesusFreke/dexlib/util/Pair.java @@ -0,0 +1,40 @@ +/* + * [The "BSD licence"] + * Copyright (c) 2009 Ben Gruver + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.JesusFreke.dexlib.util; + +public class Pair { + public final A first; + public final B second; + + public Pair(A first, B second) { + this.first = first; + this.second = second; + } +} + diff --git a/src/main/java/org/JesusFreke/dexlib/util/TryListBuilder.java b/src/main/java/org/JesusFreke/dexlib/util/TryListBuilder.java new file mode 100644 index 00000000..b8442994 --- /dev/null +++ b/src/main/java/org/JesusFreke/dexlib/util/TryListBuilder.java @@ -0,0 +1,331 @@ +/* + * [The "BSD licence"] + * Copyright (c) 2009 Ben Gruver + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.JesusFreke.dexlib.util; + +import org.JesusFreke.dexlib.TypeIdItem; +import org.JesusFreke.dexlib.CodeItem; +import org.JesusFreke.dexlib.DexFile; + +import java.util.*; + +public class TryListBuilder +{ + //TODO: need to thoroughly test this + + private TryRange firstTryRange = new TryRange(0,0); + private TryRange lastTryRange = new TryRange(0,0); + + public TryListBuilder() { + firstTryRange.next = lastTryRange; + lastTryRange.previous = firstTryRange; + } + + private class TryRange + { + public TryRange previous = null; + public TryRange next = null; + + public int startAddress; + public int endAddress; + public LinkedList handlers; + + public int catchAllHandlerAddress; + + public TryRange(int startAddress, int endAddress) { + this.startAddress = startAddress; + this.endAddress = endAddress; + this.handlers = new LinkedList(); + this.previous = null; + this.next = null; + catchAllHandlerAddress = -1; + } + + public void append(TryRange tryRange) { + /*we use a dummy last item, so this.next will always + have a value*/ + this.next.previous = tryRange; + tryRange.next = this.next; + + this.next = tryRange; + tryRange.previous = this; + } + + public void prepend(TryRange tryRange){ + /*we use a dummy first item, so this.previous will always + have a value*/ + this.previous.next = tryRange; + tryRange.previous = this.previous; + + this.previous = tryRange; + tryRange.next = this; + } + + /** + * This splits the current range into two ranges at the given + * address. The existing range will be shortened to the first + * half, and a new range will be created and returned for the + * 2nd half. + * @param address The address to split at + * @return The 2nd half of the + */ + public TryRange split(int address) { + //this is a private class, so address is assumed + //to be valid + + TryRange tryRange = new TryRange(address, endAddress); + tryRange.catchAllHandlerAddress = this.catchAllHandlerAddress; + append(tryRange); + + this.endAddress = address; + + return tryRange; + } + + public void appendHandler(Handler handler) { + handlers.addLast(handler); + } + + public void prependHandler(Handler handler) { + handlers.addFirst(handler); + } + } + + private class Handler + { + public final TypeIdItem type; + public final int handlerAddress; + + public Handler(TypeIdItem type, int handlerAddress) { + this.type = type; + this.handlerAddress = handlerAddress; + } + } + + /*public TryListBuilder(CodeItem item) + { + } */ + + public Pair, List> encodeTries(DexFile dexFile) { + if (firstTryRange.next == lastTryRange) { + return new Pair, List>(null, null); + } + + ArrayList tries = new ArrayList(); + ArrayList handlers = new ArrayList(); + + HashMap handlerDict = + new HashMap(); + + TryRange tryRange = firstTryRange.next; + + while (tryRange != lastTryRange) { + ArrayList encodedTypeAddrPairs = + new ArrayList(); + + for (Handler handler: tryRange.handlers) { + CodeItem.EncodedTypeAddrPair encodedTypeAddrPair = new CodeItem.EncodedTypeAddrPair( + dexFile, + handler.type, + handler.handlerAddress); + encodedTypeAddrPairs.add(encodedTypeAddrPair); + } + + + CodeItem.EncodedCatchHandler encodedCatchHandler = new CodeItem.EncodedCatchHandler( + dexFile, + encodedTypeAddrPairs, + tryRange.catchAllHandlerAddress); + CodeItem.EncodedCatchHandler internedEncodedCatchHandler = handlerDict.get(encodedCatchHandler); + if (internedEncodedCatchHandler == null) { + handlerDict.put(encodedCatchHandler, encodedCatchHandler); + handlers.add(encodedCatchHandler); + } else { + encodedCatchHandler = internedEncodedCatchHandler; + } + + CodeItem.TryItem tryItem = new CodeItem.TryItem( + tryRange.startAddress, + tryRange.endAddress - tryRange.startAddress, + encodedCatchHandler); + tries.add(tryItem); + + tryRange = tryRange.next; + } + + return new Pair, List>(tries, handlers); + } + + public void addCatchAllHandler(int startAddress, int endAddress, int handlerAddress) { + TryRange startRange = null; + TryRange endRange = null; + + Pair ranges = getBoundingRanges(startAddress, endAddress); + startRange = ranges.first; + endRange = ranges.second; + + int previousEnd = startAddress; + TryRange tryRange = startRange; + + /*Now we have the start and end ranges that exactly match the start and end + of the range being added. We need to iterate over all the ranges from the start + to end range inclusively, and append the handler to the end of each range's handler + list. We also need to create a new range for any "holes" in the existing ranges*/ + do + { + //is there a hole? If so, add a new range to fill the hole + if (tryRange.startAddress > previousEnd) { + TryRange newRange = new TryRange(previousEnd, tryRange.startAddress); + tryRange.prepend(newRange); + tryRange = newRange; + } + + tryRange.catchAllHandlerAddress = handlerAddress; + previousEnd = tryRange.endAddress; + tryRange = tryRange.next; + } while (tryRange.previous != endRange); + } + + public Pair getBoundingRanges(int startAddress, int endAddress) { + TryRange startRange = null; + TryRange endRange = null; + + TryRange tryRange = firstTryRange.next; + while (tryRange != lastTryRange) { + if (tryRange.startAddress == startAddress) { + //|-----| + //^------ + /*Bam. We hit the start of the range right on the head*/ + startRange = tryRange; + break; + } else if (tryRange.startAddress < startAddress && tryRange.endAddress > startAddress) { + //|-----| + // ^---- + /*Almost. The start of the range being added is in the middle + of an existing try range. We need to split the existing range + at the start address of the range being added*/ + startRange = tryRange.split(startAddress); + break; + }else if (tryRange.startAddress > startAddress) { + // |-----| + //^--------- + /*Oops, too far! We've passed the start of the range being added. + We need to add a new range just before this one*/ + startRange = new TryRange(startAddress, tryRange.startAddress); + tryRange.prepend(startRange); + break; + } + + tryRange = tryRange.next; + } + + //|-----| + // ^----- + /*Either the list of tries is blank, or all the tries in the list + end before the range being added starts. In either case, we just need + to add a new range at the end of the list*/ + if (startRange == null) { + TryRange newRange = new TryRange(startAddress, endAddress); + lastTryRange.prepend(newRange); + return new Pair(newRange, newRange); + } + + tryRange = startRange; + while (tryRange != lastTryRange) { + if (tryRange.endAddress == endAddress) { + //|-----| + //------^ + /*Bam! We hit the end right on the head.*/ + endRange = tryRange; + break; + } else if (tryRange.startAddress < endAddress && tryRange.endAddress > endAddress) { + //|-----| + //--^ + /*Almost. The range being added ends in the middle of an + existing range. We need to split the existing range + at the end of the range being added.*/ + tryRange.split(endAddress); + endRange = tryRange; + break; + } else if (tryRange.startAddress >= endAddress) { + //|-----| |-----| + //-----------^ + /*Oops, too far! The current range starts after the range being added + ends. We need to create a new range that starts at the end of the + previous range, and ends at the end of the range being added*/ + endRange = new TryRange(tryRange.previous.endAddress, endAddress); + tryRange.prepend(endRange); + break; + } + tryRange = tryRange.next; + } + + //|-----| + //--------^ + /*The last range in the list ended before the end of the range being added. + We need to add a new range that starts at the end of the last range in the + list, and ends at the end of the range being added.*/ + if (endRange == null) { + endRange = new TryRange(lastTryRange.previous.endAddress, endAddress); + lastTryRange.prepend(endRange); + } + + return new Pair(startRange, endRange); + } + + public void addHandler(TypeIdItem type, int startAddress, int endAddress, int handlerAddress) { + TryRange startRange = null; + TryRange endRange = null; + + Pair ranges = getBoundingRanges(startAddress, endAddress); + startRange = ranges.first; + endRange = ranges.second; + Handler handler = new Handler(type, handlerAddress); + + int previousEnd = startAddress; + TryRange tryRange = startRange; + + /*Now we have the start and end ranges that exactly match the start and end + of the range being added. We need to iterate over all the ranges from the start + to end range inclusively, and append the handler to the end of each range's handler + list. We also need to create a new range for any "holes" in the existing ranges*/ + do + { + //is there a hole? If so, add a new range to fill the hole + if (tryRange.startAddress > previousEnd) { + TryRange newRange = new TryRange(previousEnd, tryRange.startAddress); + tryRange.prepend(newRange); + tryRange = newRange; + } + + tryRange.appendHandler(handler); + previousEnd = tryRange.endAddress; + tryRange = tryRange.next; + } while (tryRange.previous != endRange); + } +} diff --git a/src/test/resources/examples/HelloWorld2.smali b/src/test/resources/examples/HelloWorld2.smali index ab1b38a4..447c552b 100644 --- a/src/test/resources/examples/HelloWorld2.smali +++ b/src/test/resources/examples/HelloWorld2.smali @@ -36,6 +36,8 @@ ;5000000000 ;5000000 ;Label12 +;Label13 +;In the exception handler. .method static constructor ()V ;test @@ -330,6 +332,43 @@ SparseSwitch: .end method +.method public testTry()Ljava/lang/String; + .registers 2 + + ;0 + const-string v0, "This shouldn't be displayed!" + + ;2 + + tryStart: + new-instance v1, Ljava/lang/Exception; + + ;4 + + invoke-direct {v1}, java/lang/Exception/()V + + ;7 + + throw v1 + + nop + nop + + ;8 + tryEnd: + + return-object v0 + + ;9 + + .catch Ljava/lang/Exception; from tryStart: to tryEnd: using handler: + + handler: + const-string v0, "In the exception handler." + return-object v0 + +.end method + .method public onCreate(Landroid/os/Bundle;)V @@ -675,6 +714,18 @@ SparseSwitch: + ;test try-catch block + invoke-virtual {v4}, org/JesusFreke/HelloWorld2/HelloWorld2/testTry()Ljava/lang/String; + move-result-object v1 + + invoke-virtual {v2, v1}, java/lang/String/concat(Ljava/lang/String;)Ljava/lang/String; + move-result-object v2 + + invoke-virtual {v2, v3}, java/lang/String/concat(Ljava/lang/String;)Ljava/lang/String; + move-result-object v2 + + + check-cast v4, Landroid/app/Activity; invoke-virtual {v0,v2}, android/widget/TextView/setText(Ljava/lang/CharSequence;)V