diff options
author | Igor Murashkin <iam@google.com> | 2015-04-23 11:31:08 -0700 |
---|---|---|
committer | Igor Murashkin <iam@google.com> | 2015-04-23 11:31:08 -0700 |
commit | 07990426302adcf94f2d9595b8066f1257219799 (patch) | |
tree | a55d8ef83f6b2b54ee45de8526ec66e230259cb9 | |
parent | 15284127c9ed241f1de20fdd8cdb24cb12cd2119 (diff) | |
parent | 17828564bae2c03788e5366b73ca9e259f70ca5d (diff) | |
download | smali-07990426302adcf94f2d9595b8066f1257219799.tar.gz |
Merge remote-tracking branch 'remotes/aosp/upstream-master' into HEAD
* Brings-up smali to 17828564bae2c03788e5366b73ca9e259f70ca5d
which has the experimental new opcodes
83 files changed, 2264 insertions, 429 deletions
@@ -5,3 +5,7 @@ /dexlib2/accessorTestGenerator/build /smali/build /util/build +*.iml +*.ipr +*.iws +.idea @@ -11,7 +11,7 @@ The primary webpage is http://smali.googlecode.com, and the source is also mirro #### Some useful links for getting started with smali -- [Official dex bytecode reference](http://s.android.com/tech/dalvik/dalvik-bytecode.html) +- [Official dex bytecode reference](https://source.android.com/devices/tech/dalvik/dalvik-bytecode.html) - [Registers wiki page](https://code.google.com/p/smali/wiki/Registers) - [Types, Methods and Fields wiki page](https://code.google.com/p/smali/wiki/TypesMethodsAndFields) -- [Official dex format reference](http://s.android.com/tech/dalvik/dex-format.html)
\ No newline at end of file +- [Official dex format reference](https://source.android.com/devices/tech/dalvik/dex-format.html) diff --git a/baksmali/build.gradle b/baksmali/build.gradle index 150eb6fd..4780cd76 100644 --- a/baksmali/build.gradle +++ b/baksmali/build.gradle @@ -29,8 +29,13 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -configurations { - proguard +buildscript { + repositories { + mavenCentral() + } + dependencies { + classpath depends.proguard_gradle + } } dependencies { @@ -41,8 +46,6 @@ dependencies { testCompile depends.junit testCompile project(':smali') - - proguard depends.proguard } processResources.inputs.property('version', version) @@ -59,11 +62,11 @@ task fatJar(type: Jar) { from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } } manifest { - attributes("Main-Class": "org.jf.baksmali.main") + attributes('Main-Class': 'org.jf.baksmali.main') } doLast { - if (!System.getProperty("os.name").toLowerCase().contains("windows")) { + if (!System.getProperty('os.name').toLowerCase().contains('windows')) { ant.symlink(link: file("${destinationDir}/baksmali.jar"), resource: archivePath, overwrite: true) } } @@ -81,22 +84,22 @@ uploadArchives { } } -task proguard(type: JavaExec, dependsOn: fatJar) { +task proguard(type: proguard.gradle.ProGuardTask, dependsOn: fatJar) { def outFile = fatJar.destinationDir.getPath() + '/' + fatJar.baseName + '-' + fatJar.version + '-small' + '.' + fatJar.extension - inputs.file fatJar.archivePath - outputs.file outFile - - classpath = configurations.proguard - main = 'proguard.ProGuard' - args '-injars ' + fatJar.archivePath - args '-outjars ' + outFile - args '-libraryjars ' + System.properties['java.home'] + '/lib/rt.jar' - args '-dontobfuscate' - args '-dontoptimize' - args '-keep public class org.jf.baksmali.main { public static void main(java.lang.String[]); }' - args '-keepclassmembers enum * { public static **[] values(); public static ** valueOf(java.lang.String); }' - args '-dontwarn com.google.common.**' - args '-dontnote com.google.common.**' + + injars fatJar.archivePath + outjars outFile + + libraryjars "${System.properties['java.home']}/lib/rt.jar" + + dontobfuscate + dontoptimize + + keep 'public class org.jf.baksmali.main { public static void main(java.lang.String[]); }' + keepclassmembers 'enum * { public static **[] values(); public static ** valueOf(java.lang.String); }' + + dontwarn 'com.google.common.**' + dontnote 'com.google.common.**' } tasks.getByPath(':release').dependsOn(proguard) diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/InstructionMethodItem.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/InstructionMethodItem.java index 12acd791..b3f9ae17 100644 --- a/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/InstructionMethodItem.java +++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/InstructionMethodItem.java @@ -41,8 +41,6 @@ import org.jf.dexlib2.iface.instruction.*; import org.jf.dexlib2.iface.instruction.formats.Instruction20bc; import org.jf.dexlib2.iface.instruction.formats.Instruction31t; import org.jf.dexlib2.iface.instruction.formats.UnknownInstruction; -import org.jf.dexlib2.iface.reference.FieldReference; -import org.jf.dexlib2.iface.reference.MethodReference; import org.jf.dexlib2.iface.reference.Reference; import org.jf.dexlib2.util.ReferenceUtil; import org.jf.util.ExceptionWithContext; @@ -132,26 +130,37 @@ public class InstructionMethodItem<T extends Instruction> extends MethodItem { } if (instruction instanceof Instruction31t) { - Opcode payloadOpcode; + boolean validPayload = true; + switch (instruction.getOpcode()) { case PACKED_SWITCH: - payloadOpcode = Opcode.PACKED_SWITCH_PAYLOAD; + int baseAddress = methodDef.getPackedSwitchBaseAddress( + this.codeAddress + ((Instruction31t)instruction).getCodeOffset()); + if (baseAddress == -1) { + validPayload = false; + } break; case SPARSE_SWITCH: - payloadOpcode = Opcode.SPARSE_SWITCH_PAYLOAD; + baseAddress = methodDef.getSparseSwitchBaseAddress( + this.codeAddress + ((Instruction31t)instruction).getCodeOffset()); + if (baseAddress == -1) { + validPayload = false; + } break; case FILL_ARRAY_DATA: - payloadOpcode = Opcode.ARRAY_PAYLOAD; + try { + methodDef.findPayloadOffset(this.codeAddress + ((Instruction31t)instruction).getCodeOffset(), + Opcode.ARRAY_PAYLOAD); + } catch (InvalidSwitchPayload ex) { + validPayload = false; + } break; default: throw new ExceptionWithContext("Invalid 31t opcode: %s", instruction.getOpcode()); } - try { - methodDef.findSwitchPayload(this.codeAddress + ((Instruction31t)instruction).getCodeOffset(), - payloadOpcode); - } catch (InvalidSwitchPayload ex) { - writer.write("#invalid payload reference"); + if (!validPayload) { + writer.write("#invalid payload reference\n"); commentOutInstruction = true; } } @@ -300,6 +309,11 @@ public class InstructionMethodItem<T extends Instruction> extends MethodItem { writer.write(", "); writeThirdRegister(writer); break; + case Format25x: + writeOpcode(writer); + writer.write(' '); + writeInvoke25xRegisters(writer); // vC, {vD, ...} + break; case Format35c: writeOpcode(writer); writer.write(' '); @@ -425,6 +439,43 @@ public class InstructionMethodItem<T extends Instruction> extends MethodItem { writer.write('}'); } + protected void writeInvoke25xRegisters(IndentingWriter writer) throws IOException { + OneFixedFourParameterRegisterInstruction instruction = + (OneFixedFourParameterRegisterInstruction)this.instruction; + final int parameterRegCount = instruction.getParameterRegisterCount(); + + writeRegister(writer, instruction.getRegisterFixedC()); // fixed register always present + + writer.write(", {"); + switch (parameterRegCount) { + case 1: + writeRegister(writer, instruction.getRegisterParameterD()); + break; + case 2: + writeRegister(writer, instruction.getRegisterParameterD()); + writer.write(", "); + writeRegister(writer, instruction.getRegisterParameterE()); + break; + case 3: + writeRegister(writer, instruction.getRegisterParameterD()); + writer.write(", "); + writeRegister(writer, instruction.getRegisterParameterE()); + writer.write(", "); + writeRegister(writer, instruction.getRegisterParameterF()); + break; + case 4: + writeRegister(writer, instruction.getRegisterParameterD()); + writer.write(", "); + writeRegister(writer, instruction.getRegisterParameterE()); + writer.write(", "); + writeRegister(writer, instruction.getRegisterParameterF()); + writer.write(", "); + writeRegister(writer, instruction.getRegisterParameterG()); + break; + } + writer.write('}'); + } + protected void writeInvokeRangeRegisters(IndentingWriter writer) throws IOException { RegisterRangeInstruction instruction = (RegisterRangeInstruction)this.instruction; diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/PackedSwitchMethodItem.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/PackedSwitchMethodItem.java index f0dd656b..30edfcd4 100644 --- a/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/PackedSwitchMethodItem.java +++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/PackedSwitchMethodItem.java @@ -28,6 +28,7 @@ package org.jf.baksmali.Adaptors.Format; +import org.jf.baksmali.Adaptors.CommentingIndentingWriter; import org.jf.baksmali.Adaptors.LabelMethodItem; import org.jf.baksmali.Adaptors.MethodDefinition; import org.jf.dexlib2.iface.instruction.SwitchElement; @@ -43,6 +44,9 @@ public class PackedSwitchMethodItem extends InstructionMethodItem<PackedSwitchPa private final List<PackedSwitchTarget> targets; private final int firstKey; + // Whether this sparse switch instruction should be commented out because it is never referenced + private boolean commentedOut; + public PackedSwitchMethodItem(MethodDefinition methodDef, int codeAddress, PackedSwitchPayload instruction) { super(methodDef, codeAddress, instruction); @@ -51,7 +55,6 @@ public class PackedSwitchMethodItem extends InstructionMethodItem<PackedSwitchPa targets = new ArrayList<PackedSwitchTarget>(); boolean first = true; - //TODO: does dalvik allow switc payloads with no cases? int firstKey = 0; if (baseCodeAddress >= 0) { for (SwitchElement switchElement: instruction.getSwitchElements()) { @@ -65,6 +68,7 @@ public class PackedSwitchMethodItem extends InstructionMethodItem<PackedSwitchPa targets.add(new PackedSwitchLabelTarget(label)); } } else { + commentedOut = true; for (SwitchElement switchElement: instruction.getSwitchElements()) { if (first) { firstKey = switchElement.getKey(); @@ -78,6 +82,9 @@ public class PackedSwitchMethodItem extends InstructionMethodItem<PackedSwitchPa @Override public boolean writeTo(IndentingWriter writer) throws IOException { + if (commentedOut) { + writer = new CommentingIndentingWriter(writer); + } writer.write(".packed-switch "); IntegerRenderer.writeTo(writer, firstKey); writer.indent(4); diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/SparseSwitchMethodItem.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/SparseSwitchMethodItem.java index 0e01f41b..68d7e92b 100644 --- a/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/SparseSwitchMethodItem.java +++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/SparseSwitchMethodItem.java @@ -28,6 +28,7 @@ package org.jf.baksmali.Adaptors.Format; +import org.jf.baksmali.Adaptors.CommentingIndentingWriter; import org.jf.baksmali.Adaptors.LabelMethodItem; import org.jf.baksmali.Adaptors.MethodDefinition; import org.jf.dexlib2.iface.instruction.SwitchElement; @@ -42,6 +43,9 @@ import java.util.List; public class SparseSwitchMethodItem extends InstructionMethodItem<SparseSwitchPayload> { private final List<SparseSwitchTarget> targets; + // Whether this sparse switch instruction should be commented out because it is never referenced + private boolean commentedOut; + public SparseSwitchMethodItem(MethodDefinition methodDef, int codeAddress, SparseSwitchPayload instruction) { super(methodDef, codeAddress, instruction); @@ -56,6 +60,7 @@ public class SparseSwitchMethodItem extends InstructionMethodItem<SparseSwitchPa targets.add(new SparseSwitchLabelTarget(switchElement.getKey(), label)); } } else { + commentedOut = true; //if we couldn't determine a base address, just use relative offsets rather than labels for (SwitchElement switchElement: instruction.getSwitchElements()) { targets.add(new SparseSwitchOffsetTarget(switchElement.getKey(), switchElement.getOffset())); @@ -65,6 +70,10 @@ public class SparseSwitchMethodItem extends InstructionMethodItem<SparseSwitchPa @Override public boolean writeTo(IndentingWriter writer) throws IOException { + if (commentedOut) { + writer = new CommentingIndentingWriter(writer); + } + writer.write(".sparse-switch\n"); writer.indent(4); for (SparseSwitchTarget target: targets) { diff --git a/baksmali/src/main/java/org/jf/baksmali/Adaptors/MethodDefinition.java b/baksmali/src/main/java/org/jf/baksmali/Adaptors/MethodDefinition.java index eb5caa67..4081a75c 100644 --- a/baksmali/src/main/java/org/jf/baksmali/Adaptors/MethodDefinition.java +++ b/baksmali/src/main/java/org/jf/baksmali/Adaptors/MethodDefinition.java @@ -29,6 +29,7 @@ package org.jf.baksmali.Adaptors; import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; import org.jf.baksmali.Adaptors.Debug.DebugMethodItem; import org.jf.baksmali.Adaptors.Format.InstructionMethodItemFactory; import org.jf.baksmali.baksmaliOptions; @@ -45,11 +46,14 @@ import org.jf.dexlib2.iface.debug.DebugItem; import org.jf.dexlib2.iface.instruction.Instruction; import org.jf.dexlib2.iface.instruction.OffsetInstruction; import org.jf.dexlib2.iface.instruction.ReferenceInstruction; +import org.jf.dexlib2.iface.instruction.formats.Instruction31t; import org.jf.dexlib2.iface.reference.MethodReference; +import org.jf.dexlib2.immutable.instruction.ImmutableInstruction31t; import org.jf.dexlib2.util.InstructionOffsetMap; import org.jf.dexlib2.util.InstructionOffsetMap.InvalidInstructionOffset; import org.jf.dexlib2.util.ReferenceUtil; import org.jf.dexlib2.util.SyntheticAccessorResolver; +import org.jf.dexlib2.util.SyntheticAccessorResolver.AccessedMember; import org.jf.dexlib2.util.TypeUtils; import org.jf.util.ExceptionWithContext; import org.jf.util.IndentingWriter; @@ -65,6 +69,8 @@ public class MethodDefinition { @Nonnull public final Method method; @Nonnull public final MethodImplementation methodImpl; @Nonnull public final ImmutableList<Instruction> instructions; + @Nonnull public final List<Instruction> effectiveInstructions; + @Nonnull public final ImmutableList<MethodParameter> methodParameters; public RegisterFormatter registerFormatter; @@ -86,10 +92,15 @@ public class MethodDefinition { instructions = ImmutableList.copyOf(methodImpl.getInstructions()); methodParameters = ImmutableList.copyOf(method.getParameters()); + effectiveInstructions = Lists.newArrayList(instructions); + packedSwitchMap = new SparseIntArray(0); sparseSwitchMap = new SparseIntArray(0); instructionOffsetMap = new InstructionOffsetMap(instructions); + int endOffset = instructionOffsetMap.getInstructionCodeOffset(instructions.size()-1) + + instructions.get(instructions.size()-1).getCodeUnits(); + for (int i=0; i<instructions.size(); i++) { Instruction instruction = instructions.get(i); @@ -99,11 +110,20 @@ public class MethodDefinition { int codeOffset = instructionOffsetMap.getInstructionCodeOffset(i); int targetOffset = codeOffset + ((OffsetInstruction)instruction).getCodeOffset(); try { - targetOffset = findSwitchPayload(targetOffset, Opcode.PACKED_SWITCH_PAYLOAD); + targetOffset = findPayloadOffset(targetOffset, Opcode.PACKED_SWITCH_PAYLOAD); } catch (InvalidSwitchPayload ex) { valid = false; } if (valid) { + if (packedSwitchMap.get(targetOffset, -1) != -1) { + Instruction payloadInstruction = + findSwitchPayload(targetOffset, Opcode.PACKED_SWITCH_PAYLOAD); + targetOffset = endOffset; + effectiveInstructions.set(i, new ImmutableInstruction31t(opcode, + ((Instruction31t)instruction).getRegisterA(), targetOffset-codeOffset)); + effectiveInstructions.add(payloadInstruction); + endOffset += payloadInstruction.getCodeUnits(); + } packedSwitchMap.append(targetOffset, codeOffset); } } else if (opcode == Opcode.SPARSE_SWITCH) { @@ -111,18 +131,27 @@ public class MethodDefinition { int codeOffset = instructionOffsetMap.getInstructionCodeOffset(i); int targetOffset = codeOffset + ((OffsetInstruction)instruction).getCodeOffset(); try { - targetOffset = findSwitchPayload(targetOffset, Opcode.SPARSE_SWITCH_PAYLOAD); + targetOffset = findPayloadOffset(targetOffset, Opcode.SPARSE_SWITCH_PAYLOAD); } catch (InvalidSwitchPayload ex) { valid = false; // The offset to the payload instruction was invalid. Nothing to do, except that we won't // add this instruction to the map. } if (valid) { + if (sparseSwitchMap.get(targetOffset, -1) != -1) { + Instruction payloadInstruction = + findSwitchPayload(targetOffset, Opcode.SPARSE_SWITCH_PAYLOAD); + targetOffset = endOffset; + effectiveInstructions.set(i, new ImmutableInstruction31t(opcode, + ((Instruction31t)instruction).getRegisterA(), targetOffset-codeOffset)); + effectiveInstructions.add(payloadInstruction); + endOffset += payloadInstruction.getCodeUnits(); + } sparseSwitchMap.append(targetOffset, codeOffset); } } } - }catch (Exception ex) { + } catch (Exception ex) { String methodString; try { methodString = ReferenceUtil.getMethodDescriptor(method); @@ -216,7 +245,36 @@ public class MethodDefinition { writer.write(".end method\n"); } - public int findSwitchPayload(int targetOffset, Opcode type) { + public Instruction findSwitchPayload(int targetOffset, Opcode type) { + int targetIndex; + try { + targetIndex = instructionOffsetMap.getInstructionIndexAtCodeOffset(targetOffset); + } catch (InvalidInstructionOffset ex) { + throw new InvalidSwitchPayload(targetOffset); + } + + //TODO: does dalvik let you pad with multiple nops? + //TODO: does dalvik let a switch instruction point to a non-payload instruction? + + Instruction instruction = instructions.get(targetIndex); + if (instruction.getOpcode() != type) { + // maybe it's pointing to a NOP padding instruction. Look at the next instruction + if (instruction.getOpcode() == Opcode.NOP) { + targetIndex += 1; + if (targetIndex < instructions.size()) { + instruction = instructions.get(targetIndex); + if (instruction.getOpcode() == type) { + return instruction; + } + } + } + throw new InvalidSwitchPayload(targetOffset); + } else { + return instruction; + } + } + + public int findPayloadOffset(int targetOffset, Opcode type) { int targetIndex; try { targetIndex = instructionOffsetMap.getInstructionIndexAtCodeOffset(targetOffset); @@ -343,15 +401,16 @@ public class MethodDefinition { private void addInstructionMethodItems(List<MethodItem> methodItems) { int currentCodeAddress = 0; - for (int i=0; i<instructions.size(); i++) { - Instruction instruction = instructions.get(i); + + for (int i=0; i<effectiveInstructions.size(); i++) { + Instruction instruction = effectiveInstructions.get(i); MethodItem methodItem = InstructionMethodItemFactory.makeInstructionFormatMethodItem(this, currentCodeAddress, instruction); methodItems.add(methodItem); - if (i != instructions.size() - 1) { + if (i != effectiveInstructions.size() - 1) { methodItems.add(new BlankMethodItem(currentCodeAddress)); } @@ -386,7 +445,7 @@ public class MethodDefinition { if (methodReference != null && SyntheticAccessorResolver.looksLikeSyntheticAccessor(methodReference.getName())) { - SyntheticAccessorResolver.AccessedMember accessedMember = + AccessedMember accessedMember = classDef.options.syntheticAccessorResolver.getAccessedMember(methodReference); if (accessedMember != null) { methodItems.add(new SyntheticAccessCommentMethodItem(accessedMember, currentCodeAddress)); diff --git a/baksmali/src/main/java/org/jf/baksmali/baksmali.java b/baksmali/src/main/java/org/jf/baksmali/baksmali.java index 765e77ec..47fa406d 100644 --- a/baksmali/src/main/java/org/jf/baksmali/baksmali.java +++ b/baksmali/src/main/java/org/jf/baksmali/baksmali.java @@ -67,7 +67,7 @@ public class baksmali { options.classPath = ClassPath.fromClassPath(options.bootClassPathDirs, Iterables.concat(options.bootClassPathEntries, extraClassPathEntries), dexFile, - options.apiLevel, options.checkPackagePrivateAccess); + options.apiLevel, options.checkPackagePrivateAccess, options.experimental); if (options.customInlineDefinitions != null) { options.inlineResolver = new CustomInlineMethodResolver(options.classPath, diff --git a/baksmali/src/main/java/org/jf/baksmali/baksmaliOptions.java b/baksmali/src/main/java/org/jf/baksmali/baksmaliOptions.java index ada28239..b6cc1571 100644 --- a/baksmali/src/main/java/org/jf/baksmali/baksmaliOptions.java +++ b/baksmali/src/main/java/org/jf/baksmali/baksmaliOptions.java @@ -71,6 +71,7 @@ public class baksmaliOptions { public boolean noAccessorComments = false; public boolean allowOdex = false; public boolean deodex = false; + public boolean experimental = false; public boolean ignoreErrors = false; public boolean checkPackagePrivateAccess = false; public boolean useImplicitReferences = false; diff --git a/baksmali/src/main/java/org/jf/baksmali/dump.java b/baksmali/src/main/java/org/jf/baksmali/dump.java index bd040e6d..1ef7df0e 100644 --- a/baksmali/src/main/java/org/jf/baksmali/dump.java +++ b/baksmali/src/main/java/org/jf/baksmali/dump.java @@ -40,7 +40,7 @@ import java.io.IOException; import java.io.Writer; public class dump { - public static void dump(DexBackedDexFile dexFile, String dumpFileName, int apiLevel) throws IOException { + public static void dump(DexBackedDexFile dexFile, String dumpFileName, int apiLevel, boolean experimental) throws IOException { if (dumpFileName != null) { Writer writer = null; @@ -52,7 +52,7 @@ public class dump { consoleWidth = 120; } - RawDexFile rawDexFile = new RawDexFile(new Opcodes(apiLevel), dexFile); + RawDexFile rawDexFile = new RawDexFile(new Opcodes(apiLevel, experimental), dexFile); DexAnnotator annotator = new DexAnnotator(rawDexFile, consoleWidth); annotator.writeAnnotations(writer); } catch (IOException ex) { diff --git a/baksmali/src/main/java/org/jf/baksmali/main.java b/baksmali/src/main/java/org/jf/baksmali/main.java index 28c42030..71598fa6 100644 --- a/baksmali/src/main/java/org/jf/baksmali/main.java +++ b/baksmali/src/main/java/org/jf/baksmali/main.java @@ -192,6 +192,9 @@ public class main { case 'x': options.deodex = true; break; + case 'X': + options.experimental = true; + break; case 'm': options.noAccessorComments = true; break; @@ -253,7 +256,8 @@ public class main { } //Read in and parse the dex file - DexBackedDexFile dexFile = DexFileFactory.loadDexFile(dexFileFile, options.dexEntry, options.apiLevel); + DexBackedDexFile dexFile = DexFileFactory.loadDexFile(dexFileFile, options.dexEntry, + options.apiLevel, options.experimental); if (dexFile.isOdexFile()) { if (!options.deodex) { @@ -270,13 +274,15 @@ public class main { if (dexFile instanceof DexBackedOdexFile) { options.bootClassPathEntries = ((DexBackedOdexFile)dexFile).getDependencies(); } else { - options.bootClassPathEntries = getDefaultBootClassPathForApi(options.apiLevel); + options.bootClassPathEntries = getDefaultBootClassPathForApi(options.apiLevel, + options.experimental); } } if (options.customInlineDefinitions == null && dexFile instanceof DexBackedOdexFile) { options.inlineResolver = - InlineMethodResolver.createInlineMethodResolver(((DexBackedOdexFile)dexFile).getOdexVersion()); + InlineMethodResolver.createInlineMethodResolver( + ((DexBackedOdexFile)dexFile).getOdexVersion()); } boolean errorOccurred = false; @@ -288,7 +294,7 @@ public class main { if (dumpFileName == null) { dumpFileName = commandLine.getOptionValue(inputDexFileName + ".dump"); } - dump.dump(dexFile, dumpFileName, options.apiLevel); + dump.dump(dexFile, dumpFileName, options.apiLevel, options.experimental); } if (errorOccurred) { @@ -352,6 +358,10 @@ public class main { "odex file") .create("x"); + Option experimentalOption = OptionBuilder.withLongOpt("experimental") + .withDescription("enable experimental opcodes to be disassembled, even if they aren't necessarily supported in the Android runtime yet") + .create("X"); + Option useLocalsOption = OptionBuilder.withLongOpt("use-locals") .withDescription("output the .locals directive with the number of non-parameter registers, rather" + " than the .register directive with the total number of register") @@ -469,6 +479,7 @@ public class main { basicOptions.addOption(outputDirOption); basicOptions.addOption(noParameterRegistersOption); basicOptions.addOption(deodexerantOption); + basicOptions.addOption(experimentalOption); basicOptions.addOption(useLocalsOption); basicOptions.addOption(sequentialLabelsOption); basicOptions.addOption(noDebugInfoOption); @@ -496,9 +507,9 @@ public class main { options.addOption((Option)option); } } - + @Nonnull - private static List<String> getDefaultBootClassPathForApi(int apiLevel) { + private static List<String> getDefaultBootClassPathForApi(int apiLevel, boolean experimental) { if (apiLevel < 9) { return Lists.newArrayList( "/system/framework/core.jar", diff --git a/baksmali/src/test/java/org/jf/baksmali/AnalysisTest.java b/baksmali/src/test/java/org/jf/baksmali/AnalysisTest.java index bf353e83..725032af 100644 --- a/baksmali/src/test/java/org/jf/baksmali/AnalysisTest.java +++ b/baksmali/src/test/java/org/jf/baksmali/AnalysisTest.java @@ -85,7 +85,7 @@ public class AnalysisTest { public void runTest(String test, boolean registerInfo) throws IOException, URISyntaxException { String dexFilePath = String.format("%s%sclasses.dex", test, File.separatorChar); - DexFile dexFile = DexFileFactory.loadDexFile(findResource(dexFilePath), 15); + DexFile dexFile = DexFileFactory.loadDexFile(findResource(dexFilePath), 15, false); baksmaliOptions options = new baksmaliOptions(); if (registerInfo) { diff --git a/baksmali/src/test/java/org/jf/baksmali/BaksmaliTestUtils.java b/baksmali/src/test/java/org/jf/baksmali/BaksmaliTestUtils.java new file mode 100644 index 00000000..1c570b6c --- /dev/null +++ b/baksmali/src/test/java/org/jf/baksmali/BaksmaliTestUtils.java @@ -0,0 +1,119 @@ +/* + * Copyright 2015, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "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 COPYRIGHT + * OWNER OR CONTRIBUTORS 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.jf.baksmali; + +import com.google.common.io.ByteStreams; +import junit.framework.Assert; + +import org.antlr.runtime.RecognitionException; +import org.jf.baksmali.Adaptors.ClassDefinition; +import org.jf.dexlib2.iface.ClassDef; +import org.jf.smali.SmaliTestUtils; +import org.jf.util.IndentingWriter; +import org.jf.util.TextUtils; + +import javax.annotation.Nonnull; +import java.io.IOException; +import java.io.InputStream; +import java.io.StringWriter; + +public class BaksmaliTestUtils { + public static void assertSmaliCompiledEquals(String source, String expected, + baksmaliOptions options, boolean stripComments) throws IOException, + RecognitionException { + ClassDef classDef = SmaliTestUtils.compileSmali(source, options.apiLevel, + options.experimental); + + // Remove unnecessary whitespace and optionally strip all comments from smali file + String normalizedActual = getNormalizedSmali(classDef, options, stripComments); + String normalizedExpected = normalizeSmali(expected, stripComments); + + // Assert that normalized strings are now equal + Assert.assertEquals(normalizedExpected, normalizedActual); + } + + public static void assertSmaliCompiledEquals(String source, String expected, + baksmaliOptions options) throws IOException, RecognitionException { + assertSmaliCompiledEquals(source, expected, options, false); + } + + public static void assertSmaliCompiledEquals(String source, String expected) + throws IOException, RecognitionException { + baksmaliOptions options = new baksmaliOptions(); + assertSmaliCompiledEquals(source, expected, options); + } + + @Nonnull + public static String normalizeSmali(@Nonnull String smaliText, boolean stripComments) { + if (stripComments) { + smaliText = TextUtils.stripComments(smaliText); + } + return TextUtils.normalizeWhitespace(smaliText); + } + + @Nonnull + public static String getNormalizedSmali(@Nonnull ClassDef classDef, @Nonnull baksmaliOptions options, + boolean stripComments) + throws IOException { + StringWriter stringWriter = new StringWriter(); + IndentingWriter writer = new IndentingWriter(stringWriter); + ClassDefinition classDefinition = new ClassDefinition(options, classDef); + classDefinition.writeTo(writer); + writer.close(); + return normalizeSmali(stringWriter.toString(), stripComments); + } + + @Nonnull + public static byte[] readResourceBytesFully(@Nonnull String fileName) throws IOException { + InputStream smaliStream = RoundtripTest.class.getClassLoader(). + getResourceAsStream(fileName); + if (smaliStream == null) { + org.junit.Assert.fail("Could not load " + fileName); + } + + return ByteStreams.toByteArray(smaliStream); + } + + @Nonnull + public static String readResourceFully(@Nonnull String fileName) throws IOException { + return readResourceFully(fileName, "UTF-8"); + } + + @Nonnull + public static String readResourceFully(@Nonnull String fileName, @Nonnull String encoding) + throws IOException { + return new String(readResourceBytesFully(fileName), encoding); + } + + // Static helpers class; do not instantiate. + private BaksmaliTestUtils() { throw new AssertionError(); } +} diff --git a/baksmali/src/test/java/org/jf/baksmali/DisassemblyTest.java b/baksmali/src/test/java/org/jf/baksmali/DisassemblyTest.java new file mode 100644 index 00000000..35304f7e --- /dev/null +++ b/baksmali/src/test/java/org/jf/baksmali/DisassemblyTest.java @@ -0,0 +1,104 @@ +/* + * Copyright 2015, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "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 COPYRIGHT + * OWNER OR CONTRIBUTORS 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.jf.baksmali; + +import com.google.common.collect.Iterables; +import org.jf.dexlib2.Opcodes; +import org.jf.dexlib2.dexbacked.DexBackedDexFile; +import org.jf.dexlib2.iface.ClassDef; +import org.junit.Assert; + +import javax.annotation.Nonnull; +import java.io.File; +import java.io.IOException; + +/** + * A base test class for performing a disassembly on a dex file and verifying the results + * + * The test accepts a single-class dex file as input, disassembles it, and verifies that + * the result equals a known-good output smali file. + * + * By default, the input and output files should be resources at [testDir]/[testName]Input.dex + * and [testDir]/[testName]Output.smali respectively + */ +public class DisassemblyTest { + protected final String testDir; + + protected DisassemblyTest(@Nonnull String testDir) { + this.testDir = testDir; + } + + protected DisassemblyTest() { + this.testDir = this.getClass().getSimpleName(); + } + + @Nonnull + protected String getInputFilename(@Nonnull String testName) { + return String.format("%s%s%sInput.dex", testDir, File.separatorChar, testName); + } + + @Nonnull + protected String getOutputFilename(@Nonnull String testName) { + return String.format("%s%s%sOutput.smali", testDir, File.separatorChar, testName); + } + + protected void runTest(@Nonnull String testName) { + runTest(testName, new baksmaliOptions()); + } + + protected void runTest(@Nonnull String testName, @Nonnull baksmaliOptions options) { + try { + // Load file from resources as a stream + String inputFilename = getInputFilename(testName); + byte[] inputBytes = BaksmaliTestUtils.readResourceBytesFully(getInputFilename(testName)); + + DexBackedDexFile inputDex = new DexBackedDexFile(new Opcodes(options.apiLevel, false), inputBytes); + Assert.assertEquals(1, inputDex.getClassCount()); + ClassDef inputClass = Iterables.getFirst(inputDex.getClasses(), null); + Assert.assertNotNull(inputClass); + String input = BaksmaliTestUtils.getNormalizedSmali(inputClass, options, true); + + String output; + if (getOutputFilename(testName).equals(inputFilename)) { + output = input; + } else { + output = BaksmaliTestUtils.readResourceFully(getOutputFilename(testName)); + } + output = BaksmaliTestUtils.normalizeSmali(output, true); + + // Run smali, baksmali, and then compare strings are equal (minus comments/whitespace) + Assert.assertEquals(output, input); + } catch (IOException ex) { + Assert.fail(); + } + } +} diff --git a/baksmali/src/test/java/org/jf/baksmali/IdenticalRoundtripTest.java b/baksmali/src/test/java/org/jf/baksmali/IdenticalRoundtripTest.java new file mode 100644 index 00000000..e636ee1e --- /dev/null +++ b/baksmali/src/test/java/org/jf/baksmali/IdenticalRoundtripTest.java @@ -0,0 +1,59 @@ +/* + * Copyright 2015, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "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 COPYRIGHT + * OWNER OR CONTRIBUTORS 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.jf.baksmali; + +import javax.annotation.Nonnull; +import java.io.File; + +/** + * A base test class for performing a roundtrip assembly/disassembly where the input and output + * should be identical. + * + * By default, the input/output file should be a resource at [testDir]/[testName].smali + */ +public abstract class IdenticalRoundtripTest extends RoundtripTest { + + public IdenticalRoundtripTest(@Nonnull String testDir) { + super(testDir); + } + + public IdenticalRoundtripTest() { + } + + @Nonnull @Override protected String getInputFilename(@Nonnull String testName) { + return String.format("%s%s%s.smali", testDir, File.separatorChar, testName); + } + + @Nonnull @Override protected String getOutputFilename(@Nonnull String testName) { + return getInputFilename(testName); + } +} diff --git a/baksmali/src/test/java/org/jf/baksmali/ImplicitReferenceTest.java b/baksmali/src/test/java/org/jf/baksmali/ImplicitReferenceTest.java index 0cda27a1..1f2ae5bf 100644 --- a/baksmali/src/test/java/org/jf/baksmali/ImplicitReferenceTest.java +++ b/baksmali/src/test/java/org/jf/baksmali/ImplicitReferenceTest.java @@ -31,22 +31,15 @@ package org.jf.baksmali; -import junit.framework.Assert; import org.antlr.runtime.RecognitionException; -import org.jf.baksmali.Adaptors.ClassDefinition; -import org.jf.dexlib2.iface.ClassDef; -import org.jf.smali.SmaliTestUtils; -import org.jf.util.IndentingWriter; -import org.jf.util.TextUtils; import org.junit.Test; import java.io.IOException; -import java.io.StringWriter; public class ImplicitReferenceTest { @Test public void testImplicitMethodReferences() throws IOException, RecognitionException { - ClassDef classDef = SmaliTestUtils.compileSmali("" + + String source = "" + ".class public LHelloWorld;\n" + ".super Ljava/lang/Object;\n" + ".method public static main([Ljava/lang/String;)V\n" + @@ -55,7 +48,7 @@ public class ImplicitReferenceTest { " invoke-static {p0}, LHelloWorld;->V()V\n" + " invoke-static {p0}, LHelloWorld;->I()V\n" + " return-void\n" + - ".end method"); + ".end method"; String expected = "" + ".class public LHelloWorld;\n" + @@ -72,19 +65,12 @@ public class ImplicitReferenceTest { baksmaliOptions options = new baksmaliOptions(); options.useImplicitReferences = true; - StringWriter stringWriter = new StringWriter(); - IndentingWriter writer = new IndentingWriter(stringWriter); - ClassDefinition classDefinition = new ClassDefinition(options, classDef); - classDefinition.writeTo(writer); - writer.close(); - - Assert.assertEquals(TextUtils.normalizeWhitespace(expected), - TextUtils.normalizeWhitespace(stringWriter.toString())); + BaksmaliTestUtils.assertSmaliCompiledEquals(source, expected, options); } @Test public void testExplicitMethodReferences() throws IOException, RecognitionException { - ClassDef classDef = SmaliTestUtils.compileSmali("" + + String source = "" + ".class public LHelloWorld;\n" + ".super Ljava/lang/Object;\n" + ".method public static main([Ljava/lang/String;)V\n" + @@ -93,7 +79,7 @@ public class ImplicitReferenceTest { " invoke-static {p0}, LHelloWorld;->V()V\n" + " invoke-static {p0}, LHelloWorld;->I()V\n" + " return-void\n" + - ".end method"); + ".end method"; String expected = "" + ".class public LHelloWorld;\n" + @@ -110,25 +96,18 @@ public class ImplicitReferenceTest { baksmaliOptions options = new baksmaliOptions(); options.useImplicitReferences = false; - StringWriter stringWriter = new StringWriter(); - IndentingWriter writer = new IndentingWriter(stringWriter); - ClassDefinition classDefinition = new ClassDefinition(options, classDef); - classDefinition.writeTo(writer); - writer.close(); - - Assert.assertEquals(TextUtils.normalizeWhitespace(expected), - TextUtils.normalizeWhitespace(stringWriter.toString())); + BaksmaliTestUtils.assertSmaliCompiledEquals(source, expected, options); } @Test public void testImplicitMethodLiterals() throws IOException, RecognitionException { - ClassDef classDef = SmaliTestUtils.compileSmali("" + + String source = "" + ".class public LHelloWorld;\n" + ".super Ljava/lang/Object;\n" + ".field public static field1:Ljava/lang/reflect/Method; = LHelloWorld;->toString()V\n" + ".field public static field2:Ljava/lang/reflect/Method; = LHelloWorld;->V()V\n" + ".field public static field3:Ljava/lang/reflect/Method; = LHelloWorld;->I()V\n" + - ".field public static field4:Ljava/lang/Class; = I"); + ".field public static field4:Ljava/lang/Class; = I"; String expected = "" + ".class public LHelloWorld;\n" + @@ -142,25 +121,18 @@ public class ImplicitReferenceTest { baksmaliOptions options = new baksmaliOptions(); options.useImplicitReferences = true; - StringWriter stringWriter = new StringWriter(); - IndentingWriter writer = new IndentingWriter(stringWriter); - ClassDefinition classDefinition = new ClassDefinition(options, classDef); - classDefinition.writeTo(writer); - writer.close(); - - Assert.assertEquals(TextUtils.normalizeWhitespace(expected), - TextUtils.normalizeWhitespace(stringWriter.toString())); + BaksmaliTestUtils.assertSmaliCompiledEquals(source, expected, options); } @Test public void testExplicitMethodLiterals() throws IOException, RecognitionException { - ClassDef classDef = SmaliTestUtils.compileSmali("" + + String source = "" + ".class public LHelloWorld;\n" + ".super Ljava/lang/Object;\n" + ".field public static field1:Ljava/lang/reflect/Method; = LHelloWorld;->toString()V\n" + ".field public static field2:Ljava/lang/reflect/Method; = LHelloWorld;->V()V\n" + ".field public static field3:Ljava/lang/reflect/Method; = LHelloWorld;->I()V\n" + - ".field public static field4:Ljava/lang/Class; = I"); + ".field public static field4:Ljava/lang/Class; = I"; String expected = "" + ".class public LHelloWorld;\n" + @@ -174,19 +146,12 @@ public class ImplicitReferenceTest { baksmaliOptions options = new baksmaliOptions(); options.useImplicitReferences = false; - StringWriter stringWriter = new StringWriter(); - IndentingWriter writer = new IndentingWriter(stringWriter); - ClassDefinition classDefinition = new ClassDefinition(options, classDef); - classDefinition.writeTo(writer); - writer.close(); - - Assert.assertEquals(TextUtils.normalizeWhitespace(expected), - TextUtils.normalizeWhitespace(stringWriter.toString())); + BaksmaliTestUtils.assertSmaliCompiledEquals(source, expected, options); } @Test public void testImplicitFieldReferences() throws IOException, RecognitionException { - ClassDef classDef = SmaliTestUtils.compileSmali("" + + String source = "" + ".class public LHelloWorld;\n" + ".super Ljava/lang/Object;\n" + ".method public static main([Ljava/lang/String;)V\n" + @@ -195,7 +160,7 @@ public class ImplicitReferenceTest { " sget v0, LHelloWorld;->I:I\n" + " sget v0, LHelloWorld;->V:I\n" + " return-void\n" + - ".end method"); + ".end method"; String expected = "" + ".class public LHelloWorld;\n" + @@ -212,19 +177,12 @@ public class ImplicitReferenceTest { baksmaliOptions options = new baksmaliOptions(); options.useImplicitReferences = true; - StringWriter stringWriter = new StringWriter(); - IndentingWriter writer = new IndentingWriter(stringWriter); - ClassDefinition classDefinition = new ClassDefinition(options, classDef); - classDefinition.writeTo(writer); - writer.close(); - - Assert.assertEquals(TextUtils.normalizeWhitespace(expected), - TextUtils.normalizeWhitespace(stringWriter.toString())); + BaksmaliTestUtils.assertSmaliCompiledEquals(source, expected, options); } @Test public void testExplicitFieldReferences() throws IOException, RecognitionException { - ClassDef classDef = SmaliTestUtils.compileSmali("" + + String source = "" + ".class public LHelloWorld;\n" + ".super Ljava/lang/Object;\n" + ".method public static main([Ljava/lang/String;)V\n" + @@ -233,7 +191,7 @@ public class ImplicitReferenceTest { " sget v0, LHelloWorld;->I:I\n" + " sget v0, LHelloWorld;->V:I\n" + " return-void\n" + - ".end method"); + ".end method"; String expected = "" + ".class public LHelloWorld;\n" + @@ -250,24 +208,17 @@ public class ImplicitReferenceTest { baksmaliOptions options = new baksmaliOptions(); options.useImplicitReferences = false; - StringWriter stringWriter = new StringWriter(); - IndentingWriter writer = new IndentingWriter(stringWriter); - ClassDefinition classDefinition = new ClassDefinition(options, classDef); - classDefinition.writeTo(writer); - writer.close(); - - Assert.assertEquals(TextUtils.normalizeWhitespace(expected), - TextUtils.normalizeWhitespace(stringWriter.toString())); + BaksmaliTestUtils.assertSmaliCompiledEquals(source, expected, options); } @Test public void testImplicitFieldLiterals() throws IOException, RecognitionException { - ClassDef classDef = SmaliTestUtils.compileSmali("" + + String source = "" + ".class public LHelloWorld;\n" + ".super Ljava/lang/Object;\n" + ".field public static field1:Ljava/lang/reflect/Field; = LHelloWorld;->someField:I\n" + ".field public static field2:Ljava/lang/reflect/Field; = LHelloWorld;->V:I\n" + - ".field public static field3:Ljava/lang/reflect/Field; = LHelloWorld;->I:I"); + ".field public static field3:Ljava/lang/reflect/Field; = LHelloWorld;->I:I"; String expected = "" + ".class public LHelloWorld;\n" + @@ -280,24 +231,17 @@ public class ImplicitReferenceTest { baksmaliOptions options = new baksmaliOptions(); options.useImplicitReferences = true; - StringWriter stringWriter = new StringWriter(); - IndentingWriter writer = new IndentingWriter(stringWriter); - ClassDefinition classDefinition = new ClassDefinition(options, classDef); - classDefinition.writeTo(writer); - writer.close(); - - Assert.assertEquals(TextUtils.normalizeWhitespace(expected), - TextUtils.normalizeWhitespace(stringWriter.toString())); + BaksmaliTestUtils.assertSmaliCompiledEquals(source, expected, options); } @Test public void testExplicitFieldLiterals() throws IOException, RecognitionException { - ClassDef classDef = SmaliTestUtils.compileSmali("" + + String source = "" + ".class public LHelloWorld;\n" + ".super Ljava/lang/Object;\n" + ".field public static field1:Ljava/lang/reflect/Field; = LHelloWorld;->someField:I\n" + ".field public static field2:Ljava/lang/reflect/Field; = LHelloWorld;->V:I\n" + - ".field public static field3:Ljava/lang/reflect/Field; = LHelloWorld;->I:I"); + ".field public static field3:Ljava/lang/reflect/Field; = LHelloWorld;->I:I"; String expected = "" + ".class public LHelloWorld;\n" + @@ -310,13 +254,7 @@ public class ImplicitReferenceTest { baksmaliOptions options = new baksmaliOptions(); options.useImplicitReferences = false; - StringWriter stringWriter = new StringWriter(); - IndentingWriter writer = new IndentingWriter(stringWriter); - ClassDefinition classDefinition = new ClassDefinition(options, classDef); - classDefinition.writeTo(writer); - writer.close(); - - Assert.assertEquals(TextUtils.normalizeWhitespace(expected), - TextUtils.normalizeWhitespace(stringWriter.toString())); + BaksmaliTestUtils.assertSmaliCompiledEquals(source, expected, options); } + } diff --git a/baksmali/src/test/java/org/jf/baksmali/LambdaTest.java b/baksmali/src/test/java/org/jf/baksmali/LambdaTest.java new file mode 100644 index 00000000..5431df54 --- /dev/null +++ b/baksmali/src/test/java/org/jf/baksmali/LambdaTest.java @@ -0,0 +1,49 @@ +/* + * Copyright 2015, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "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 COPYRIGHT + * OWNER OR CONTRIBUTORS 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.jf.baksmali; + +import org.junit.Test; + +public class LambdaTest extends IdenticalRoundtripTest { + + private baksmaliOptions createOptions() { + baksmaliOptions options = new baksmaliOptions(); + options.apiLevel = 23; // since we need at least level 23 for lambda opcodes + options.experimental = true; // since these opcodes aren't implemented in runtime yet); + return options; + } + + @Test + public void testHelloWorldLambda() { + runTest("HelloWorldLambda", createOptions()); + } +} diff --git a/baksmali/src/test/java/org/jf/baksmali/ManyRegistersTest.java b/baksmali/src/test/java/org/jf/baksmali/ManyRegistersTest.java new file mode 100644 index 00000000..5a267161 --- /dev/null +++ b/baksmali/src/test/java/org/jf/baksmali/ManyRegistersTest.java @@ -0,0 +1,42 @@ +/* + * Copyright 2015, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "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 COPYRIGHT + * OWNER OR CONTRIBUTORS 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.jf.baksmali; + +import org.junit.Test; + +public class ManyRegistersTest extends IdenticalRoundtripTest { + + @Test + public void testManyRegisters() { + runTest("ManyRegisters"); + } +} diff --git a/baksmali/src/test/java/org/jf/baksmali/MultiSwitchTest.java b/baksmali/src/test/java/org/jf/baksmali/MultiSwitchTest.java new file mode 100644 index 00000000..cb29402f --- /dev/null +++ b/baksmali/src/test/java/org/jf/baksmali/MultiSwitchTest.java @@ -0,0 +1,42 @@ +/* + * Copyright 2015, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "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 COPYRIGHT + * OWNER OR CONTRIBUTORS 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.jf.baksmali; + +import org.junit.Test; + +public class MultiSwitchTest extends DisassemblyTest { + + @Test + public void testMultiSwitch() { + runTest("MultiSwitch"); + } +} diff --git a/baksmali/src/test/java/org/jf/baksmali/RoundtripTest.java b/baksmali/src/test/java/org/jf/baksmali/RoundtripTest.java new file mode 100644 index 00000000..c9ff2d4d --- /dev/null +++ b/baksmali/src/test/java/org/jf/baksmali/RoundtripTest.java @@ -0,0 +1,95 @@ +/* + * Copyright 2015, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "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 COPYRIGHT + * OWNER OR CONTRIBUTORS 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.jf.baksmali; + +import org.antlr.runtime.RecognitionException; +import org.junit.Assert; + +import javax.annotation.Nonnull; +import java.io.File; +import java.io.IOException; + +/** + * A base test class for performing a roundtrip assembly/disassembly + * + * The test accepts a smali file as input, performs a smali -> dex -> smali roundtrip, and + * verifies that the result equals a known-good output smali file. + * + * By default, the input and output files should be resources at [testDir]/[testName]Input.smali + * and [testDir]/[testName]Output.smali respectively + */ +public abstract class RoundtripTest { + protected final String testDir; + + protected RoundtripTest(@Nonnull String testDir) { + this.testDir = testDir; + } + + protected RoundtripTest() { + this.testDir = this.getClass().getSimpleName(); + } + + @Nonnull + protected String getInputFilename(@Nonnull String testName) { + return String.format("%s%s%sInput.smali", testDir, File.separatorChar, testName); + } + + @Nonnull + protected String getOutputFilename(@Nonnull String testName) { + return String.format("%s%s%sOutput.smali", testDir, File.separatorChar, testName); + } + + protected void runTest(@Nonnull String testName) { + runTest(testName, new baksmaliOptions()); + } + + protected void runTest(@Nonnull String testName, @Nonnull baksmaliOptions options) { + try { + // Load file from resources as a stream + String inputFilename = getInputFilename(testName); + String input = BaksmaliTestUtils.readResourceFully(getInputFilename(testName)); + String output; + if (getOutputFilename(testName).equals(inputFilename)) { + output = input; + } else { + output = BaksmaliTestUtils.readResourceFully(getOutputFilename(testName)); + } + + // Run smali, baksmali, and then compare strings are equal (minus comments/whitespace) + BaksmaliTestUtils.assertSmaliCompiledEquals(input, output, options, true); + } catch (IOException ex) { + Assert.fail(); + } catch (RecognitionException ex) { + Assert.fail(); + } + } +} diff --git a/baksmali/src/test/java/org/jf/baksmali/SwitchTest.java b/baksmali/src/test/java/org/jf/baksmali/SwitchTest.java new file mode 100644 index 00000000..48b64b22 --- /dev/null +++ b/baksmali/src/test/java/org/jf/baksmali/SwitchTest.java @@ -0,0 +1,41 @@ +/* + * Copyright 2015, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "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 COPYRIGHT + * OWNER OR CONTRIBUTORS 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.jf.baksmali; + +import org.junit.Test; + +public class SwitchTest extends RoundtripTest { + @Test + public void testUnorderedSparseSwitch() { + runTest("UnorderedSparseSwitch"); + } +} diff --git a/baksmali/src/test/resources/DuplicateTest/DuplicateDirectMethods.smali b/baksmali/src/test/resources/DuplicateTest/DuplicateDirectMethods.smali index b1f30c9a..fd43b021 100644 --- a/baksmali/src/test/resources/DuplicateTest/DuplicateDirectMethods.smali +++ b/baksmali/src/test/resources/DuplicateTest/DuplicateDirectMethods.smali @@ -22,7 +22,6 @@ # return-void # .end method - .method private clah()V .registers 1 diff --git a/baksmali/src/test/resources/DuplicateTest/DuplicateDirectVirtualMethods.smali b/baksmali/src/test/resources/DuplicateTest/DuplicateDirectVirtualMethods.smali index 8d87c1d8..52865737 100644 --- a/baksmali/src/test/resources/DuplicateTest/DuplicateDirectVirtualMethods.smali +++ b/baksmali/src/test/resources/DuplicateTest/DuplicateDirectVirtualMethods.smali @@ -17,7 +17,6 @@ # .end method - # virtual methods .method public alah()V .registers 1 @@ -40,7 +39,6 @@ # return-void # .end method - .method public clah()V .registers 1 diff --git a/baksmali/src/test/resources/DuplicateTest/DuplicateVirtualMethods.smali b/baksmali/src/test/resources/DuplicateTest/DuplicateVirtualMethods.smali index 74af4c58..3c080024 100644 --- a/baksmali/src/test/resources/DuplicateTest/DuplicateVirtualMethods.smali +++ b/baksmali/src/test/resources/DuplicateTest/DuplicateVirtualMethods.smali @@ -22,7 +22,6 @@ # return-void # .end method - .method public clah()V .registers 1 diff --git a/baksmali/src/test/resources/LambdaTest/HelloWorldLambda.smali b/baksmali/src/test/resources/LambdaTest/HelloWorldLambda.smali new file mode 100644 index 00000000..d70ced50 --- /dev/null +++ b/baksmali/src/test/resources/LambdaTest/HelloWorldLambda.smali @@ -0,0 +1,55 @@ +.class public LHelloWorldLambda; + +#Ye olde hello world application (with lambdas!) +#To assemble and run this on a phone or emulator: +# +#java -jar smali.jar -o classes.dex HelloWorldLambda.smali HelloWorldFunctionalInterface.smali +#zip HelloWorld.zip classes.dex +#adb push HelloWorld.zip /data/local +#adb shell dalvikvm -cp /data/local/HelloWorld.zip HelloWorld +# +#if you get out of memory type errors when running smali.jar, try +#java -Xmx512m -jar smali.jar HelloWorldLambda.smali +#instead + +.super Ljava/lang/Object; + +.method public static doHelloWorld(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + .registers 6 # 4 parameters, 2 locals + liberate-variable v0, p0, "helloworld" + + sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream; + invoke-virtual {v1, v0}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V + + return-void +.end method + +.method public static main([Ljava/lang/String;)V + .registers 9 # 1 parameter, 8 locals + + sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream; + + const-string v1, "Hello World!" + const-string v2, "How" # vD + const-string v3, "are" # vE + const-string v4, "you" # vF + const-string v5, "doing?" # vG + + capture-variable v1, "helloworld" + + # TODO: do I need to pass the type of the lambda's functional interface here as a type id? + create-lambda v1, LHelloWorldLambda;->doHelloWorld(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + # Method descriptor is not required here, because only the single-abstract method is ever invoked. + invoke-lambda v1, {v2, v3, v4, v5} + + box-lambda v6, v1 + invoke-virtual {v6, v2, v3, v4, v5}, LHelloWorldFunctionalInterface;->applyFourStrings(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + + # FIXME: should be \HelloWorldFunctionalInterface; instead of L...; + + # TODO: do we really need the type descriptor here at all? + unbox-lambda v7, v6, LHelloWorldFunctionalInterface; + invoke-lambda v7, {v2, v3, v4, v5} + + return-void +.end method diff --git a/baksmali/src/test/resources/ManyRegistersTest/ManyRegisters.smali b/baksmali/src/test/resources/ManyRegistersTest/ManyRegisters.smali new file mode 100644 index 00000000..7f7c7bb1 --- /dev/null +++ b/baksmali/src/test/resources/ManyRegistersTest/ManyRegisters.smali @@ -0,0 +1,7 @@ +.class LManyRegisters; +.super Ljava/lang/Object; + +.method public manyRegisters()V + .registers 65535 + return-void +.end method
\ No newline at end of file diff --git a/baksmali/src/test/resources/MultiSwitchTest/MultiSwitchInput.dex b/baksmali/src/test/resources/MultiSwitchTest/MultiSwitchInput.dex Binary files differnew file mode 100644 index 00000000..6ef3d349 --- /dev/null +++ b/baksmali/src/test/resources/MultiSwitchTest/MultiSwitchInput.dex diff --git a/baksmali/src/test/resources/MultiSwitchTest/MultiSwitchInput.smali b/baksmali/src/test/resources/MultiSwitchTest/MultiSwitchInput.smali new file mode 100644 index 00000000..b4fd27d8 --- /dev/null +++ b/baksmali/src/test/resources/MultiSwitchTest/MultiSwitchInput.smali @@ -0,0 +1,72 @@ +.class public LMultiSwitch; +.super Ljava/lang/Object; +.source "Format31t.smali" + +.method public multi-packed-switch()V + .registers 1 + const p0, 0xc + packed-switch p0, :pswitch_data_12 + goto :goto_b + :pswitch_7 + return-void + :pswitch_8 + return-void + :pswitch_9 + return-void + :pswitch_a + return-void + :goto_b + packed-switch p0, :pswitch_data_12 + nop + return-void + :pswitch_f + return-void + :pswitch_10 + return-void + :pswitch_11 + return-void + :pswitch_12 + :pswitch_data_12 + .packed-switch 0xa + :pswitch_7 + :pswitch_8 + :pswitch_9 + :pswitch_a + .end packed-switch + +.end method + +.method public multi-sparse-switch()V + .registers 1 + const p0, 0xd + sparse-switch p0, :sswitch_data_12 + goto :goto_b + :sswitch_7 + return-void + :sswitch_8 + return-void + :sswitch_9 + return-void + :sswitch_a + return-void + :goto_b + sparse-switch p0, :sswitch_data_12 + nop + return-void + :sswitch_f + return-void + :sswitch_10 + return-void + :sswitch_11 + return-void + + :sswitch_12 + + :sswitch_data_12 + .sparse-switch + 0xa -> :sswitch_7 + 0xf -> :sswitch_9 + 0x14 -> :sswitch_8 + 0x63 -> :sswitch_a + .end sparse-switch +.end method
\ No newline at end of file diff --git a/baksmali/src/test/resources/MultiSwitchTest/MultiSwitchOutput.smali b/baksmali/src/test/resources/MultiSwitchTest/MultiSwitchOutput.smali new file mode 100644 index 00000000..f3aeeed5 --- /dev/null +++ b/baksmali/src/test/resources/MultiSwitchTest/MultiSwitchOutput.smali @@ -0,0 +1,119 @@ +.class public LMultiSwitch; +.super Ljava/lang/Object; +.source "Format31t.smali" + + +# virtual methods +.method public multi-packed-switch()V + .registers 1 + + const p0, 0xc + + packed-switch p0, :pswitch_data_14 + + goto :goto_b + + :pswitch_7 + return-void + + :pswitch_8 + return-void + + :pswitch_9 + return-void + + :pswitch_a + return-void + + :goto_b + packed-switch p0, :pswitch_data_20 + + nop + + :pswitch_f + return-void + + :pswitch_10 + return-void + + :pswitch_11 + return-void + + :pswitch_12 + return-void + + nop + + :pswitch_data_14 + .packed-switch 0xa + :pswitch_7 + :pswitch_8 + :pswitch_9 + :pswitch_a + .end packed-switch + + :pswitch_data_20 + .packed-switch 0xa + :pswitch_f + :pswitch_10 + :pswitch_11 + :pswitch_12 + .end packed-switch +.end method + +.method public multi-sparse-switch()V + .registers 1 + + const p0, 0xd + + sparse-switch p0, :sswitch_data_14 + + goto :goto_b + + :sswitch_7 + return-void + + :sswitch_8 + return-void + + :sswitch_9 + return-void + + :sswitch_a + return-void + + :goto_b + sparse-switch p0, :sswitch_data_26 + + nop + + :sswitch_f + return-void + + :sswitch_10 + return-void + + :sswitch_11 + return-void + + :sswitch_12 + return-void + + nop + + :sswitch_data_14 + .sparse-switch + 0xa -> :sswitch_7 + 0xf -> :sswitch_9 + 0x14 -> :sswitch_8 + 0x63 -> :sswitch_a + .end sparse-switch + + :sswitch_data_26 + .sparse-switch + 0xa -> :sswitch_f + 0xf -> :sswitch_11 + 0x14 -> :sswitch_10 + 0x63 -> :sswitch_12 + .end sparse-switch +.end method diff --git a/baksmali/src/test/resources/SwitchTest/UnorderedSparseSwitchInput.smali b/baksmali/src/test/resources/SwitchTest/UnorderedSparseSwitchInput.smali new file mode 100644 index 00000000..6e3d23d4 --- /dev/null +++ b/baksmali/src/test/resources/SwitchTest/UnorderedSparseSwitchInput.smali @@ -0,0 +1,35 @@ +.class public LUnorderedSparseSwitch; +.super Ljava/lang/Object; + +.method public static test_sparse-switch()V + .registers 1 + + const v0, 13 + + sparse-switch v0, :SparseSwitch + +:Label10 + return-void + +:Label20 + return-void + +:Label15 + return-void + +:Label13 + return-void + +:Label99 + return-void + +# Note: unordered keys +:SparseSwitch + .sparse-switch + 10 -> :Label10 + 20 -> :Label20 + 15 -> :Label15 + 99 -> :Label99 + 13 -> :Label13 + .end sparse-switch +.end method
\ No newline at end of file diff --git a/baksmali/src/test/resources/SwitchTest/UnorderedSparseSwitchOutput.smali b/baksmali/src/test/resources/SwitchTest/UnorderedSparseSwitchOutput.smali new file mode 100644 index 00000000..c4c455b6 --- /dev/null +++ b/baksmali/src/test/resources/SwitchTest/UnorderedSparseSwitchOutput.smali @@ -0,0 +1,28 @@ +.class public LUnorderedSparseSwitch; +.super Ljava/lang/Object; +.method public static test_sparse-switch()V +.registers 1 +const v0, 0xd +sparse-switch v0, :sswitch_data_c +:sswitch_6 +return-void +:sswitch_7 +return-void +:sswitch_8 +return-void +:sswitch_9 +return-void +:sswitch_a +return-void +nop + +# Note: ordered keys +:sswitch_data_c +.sparse-switch +0xa -> :sswitch_6 +0xd -> :sswitch_9 +0xf -> :sswitch_8 +0x14 -> :sswitch_7 +0x63 -> :sswitch_a +.end sparse-switch +.end method
\ No newline at end of file diff --git a/build.gradle b/build.gradle index a9ee7e0e..f0d1e00a 100644 --- a/build.gradle +++ b/build.gradle @@ -39,7 +39,7 @@ if (!('release' in gradle.startParameter.taskNames)) { def versionSuffix try { def git = org.eclipse.jgit.api.Git.open(file('.')) - def head = git.getRepository().getRef("HEAD") + def head = git.getRepository().getRef('HEAD') versionSuffix = head.getObjectId().abbreviate(8).name() if (!git.status().call().clean) { @@ -90,7 +90,9 @@ subprojects { stringtemplate: 'org.antlr:stringtemplate:3.2.1', commons_cli: 'commons-cli:commons-cli:1.2', jflex: 'de.jflex:jflex:1.4.3', - proguard: 'net.sf.proguard:proguard-base:4.8' + jflex_plugin: 'co.tomlee.gradle.plugins:gradle-jflex-plugin:0.0.1', + proguard_gradle: 'net.sf.proguard:proguard-gradle:5.2.1', + dx: 'com.google.android.tools:dx:1.7' ] } @@ -124,7 +126,7 @@ subprojects { } signing { - required { gradle.taskGraph.hasTask("uploadArchives") } + required { gradle.taskGraph.hasTask('uploadArchives') } sign configurations.archives } @@ -135,7 +137,7 @@ subprojects { beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } if (rootProject.hasProperty('sonatypeUsername') && rootProject.hasProperty('sonatypePassword')) { - repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") { + repository(url: 'https://oss.sonatype.org/service/local/staging/deploy/maven2/') { authentication(userName: sonatypeUsername, password: sonatypePassword) } } @@ -182,5 +184,5 @@ buildscript { } task wrapper(type: Wrapper) { - gradleVersion = '1.10' + gradleVersion = '2.3' }
\ No newline at end of file diff --git a/deodexerant/Android.mk b/deodexerant/Android.mk index c1921f9b..46a9dba8 100644 --- a/deodexerant/Android.mk +++ b/deodexerant/Android.mk @@ -39,4 +39,6 @@ LOCAL_SHARED_LIBRARIES := libdl LOCAL_MODULE_TAGS := optional -include $(BUILD_EXECUTABLE)
\ No newline at end of file +LOCAL_LDFLAGS := -Wl,--hash-style=sysv + +include $(BUILD_EXECUTABLE) diff --git a/dexlib2/build.gradle b/dexlib2/build.gradle index d39c5db9..dc3e853a 100644 --- a/dexlib2/build.gradle +++ b/dexlib2/build.gradle @@ -31,6 +31,7 @@ configurations { accessorTestGenerator + dx } dependencies { @@ -41,42 +42,13 @@ dependencies { testCompile depends.junit accessorTestGenerator project('accessorTestGenerator') + + dx depends.dx } ext.testAccessorOutputDir = file("${buildDir}/generated-accessor-test-sources") ext.testAccessorOutputFile = file("${buildDir}/generated-accessor-test-sources/org/jf/dexlib2/AccessorTypes.java") -sourceSets { - // The sources for building the test dex file for the accessor test - accessorTestDex { - java { - srcDir testAccessorOutputDir - } - } - - // The sources for the accessor test itself - accessorTest { - java { - compileClasspath += main.output - runtimeClasspath += main.output - } - } -} - -configurations { - accessorTestDexCompile.extendsFrom compile - accessorTestDexRuntime.extendsFrom runtime - - accessorTestCompile.extendsFrom testCompile - accessorTestRuntime.extendsFrom testRuntime -} - -idea { - module { - testSourceDirs += sourceSets.accessorTest.java.srcDirs - } -} - // You must manually execute this task to regenerate SyntheticAccessorFSM.java, after modifying the ragel file // e.g. ./gradlew ragel task ragel(type:Exec) { @@ -87,36 +59,48 @@ task ragel(type:Exec) { } task generateAccessorTestSource(type: JavaExec) { - outputs.dir file(testAccessorOutputDir) + doFirst { + file(testAccessorOutputFile.parent).mkdirs() + } - mkdir(file(testAccessorOutputFile).parent) + outputs.dir file(testAccessorOutputDir) + sourceSets['test'].java.srcDir file(testAccessorOutputDir) classpath = configurations.accessorTestGenerator main = 'org.jf.dexlib2.AccessorTestGenerator' args testAccessorOutputFile } -compileAccessorTestDexJava.dependsOn(generateAccessorTestSource) +compileTestJava.dependsOn generateAccessorTestSource -task generateAccessorTestDex(type: Exec, dependsOn: compileAccessorTestDexJava) { - def outputDex = file("${sourceSets.accessorTest.output.resourcesDir}/accessorTest.dex") - mkdir(outputDex.parent) +task generateAccessorTestDex(type: JavaExec, dependsOn: compileTestJava) { + def outputDex = file(new File(sourceSets.test.output.resourcesDir, 'accessorTest.dex')) - inputs.dir project.sourceSets.accessorTestDex.output.classesDir + doFirst { + file(outputDex.parent).mkdirs() + + // this has to be done in doFirst, so that the generated classes will be available. + // otherwise, it the tree will be populated while the build is being configured, + // which is before the compileTestJava has run + fileTree(project.sourceSets.test.output.classesDir) { + include 'org/jf/dexlib2/AccessorTypes*.class' + }.each { File file -> + args file + } + } + + inputs.dir(project.sourceSets.test.output.classesDir) outputs.file outputDex - sourceSets.accessorTest.resources + main 'com.android.dx.command.Main' + classpath = configurations.dx - workingDir project.sourceSets.accessorTestDex.output.classesDir - executable 'dx' + workingDir project.sourceSets.test.output.classesDir + //executable 'dx' args '--dex' + args '--no-strict' args "--output=${outputDex}" - args '.' -} - -task accessorTest(type: Test, dependsOn: generateAccessorTestDex) { - testClassesDir = project.sourceSets.accessorTest.output.classesDir - classpath = project.sourceSets.accessorTest.runtimeClasspath } +test.dependsOn generateAccessorTestDex uploadArchives { repositories.mavenDeployer { diff --git a/dexlib2/src/main/java/org/jf/dexlib2/DexFileFactory.java b/dexlib2/src/main/java/org/jf/dexlib2/DexFileFactory.java index d08da036..a1ddee2e 100644 --- a/dexlib2/src/main/java/org/jf/dexlib2/DexFileFactory.java +++ b/dexlib2/src/main/java/org/jf/dexlib2/DexFileFactory.java @@ -45,22 +45,26 @@ import java.util.zip.ZipFile; public final class DexFileFactory { @Nonnull - public static DexBackedDexFile loadDexFile(String path, int api) throws IOException { - return loadDexFile(new File(path), "classes.dex", new Opcodes(api)); + public static DexBackedDexFile loadDexFile(String path, int api, boolean experimental) + throws IOException { + return loadDexFile(new File(path), "classes.dex", new Opcodes(api, experimental)); } @Nonnull - public static DexBackedDexFile loadDexFile(File dexFile, int api) throws IOException { - return loadDexFile(dexFile, "classes.dex", new Opcodes(api)); + public static DexBackedDexFile loadDexFile(File dexFile, int api, boolean experimental) + throws IOException { + return loadDexFile(dexFile, "classes.dex", new Opcodes(api, experimental)); } @Nonnull - public static DexBackedDexFile loadDexFile(File dexFile, String dexEntry, int api) throws IOException { - return loadDexFile(dexFile, dexEntry, new Opcodes(api)); + public static DexBackedDexFile loadDexFile(File dexFile, String dexEntry, int api, + boolean experimental) throws IOException { + return loadDexFile(dexFile, dexEntry, new Opcodes(api, experimental)); } @Nonnull - public static DexBackedDexFile loadDexFile(File dexFile, String dexEntry, @Nonnull Opcodes opcodes) throws IOException { + public static DexBackedDexFile loadDexFile(File dexFile, String dexEntry, + @Nonnull Opcodes opcodes) throws IOException { ZipFile zipFile = null; boolean isZipFile = false; try { @@ -98,19 +102,22 @@ public final class DexFileFactory { } InputStream inputStream = new BufferedInputStream(new FileInputStream(dexFile)); - try { - return DexBackedDexFile.fromInputStream(opcodes, inputStream); - } catch (DexBackedDexFile.NotADexFile ex) { - // just eat it - } + try { + return DexBackedDexFile.fromInputStream(opcodes, inputStream); + } catch (DexBackedDexFile.NotADexFile ex) { + // just eat it + } - // Note: DexBackedDexFile.fromInputStream will reset inputStream back to the same position, if it fails + // Note: DexBackedDexFile.fromInputStream will reset inputStream back to the same position, if it fails - try { - return DexBackedOdexFile.fromInputStream(opcodes, inputStream); - } catch (DexBackedOdexFile.NotAnOdexFile ex) { - // just eat it + try { + return DexBackedOdexFile.fromInputStream(opcodes, inputStream); + } catch (DexBackedOdexFile.NotAnOdexFile ex) { + // just eat it + } + } finally { + inputStream.close(); } throw new ExceptionWithContext("%s is not an apk, dex file or odex file.", dexFile.getPath()); diff --git a/dexlib2/src/main/java/org/jf/dexlib2/Format.java b/dexlib2/src/main/java/org/jf/dexlib2/Format.java index d91b7432..ee34aa50 100644 --- a/dexlib2/src/main/java/org/jf/dexlib2/Format.java +++ b/dexlib2/src/main/java/org/jf/dexlib2/Format.java @@ -51,6 +51,7 @@ public enum Format { Format22t(4), Format22x(4), Format23x(4), + Format25x(4), Format30t(6), Format31c(6), Format31i(6), diff --git a/dexlib2/src/main/java/org/jf/dexlib2/Opcode.java b/dexlib2/src/main/java/org/jf/dexlib2/Opcode.java index d0adee8c..3b082ee8 100644 --- a/dexlib2/src/main/java/org/jf/dexlib2/Opcode.java +++ b/dexlib2/src/main/java/org/jf/dexlib2/Opcode.java @@ -269,13 +269,13 @@ public enum Opcode INVOKE_OBJECT_INIT_RANGE((short)0xf0, "invoke-object-init/range", minApi(14), ReferenceType.METHOD, Format.Format3rc, Opcode.ODEX_ONLY | Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_RESULT | Opcode.CAN_INITIALIZE_REFERENCE), RETURN_VOID_BARRIER((short)0xf1, "return-void-barrier", minApi(11), ReferenceType.NONE, Format.Format10x, Opcode.ODEX_ONLY), IGET_QUICK((short)0xf2, "iget-quick", ReferenceType.NONE, Format.Format22cs, Opcode.ODEX_ONLY | Opcode.ODEXED_INSTANCE_QUICK | Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER), - IGET_WIDE_QUICK((short)0xf3, "iget-wide-quick", ReferenceType.NONE, Format.Format22cs, Opcode.ODEX_ONLY | Opcode.ODEXED_INSTANCE_QUICK | Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER | Opcode.SETS_WIDE_REGISTER), - IGET_OBJECT_QUICK((short)0xf4, "iget-object-quick", ReferenceType.NONE, Format.Format22cs, Opcode.ODEX_ONLY | Opcode.ODEXED_INSTANCE_QUICK | Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER), - IPUT_QUICK((short)0xf5, "iput-quick", ReferenceType.NONE, Format.Format22cs, Opcode.ODEX_ONLY | Opcode.ODEXED_INSTANCE_QUICK | Opcode.CAN_THROW | Opcode.CAN_CONTINUE), - IPUT_WIDE_QUICK((short)0xf6, "iput-wide-quick", ReferenceType.NONE, Format.Format22cs, Opcode.ODEX_ONLY | Opcode.ODEXED_INSTANCE_QUICK | Opcode.CAN_THROW | Opcode.CAN_CONTINUE), - IPUT_OBJECT_QUICK((short)0xf7, "iput-object-quick", ReferenceType.NONE, Format.Format22cs, Opcode.ODEX_ONLY | Opcode.ODEXED_INSTANCE_QUICK | Opcode.CAN_THROW | Opcode.CAN_CONTINUE), - INVOKE_VIRTUAL_QUICK((short)0xf8, "invoke-virtual-quick", ReferenceType.NONE, Format.Format35ms, Opcode.ODEX_ONLY | Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_RESULT), - INVOKE_VIRTUAL_QUICK_RANGE((short)0xf9, "invoke-virtual-quick/range", ReferenceType.NONE, Format.Format3rms, Opcode.ODEX_ONLY | Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_RESULT), + IGET_WIDE_QUICK((short)0xf3, "iget-wide-quick", maxApi(22), ReferenceType.NONE, Format.Format22cs, Opcode.ODEX_ONLY | Opcode.ODEXED_INSTANCE_QUICK | Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER | Opcode.SETS_WIDE_REGISTER), + IGET_OBJECT_QUICK((short)0xf4, "iget-object-quick", maxApi(22), ReferenceType.NONE, Format.Format22cs, Opcode.ODEX_ONLY | Opcode.ODEXED_INSTANCE_QUICK | Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_REGISTER), + IPUT_QUICK((short)0xf5, "iput-quick", maxApi(22), ReferenceType.NONE, Format.Format22cs, Opcode.ODEX_ONLY | Opcode.ODEXED_INSTANCE_QUICK | Opcode.CAN_THROW | Opcode.CAN_CONTINUE), + IPUT_WIDE_QUICK((short)0xf6, "iput-wide-quick", maxApi(22), ReferenceType.NONE, Format.Format22cs, Opcode.ODEX_ONLY | Opcode.ODEXED_INSTANCE_QUICK | Opcode.CAN_THROW | Opcode.CAN_CONTINUE), + IPUT_OBJECT_QUICK((short)0xf7, "iput-object-quick", maxApi(22), ReferenceType.NONE, Format.Format22cs, Opcode.ODEX_ONLY | Opcode.ODEXED_INSTANCE_QUICK | Opcode.CAN_THROW | Opcode.CAN_CONTINUE), + INVOKE_VIRTUAL_QUICK((short)0xf8, "invoke-virtual-quick", maxApi(22), ReferenceType.NONE, Format.Format35ms, Opcode.ODEX_ONLY | Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_RESULT), + INVOKE_VIRTUAL_QUICK_RANGE((short)0xf9, "invoke-virtual-quick/range", maxApi(22), ReferenceType.NONE, Format.Format3rms, Opcode.ODEX_ONLY | Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_RESULT), INVOKE_SUPER_QUICK((short)0xfa, "invoke-super-quick", ReferenceType.NONE, Format.Format35ms, Opcode.ODEX_ONLY | Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_RESULT), INVOKE_SUPER_QUICK_RANGE((short)0xfb, "invoke-super-quick/range", ReferenceType.NONE, Format.Format3rms, Opcode.ODEX_ONLY | Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_RESULT), @@ -285,7 +285,17 @@ public enum Opcode PACKED_SWITCH_PAYLOAD((short)0x100, "packed-switch-payload", ReferenceType.NONE, Format.PackedSwitchPayload, 0), SPARSE_SWITCH_PAYLOAD((short)0x200, "sparse-switch-payload", ReferenceType.NONE, Format.SparseSwitchPayload, 0), - ARRAY_PAYLOAD((short)0x300, "array-payload", ReferenceType.NONE, Format.ArrayPayload, 0); + ARRAY_PAYLOAD((short)0x300, "array-payload", ReferenceType.NONE, Format.ArrayPayload, 0), + + // Reuse the deprecated f3-ff opcodes in Art: + INVOKE_LAMBDA((short)0xf3, "invoke-lambda", minApi(23), ReferenceType.NONE, Format.Format25x, Opcode.CAN_THROW | Opcode.CAN_CONTINUE | Opcode.SETS_RESULT | Opcode.EXPERIMENTAL), + // TODO: What about JUMBO support if the string ID is too large? + CAPTURE_VARIABLE((short)0xf5, "capture-variable", minApi(23), ReferenceType.STRING, Format.Format21c, Opcode.EXPERIMENTAL), + CREATE_LAMBDA((short)0xf6, "create-lambda", minApi(23), ReferenceType.METHOD, Format.Format21c, Opcode.SETS_REGISTER | Opcode.EXPERIMENTAL), + // TODO: do we need a capture/liberate wide? + LIBERATE_VARIABLE((short)0xf7, "liberate-variable", minApi(23), ReferenceType.STRING, Format.Format22c, Opcode.SETS_REGISTER | Opcode.EXPERIMENTAL), + BOX_LAMBDA((short)0xf8, "box-lambda", minApi(23), ReferenceType.NONE, Format.Format22x, Opcode.SETS_REGISTER | Opcode.EXPERIMENTAL), + UNBOX_LAMBDA((short)0xf9, "unbox-lambda", minApi(23), ReferenceType.TYPE, Format.Format22c, Opcode.SETS_REGISTER | Opcode.EXPERIMENTAL); //if the instruction can throw an exception public static final int CAN_THROW = 0x1; @@ -309,6 +319,8 @@ public enum Opcode public static final int JUMBO_OPCODE = 0x200; //if the instruction can initialize an uninitialized object reference public static final int CAN_INITIALIZE_REFERENCE = 0x400; + //if the instruction is experimental (not potentially supported by Android runtime yet) + public static final int EXPERIMENTAL = 0x800; private static final int ALL_APIS = 0xFFFF0000; @@ -417,4 +429,8 @@ public enum Opcode public final boolean canInitializeReference() { return (flags & CAN_INITIALIZE_REFERENCE) != 0; } + + public final boolean isExperimental() { + return (flags & EXPERIMENTAL) != 0; + } } diff --git a/dexlib2/src/main/java/org/jf/dexlib2/Opcodes.java b/dexlib2/src/main/java/org/jf/dexlib2/Opcodes.java index d6e5532e..dd876813 100644 --- a/dexlib2/src/main/java/org/jf/dexlib2/Opcodes.java +++ b/dexlib2/src/main/java/org/jf/dexlib2/Opcodes.java @@ -40,13 +40,14 @@ public class Opcodes { private final Opcode[] opcodesByValue; private final HashMap<String, Opcode> opcodesByName; - public Opcodes(int api) { + public Opcodes(int api, boolean experimental) { opcodesByValue = new Opcode[256]; opcodesByName = Maps.newHashMap(); for (Opcode opcode: Opcode.values()) { if (!opcode.format.isPayloadFormat) { - if (api <= opcode.getMaxApi() && api >= opcode.getMinApi()) { + if (api <= opcode.getMaxApi() && api >= opcode.getMinApi() && + (experimental || !opcode.isExperimental())) { opcodesByValue[opcode.value] = opcode; opcodesByName.put(opcode.name.toLowerCase(), opcode); } diff --git a/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassPath.java b/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassPath.java index 7efeb408..bd9cfb1b 100644 --- a/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassPath.java +++ b/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassPath.java @@ -164,18 +164,18 @@ public class ClassPath { @Nonnull public static ClassPath fromClassPath(Iterable<String> classPathDirs, Iterable<String> classPath, DexFile dexFile, - int api) { - return fromClassPath(classPathDirs, classPath, dexFile, api, api == 17); + int api, boolean experimental) { + return fromClassPath(classPathDirs, classPath, dexFile, api, api == 17, experimental); } @Nonnull public static ClassPath fromClassPath(Iterable<String> classPathDirs, Iterable<String> classPath, DexFile dexFile, - int api, boolean checkPackagePrivateAccess) { + int api, boolean checkPackagePrivateAccess, boolean experimental) { ArrayList<DexFile> dexFiles = Lists.newArrayList(); for (String classPathEntry: classPath) { try { - dexFiles.add(loadClassPathEntry(classPathDirs, classPathEntry, api)); + dexFiles.add(loadClassPathEntry(classPathDirs, classPathEntry, api, experimental)); } catch (ExceptionWithContext e){} } dexFiles.add(dexFile); @@ -186,7 +186,8 @@ public class ClassPath { @Nonnull private static DexFile loadClassPathEntry(@Nonnull Iterable<String> classPathDirs, - @Nonnull String bootClassPathEntry, int api) { + @Nonnull String bootClassPathEntry, int api, + boolean experimental) { File rawEntry = new File(bootClassPathEntry); // strip off the path - we only care about the filename String entryName = rawEntry.getName(); @@ -221,7 +222,7 @@ public class ClassPath { "warning: cannot open %s for reading. Will continue looking.", file.getPath())); } else { try { - return DexFileFactory.loadDexFile(file, api); + return DexFileFactory.loadDexFile(file, api, experimental); } catch (DexFileFactory.NoClassesDexException ex) { // ignore and continue } catch (Exception ex) { diff --git a/dexlib2/src/main/java/org/jf/dexlib2/analysis/DumpFields.java b/dexlib2/src/main/java/org/jf/dexlib2/analysis/DumpFields.java index d57ce961..2bb3e492 100644 --- a/dexlib2/src/main/java/org/jf/dexlib2/analysis/DumpFields.java +++ b/dexlib2/src/main/java/org/jf/dexlib2/analysis/DumpFields.java @@ -73,6 +73,7 @@ public class DumpFields { ArrayList<String> bootClassPathDirs = Lists.newArrayList(); String outFile = "fields.txt"; int apiLevel = 15; + boolean experimental = false; for (int i=0; i<parsedOptions.length; i++) { Option option = parsedOptions[i]; @@ -88,6 +89,9 @@ public class DumpFields { case 'a': apiLevel = Integer.parseInt(commandLine.getOptionValue("a")); break; + case 'X': + experimental = true; + break; default: assert false; } @@ -107,9 +111,9 @@ public class DumpFields { } try { - DexBackedDexFile dexFile = DexFileFactory.loadDexFile(dexFileFile, apiLevel); + DexBackedDexFile dexFile = DexFileFactory.loadDexFile(dexFileFile, apiLevel, experimental); Iterable<String> bootClassPaths = Splitter.on(":").split("core.jar:ext.jar:framework.jar:android.policy.jar:services.jar"); - ClassPath classPath = ClassPath.fromClassPath(bootClassPathDirs, bootClassPaths, dexFile, apiLevel); + ClassPath classPath = ClassPath.fromClassPath(bootClassPathDirs, bootClassPaths, dexFile, apiLevel, experimental); FileOutputStream outStream = new FileOutputStream(outFile); for (ClassDef classDef: dexFile.getClasses()) { @@ -163,8 +167,14 @@ public class DumpFields { .withArgName("API_LEVEL") .create("a"); + Option experimentalOption = OptionBuilder.withLongOpt("experimental") + .withDescription("Enable dumping experimental opcodes, that aren't necessarily " + + "supported by the android runtime yet.") + .create("X"); + options.addOption(classPathDirOption); options.addOption(outputFileOption); options.addOption(apiLevelOption); + options.addOption(experimentalOption); } } diff --git a/dexlib2/src/main/java/org/jf/dexlib2/analysis/DumpVtables.java b/dexlib2/src/main/java/org/jf/dexlib2/analysis/DumpVtables.java index 0d7656c3..193c0d39 100644 --- a/dexlib2/src/main/java/org/jf/dexlib2/analysis/DumpVtables.java +++ b/dexlib2/src/main/java/org/jf/dexlib2/analysis/DumpVtables.java @@ -71,6 +71,7 @@ public class DumpVtables { ArrayList<String> bootClassPathDirs = Lists.newArrayList(); String outFile = "vtables.txt"; int apiLevel = 15; + boolean experimental = false; for (int i=0; i<parsedOptions.length; i++) { Option option = parsedOptions[i]; @@ -86,6 +87,9 @@ public class DumpVtables { case 'a': apiLevel = Integer.parseInt(commandLine.getOptionValue("a")); break; + case 'X': + experimental = true; + break; default: assert false; } @@ -105,9 +109,9 @@ public class DumpVtables { } try { - DexBackedDexFile dexFile = DexFileFactory.loadDexFile(dexFileFile, apiLevel); + DexBackedDexFile dexFile = DexFileFactory.loadDexFile(dexFileFile, apiLevel, experimental); Iterable<String> bootClassPaths = Splitter.on(":").split("core.jar:ext.jar:framework.jar:android.policy.jar:services.jar"); - ClassPath classPath = ClassPath.fromClassPath(bootClassPathDirs, bootClassPaths, dexFile, apiLevel); + ClassPath classPath = ClassPath.fromClassPath(bootClassPathDirs, bootClassPaths, dexFile, apiLevel, experimental); FileOutputStream outStream = new FileOutputStream(outFile); for (ClassDef classDef: dexFile.getClasses()) { @@ -167,8 +171,14 @@ public class DumpVtables { .withArgName("API_LEVEL") .create("a"); + Option experimentalOption = OptionBuilder.withLongOpt("experimental") + .withDescription("Enable dumping experimental opcodes, that aren't necessarily " + + "supported by the android runtime yet.") + .create("X"); + options.addOption(classPathDirOption); options.addOption(outputFileOption); options.addOption(apiLevelOption); + options.addOption(experimentalOption); } } diff --git a/dexlib2/src/main/java/org/jf/dexlib2/builder/MutableMethodImplementation.java b/dexlib2/src/main/java/org/jf/dexlib2/builder/MutableMethodImplementation.java index f734e018..84981221 100644 --- a/dexlib2/src/main/java/org/jf/dexlib2/builder/MutableMethodImplementation.java +++ b/dexlib2/src/main/java/org/jf/dexlib2/builder/MutableMethodImplementation.java @@ -511,82 +511,90 @@ public class MutableMethodImplementation implements MethodImplementation { @Nonnull Instruction instruction) { switch (instruction.getOpcode().format) { case Format10t: - setInstruction(location, newBuilderInstruction10t(location.codeAddress, codeAddressToIndex, - (Instruction10t)instruction)); + setInstruction(location, newBuilderInstruction10t(location.codeAddress, + codeAddressToIndex, + (Instruction10t) instruction)); return; case Format10x: - setInstruction(location, newBuilderInstruction10x((Instruction10x)instruction)); + setInstruction(location, newBuilderInstruction10x((Instruction10x) instruction)); return; case Format11n: - setInstruction(location, newBuilderInstruction11n((Instruction11n)instruction)); + setInstruction(location, newBuilderInstruction11n((Instruction11n) instruction)); return; case Format11x: - setInstruction(location, newBuilderInstruction11x((Instruction11x)instruction)); + setInstruction(location, newBuilderInstruction11x((Instruction11x) instruction)); return; case Format12x: - setInstruction(location, newBuilderInstruction12x((Instruction12x)instruction)); + setInstruction(location, newBuilderInstruction12x((Instruction12x) instruction)); return; case Format20bc: - setInstruction(location, newBuilderInstruction20bc((Instruction20bc)instruction)); + setInstruction(location, newBuilderInstruction20bc((Instruction20bc) instruction)); return; case Format20t: - setInstruction(location, newBuilderInstruction20t(location.codeAddress, codeAddressToIndex, - (Instruction20t)instruction)); + setInstruction(location, newBuilderInstruction20t(location.codeAddress, + codeAddressToIndex, + (Instruction20t) instruction)); return; case Format21c: - setInstruction(location, newBuilderInstruction21c((Instruction21c)instruction)); + setInstruction(location, newBuilderInstruction21c((Instruction21c) instruction)); return; case Format21ih: - setInstruction(location, newBuilderInstruction21ih((Instruction21ih)instruction)); + setInstruction(location, newBuilderInstruction21ih((Instruction21ih) instruction)); return; case Format21lh: - setInstruction(location, newBuilderInstruction21lh((Instruction21lh)instruction)); + setInstruction(location, newBuilderInstruction21lh((Instruction21lh) instruction)); return; case Format21s: - setInstruction(location, newBuilderInstruction21s((Instruction21s)instruction)); + setInstruction(location, newBuilderInstruction21s((Instruction21s) instruction)); return; case Format21t: - setInstruction(location, newBuilderInstruction21t(location.codeAddress, codeAddressToIndex, - (Instruction21t)instruction)); + setInstruction(location, newBuilderInstruction21t(location.codeAddress, + codeAddressToIndex, + (Instruction21t) instruction)); return; case Format22b: - setInstruction(location, newBuilderInstruction22b((Instruction22b)instruction)); + setInstruction(location, newBuilderInstruction22b((Instruction22b) instruction)); return; case Format22c: - setInstruction(location, newBuilderInstruction22c((Instruction22c)instruction)); + setInstruction(location, newBuilderInstruction22c((Instruction22c) instruction)); return; case Format22s: - setInstruction(location, newBuilderInstruction22s((Instruction22s)instruction)); + setInstruction(location, newBuilderInstruction22s((Instruction22s) instruction)); return; case Format22t: - setInstruction(location, newBuilderInstruction22t(location.codeAddress, codeAddressToIndex, - (Instruction22t)instruction)); + setInstruction(location, newBuilderInstruction22t(location.codeAddress, + codeAddressToIndex, + (Instruction22t) instruction)); return; case Format22x: - setInstruction(location, newBuilderInstruction22x((Instruction22x)instruction)); + setInstruction(location, newBuilderInstruction22x((Instruction22x) instruction)); return; case Format23x: - setInstruction(location, newBuilderInstruction23x((Instruction23x)instruction)); + setInstruction(location, newBuilderInstruction23x((Instruction23x) instruction)); + return; + case Format25x: + setInstruction(location, newBuilderInstruction25x((Instruction25x) instruction)); return; case Format30t: - setInstruction(location, newBuilderInstruction30t(location.codeAddress, codeAddressToIndex, - (Instruction30t)instruction)); + setInstruction(location, newBuilderInstruction30t(location.codeAddress, + codeAddressToIndex, + (Instruction30t) instruction)); return; case Format31c: - setInstruction(location, newBuilderInstruction31c((Instruction31c)instruction)); + setInstruction(location, newBuilderInstruction31c((Instruction31c) instruction)); return; case Format31i: - setInstruction(location, newBuilderInstruction31i((Instruction31i)instruction)); + setInstruction(location, newBuilderInstruction31i((Instruction31i) instruction)); return; case Format31t: setInstruction(location, newBuilderInstruction31t(location, codeAddressToIndex, - (Instruction31t)instruction)); + (Instruction31t) instruction)); return; case Format32x: - setInstruction(location, newBuilderInstruction32x((Instruction32x)instruction)); + setInstruction(location, newBuilderInstruction32x((Instruction32x) instruction)); return; case Format35c: - setInstruction(location, newBuilderInstruction35c((Instruction35c)instruction)); + setInstruction(location, newBuilderInstruction35c((Instruction35c) instruction)); return; case Format3rc: setInstruction(location, newBuilderInstruction3rc((Instruction3rc)instruction)); @@ -821,6 +829,18 @@ public class MutableMethodImplementation implements MethodImplementation { } @Nonnull + private BuilderInstruction25x newBuilderInstruction25x(@Nonnull Instruction25x instruction) { + return new BuilderInstruction25x( + instruction.getOpcode(), + instruction.getParameterRegisterCount(), + instruction.getRegisterFixedC(), + instruction.getRegisterParameterD(), + instruction.getRegisterParameterE(), + instruction.getRegisterParameterF(), + instruction.getRegisterParameterG()); + } + + @Nonnull private BuilderInstruction3rc newBuilderInstruction3rc(@Nonnull Instruction3rc instruction) { return new BuilderInstruction3rc( instruction.getOpcode(), diff --git a/dexlib2/src/main/java/org/jf/dexlib2/builder/instruction/BuilderInstruction25x.java b/dexlib2/src/main/java/org/jf/dexlib2/builder/instruction/BuilderInstruction25x.java new file mode 100644 index 00000000..3783d2b6 --- /dev/null +++ b/dexlib2/src/main/java/org/jf/dexlib2/builder/instruction/BuilderInstruction25x.java @@ -0,0 +1,82 @@ +/* + * Copyright 2015, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "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 COPYRIGHT + * OWNER OR CONTRIBUTORS 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.jf.dexlib2.builder.instruction; + +import org.jf.dexlib2.Format; +import org.jf.dexlib2.Opcode; +import org.jf.dexlib2.builder.BuilderInstruction; +import org.jf.dexlib2.iface.instruction.formats.Instruction25x; +import org.jf.dexlib2.util.Preconditions; + +import javax.annotation.Nonnull; + +public class BuilderInstruction25x extends BuilderInstruction implements Instruction25x { + public static final Format FORMAT = Format.Format25x; + + protected final int parameterRegisterCount; + protected final int registerClosure; + protected final int registerD; + protected final int registerE; + protected final int registerF; + protected final int registerG; + + public BuilderInstruction25x(@Nonnull Opcode opcode, + int parameterRegisterCount, + int registerClosure, + int registerD, + int registerE, + int registerF, + int registerG) { + super(opcode); + this.parameterRegisterCount = + Preconditions.check25xParameterRegisterCount(parameterRegisterCount); + this.registerClosure = Preconditions.checkNibbleRegister(registerClosure); //at least 1 reg + this.registerD = (parameterRegisterCount>0) ? + Preconditions.checkNibbleRegister(registerD) : 0; + this.registerE = (parameterRegisterCount>1) ? + Preconditions.checkNibbleRegister(registerE) : 0; + this.registerF = (parameterRegisterCount>2) ? + Preconditions.checkNibbleRegister(registerF) : 0; + this.registerG = (parameterRegisterCount>3) ? + Preconditions.checkNibbleRegister(registerG) : 0; + } + + @Override public int getRegisterCount() { return parameterRegisterCount + 1; } + @Override public int getParameterRegisterCount() { return parameterRegisterCount; } + @Override public int getRegisterFixedC() { return registerClosure; } + @Override public int getRegisterParameterD() { return registerD; } + @Override public int getRegisterParameterE() { return registerE; } + @Override public int getRegisterParameterF() { return registerF; } + @Override public int getRegisterParameterG() { return registerG; } + + @Override public Format getFormat() { return FORMAT; } +} diff --git a/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/instruction/DexBackedInstruction.java b/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/instruction/DexBackedInstruction.java index a4c29990..ac82f4b4 100644 --- a/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/instruction/DexBackedInstruction.java +++ b/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/instruction/DexBackedInstruction.java @@ -115,6 +115,8 @@ public abstract class DexBackedInstruction implements Instruction { return new DexBackedInstruction22x(dexFile, opcode, instructionStartOffset); case Format23x: return new DexBackedInstruction23x(dexFile, opcode, instructionStartOffset); + case Format25x: + return new DexBackedInstruction25x(dexFile, opcode, instructionStartOffset); case Format30t: return new DexBackedInstruction30t(dexFile, opcode, instructionStartOffset); case Format31c: diff --git a/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/instruction/DexBackedInstruction25x.java b/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/instruction/DexBackedInstruction25x.java new file mode 100644 index 00000000..80fb8767 --- /dev/null +++ b/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/instruction/DexBackedInstruction25x.java @@ -0,0 +1,83 @@ +/* + * Copyright 2015, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "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 COPYRIGHT + * OWNER OR CONTRIBUTORS 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.jf.dexlib2.dexbacked.instruction; + +import org.jf.dexlib2.Opcode; +import org.jf.dexlib2.dexbacked.DexBackedDexFile; +import org.jf.dexlib2.iface.instruction.formats.Instruction25x; +import org.jf.util.NibbleUtils; + +import javax.annotation.Nonnull; + +public class DexBackedInstruction25x extends DexBackedInstruction implements Instruction25x { + public DexBackedInstruction25x(@Nonnull DexBackedDexFile dexFile, + @Nonnull Opcode opcode, + int instructionStart) { + super(dexFile, opcode, instructionStart); + } + + @Override + public int getRegisterCount() { + return getParameterRegisterCount() + 1; + } + + @Override + public int getParameterRegisterCount() { + return NibbleUtils.extractHighUnsignedNibble(dexFile.readUbyte(instructionStart + 1)); + } + + @Override + public int getRegisterFixedC() { + return NibbleUtils.extractLowUnsignedNibble(dexFile.readUbyte(instructionStart + 2)); + } + + @Override + public int getRegisterParameterD() { + return NibbleUtils.extractHighUnsignedNibble(dexFile.readUbyte(instructionStart + 2)); + } + + @Override + public int getRegisterParameterE() { + return NibbleUtils.extractLowUnsignedNibble(dexFile.readUbyte(instructionStart + 3)); + } + + @Override + public int getRegisterParameterF() { + return NibbleUtils.extractHighUnsignedNibble(dexFile.readUbyte(instructionStart + 3)); + } + + @Override + public int getRegisterParameterG() { + return NibbleUtils.extractLowUnsignedNibble(dexFile.readUbyte(instructionStart + 1)); + } + +} diff --git a/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/raw/CodeItem.java b/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/raw/CodeItem.java index c3946976..9c79e270 100644 --- a/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/raw/CodeItem.java +++ b/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/raw/CodeItem.java @@ -129,6 +129,9 @@ public class CodeItem { case Format10x: annotateInstruction10x(out, instruction); break; + case Format25x: + annotateInstruction25x(out, (Instruction25x) instruction); + break; case Format35c: annotateInstruction35c(out, (Instruction35c)instruction); break; @@ -282,6 +285,30 @@ public class CodeItem { instruction.getOpcode().name, Joiner.on(", ").join(args), reference)); } + private void annotateInstruction25x(@Nonnull AnnotatedBytes out, + @Nonnull Instruction25x instruction) { + List<String> args = Lists.newArrayList(); + + int registerCount = instruction.getRegisterCount(); //at least 1. + if (registerCount == 2) { + args.add(formatRegister(instruction.getRegisterParameterD())); + } else if (registerCount == 3) { + args.add(formatRegister(instruction.getRegisterParameterD())); + args.add(formatRegister(instruction.getRegisterParameterE())); + } else if (registerCount == 4) { + args.add(formatRegister(instruction.getRegisterParameterD())); + args.add(formatRegister(instruction.getRegisterParameterE())); + args.add(formatRegister(instruction.getRegisterParameterF())); + } else if (registerCount == 5) { + args.add(formatRegister(instruction.getRegisterParameterD())); + args.add(formatRegister(instruction.getRegisterParameterE())); + args.add(formatRegister(instruction.getRegisterParameterF())); + args.add(formatRegister(instruction.getRegisterParameterG())); + } + out.annotate(6, String.format("%s %s, {%s}", + instruction.getOpcode().name, instruction.getRegisterFixedC(), Joiner.on(", ").join(args))); + } + private void annotateInstruction3rc(@Nonnull AnnotatedBytes out, @Nonnull Instruction3rc instruction) { int startRegister = instruction.getStartRegister(); int endRegister = startRegister + instruction.getRegisterCount() - 1; diff --git a/dexlib2/src/main/java/org/jf/dexlib2/iface/Annotatable.java b/dexlib2/src/main/java/org/jf/dexlib2/iface/Annotatable.java new file mode 100644 index 00000000..f16c207b --- /dev/null +++ b/dexlib2/src/main/java/org/jf/dexlib2/iface/Annotatable.java @@ -0,0 +1,49 @@ +/* + * Copyright 2015, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "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 COPYRIGHT + * OWNER OR CONTRIBUTORS 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.jf.dexlib2.iface; + +import javax.annotation.Nonnull; +import java.util.Set; + +/** + * This class represents an object that can have Annotations applied to it + */ +public interface Annotatable { + /** + * Gets a set of the annotations that are applied to this object. + * + * The annotations in the returned set are guaranteed to have unique types. + * + * @return A set of the annotations that are applied to this object + */ + @Nonnull Set<? extends Annotation> getAnnotations(); +} diff --git a/dexlib2/src/main/java/org/jf/dexlib2/iface/Annotation.java b/dexlib2/src/main/java/org/jf/dexlib2/iface/Annotation.java index 251ac123..7c107a45 100644 --- a/dexlib2/src/main/java/org/jf/dexlib2/iface/Annotation.java +++ b/dexlib2/src/main/java/org/jf/dexlib2/iface/Annotation.java @@ -55,7 +55,7 @@ public interface Annotation extends BasicAnnotation, Comparable<Annotation> { * * @return The type of this annotation */ - @Nonnull String getType(); + @Nonnull @Override String getType(); /** * Gets a set of the name/value elements associated with this annotation. @@ -64,7 +64,7 @@ public interface Annotation extends BasicAnnotation, Comparable<Annotation> { * * @return A set of AnnotationElements */ - @Nonnull Set<? extends AnnotationElement> getElements(); + @Nonnull @Override Set<? extends AnnotationElement> getElements(); /** * Returns a hashcode for this Annotation. diff --git a/dexlib2/src/main/java/org/jf/dexlib2/iface/ClassDef.java b/dexlib2/src/main/java/org/jf/dexlib2/iface/ClassDef.java index 73ab8690..31d7fb99 100644 --- a/dexlib2/src/main/java/org/jf/dexlib2/iface/ClassDef.java +++ b/dexlib2/src/main/java/org/jf/dexlib2/iface/ClassDef.java @@ -43,7 +43,7 @@ import java.util.Set; * It also acts as a TypeReference to itself. Any equality/comparison is based on its identity as a TypeReference, * and shouldn't take into account anything other than the type of this class. */ -public interface ClassDef extends TypeReference { +public interface ClassDef extends TypeReference, Annotatable { /** * Gets the class type. * @@ -95,7 +95,7 @@ public interface ClassDef extends TypeReference { * * @return A set of the annotations that are applied to this class */ - @Nonnull Set<? extends Annotation> getAnnotations(); + @Override @Nonnull Set<? extends Annotation> getAnnotations(); /** * Gets the static fields that are defined by this class. diff --git a/dexlib2/src/main/java/org/jf/dexlib2/iface/Field.java b/dexlib2/src/main/java/org/jf/dexlib2/iface/Field.java index 7b4aad38..0091929b 100644 --- a/dexlib2/src/main/java/org/jf/dexlib2/iface/Field.java +++ b/dexlib2/src/main/java/org/jf/dexlib2/iface/Field.java @@ -44,27 +44,27 @@ import java.util.Set; * It also acts as a FieldReference to itself. Any equality/comparison is based on its identity as a FieldReference, * and shouldn't take into account any non-FieldReference specifics of this field. */ -public interface Field extends FieldReference { +public interface Field extends FieldReference, Member { /** * Gets the type of the class that defines this field. * * @return The type of the class that defines this field */ - @Nonnull String getDefiningClass(); + @Override @Nonnull String getDefiningClass(); /** * Gets the name of this field. * * @return The name of this field */ - @Nonnull String getName(); + @Override @Nonnull String getName(); /** * Gets the type of this field. * * @return The type of this field */ - @Nonnull String getType(); + @Override @Nonnull String getType(); /** * Gets the access flags for this field. @@ -73,7 +73,7 @@ public interface Field extends FieldReference { * * @return The access flags for this field */ - int getAccessFlags(); + @Override int getAccessFlags(); /** * Gets the initial value for this field, if available. @@ -92,5 +92,5 @@ public interface Field extends FieldReference { * * @return A set of the annotations that are applied to this field */ - @Nonnull Set<? extends Annotation> getAnnotations(); + @Override @Nonnull Set<? extends Annotation> getAnnotations(); } diff --git a/dexlib2/src/main/java/org/jf/dexlib2/iface/Member.java b/dexlib2/src/main/java/org/jf/dexlib2/iface/Member.java new file mode 100644 index 00000000..343c4032 --- /dev/null +++ b/dexlib2/src/main/java/org/jf/dexlib2/iface/Member.java @@ -0,0 +1,63 @@ +/* + * Copyright 2015, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "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 COPYRIGHT + * OWNER OR CONTRIBUTORS 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.jf.dexlib2.iface; + +import javax.annotation.Nonnull; + +/** + * This class represents a generic class member + */ +public interface Member extends Annotatable { + /** + * Gets the type of the class that defines this member. + * + * @return The type of the class that defines this member + */ + @Nonnull String getDefiningClass(); + + /** + * Gets the name of this member. + * + * @return The name of this field + */ + @Nonnull String getName(); + + /** + * Gets the access flags for this member. + * + * This will be a combination of the AccessFlags.* flags that are marked as compatible for use with this type + * of member. + * + * @return The access flags for this member + */ + int getAccessFlags(); +} diff --git a/dexlib2/src/main/java/org/jf/dexlib2/iface/Method.java b/dexlib2/src/main/java/org/jf/dexlib2/iface/Method.java index c9080e0f..5d3796ff 100644 --- a/dexlib2/src/main/java/org/jf/dexlib2/iface/Method.java +++ b/dexlib2/src/main/java/org/jf/dexlib2/iface/Method.java @@ -44,7 +44,7 @@ import java.util.Set; * It also acts as a MethodReference to itself. Any equality/comparison is based on its identity as a MethodReference, * and shouldn't take into account any non-MethodReference specifics of this method. */ -public interface Method extends MethodReference { +public interface Method extends MethodReference, Member { /** * Gets the type of the class that defines this method. * @@ -86,7 +86,7 @@ public interface Method extends MethodReference { * * @return The access flags for this method */ - int getAccessFlags(); + @Override int getAccessFlags(); /** * Gets a set of the annotations that are applied to this method. @@ -95,7 +95,7 @@ public interface Method extends MethodReference { * * @return A set of the annotations that are applied to this method */ - @Nonnull Set<? extends Annotation> getAnnotations(); + @Override @Nonnull Set<? extends Annotation> getAnnotations(); /** * Gets a MethodImplementation object that defines the implementation of the method. diff --git a/dexlib2/src/main/java/org/jf/dexlib2/iface/instruction/OneFixedFourParameterRegisterInstruction.java b/dexlib2/src/main/java/org/jf/dexlib2/iface/instruction/OneFixedFourParameterRegisterInstruction.java new file mode 100644 index 00000000..c03bff77 --- /dev/null +++ b/dexlib2/src/main/java/org/jf/dexlib2/iface/instruction/OneFixedFourParameterRegisterInstruction.java @@ -0,0 +1,47 @@ +/* + * Copyright 2015, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "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 COPYRIGHT + * OWNER OR CONTRIBUTORS 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.jf.dexlib2.iface.instruction; + +public interface OneFixedFourParameterRegisterInstruction extends VariableRegisterInstruction { + int getRegisterFixedC(); + int getRegisterParameterD(); + int getRegisterParameterE(); + int getRegisterParameterF(); + int getRegisterParameterG(); + + /** Returns the count of just the parameter register counts; in range of [0, 4] */ + int getParameterRegisterCount(); + + /** Includes the total sum of both fixed and parameter register counts; at least 1 */ + @Override + int getRegisterCount(); +} diff --git a/dexlib2/src/main/java/org/jf/dexlib2/iface/instruction/formats/Instruction25x.java b/dexlib2/src/main/java/org/jf/dexlib2/iface/instruction/formats/Instruction25x.java new file mode 100644 index 00000000..51df2dee --- /dev/null +++ b/dexlib2/src/main/java/org/jf/dexlib2/iface/instruction/formats/Instruction25x.java @@ -0,0 +1,37 @@ +/* + * Copyright 2015, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "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 COPYRIGHT + * OWNER OR CONTRIBUTORS 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.jf.dexlib2.iface.instruction.formats; + +import org.jf.dexlib2.iface.instruction.OneFixedFourParameterRegisterInstruction; + +public interface Instruction25x extends OneFixedFourParameterRegisterInstruction { +} diff --git a/dexlib2/src/main/java/org/jf/dexlib2/immutable/instruction/ImmutableInstruction.java b/dexlib2/src/main/java/org/jf/dexlib2/immutable/instruction/ImmutableInstruction.java index 432f1930..ed50ef5b 100644 --- a/dexlib2/src/main/java/org/jf/dexlib2/immutable/instruction/ImmutableInstruction.java +++ b/dexlib2/src/main/java/org/jf/dexlib2/immutable/instruction/ImmutableInstruction.java @@ -97,6 +97,8 @@ public abstract class ImmutableInstruction implements Instruction { return ImmutableInstruction22x.of((Instruction22x)instruction); case Format23x: return ImmutableInstruction23x.of((Instruction23x)instruction); + case Format25x: + return ImmutableInstruction25x.of((Instruction25x) instruction); case Format30t: return ImmutableInstruction30t.of((Instruction30t)instruction); case Format31c: diff --git a/dexlib2/src/main/java/org/jf/dexlib2/immutable/instruction/ImmutableInstruction25x.java b/dexlib2/src/main/java/org/jf/dexlib2/immutable/instruction/ImmutableInstruction25x.java new file mode 100644 index 00000000..2f31eaea --- /dev/null +++ b/dexlib2/src/main/java/org/jf/dexlib2/immutable/instruction/ImmutableInstruction25x.java @@ -0,0 +1,97 @@ +/* + * Copyright 2015, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "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 COPYRIGHT + * OWNER OR CONTRIBUTORS 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.jf.dexlib2.immutable.instruction; + +import org.jf.dexlib2.Format; +import org.jf.dexlib2.Opcode; +import org.jf.dexlib2.iface.instruction.formats.Instruction25x; +import org.jf.dexlib2.util.Preconditions; + +import javax.annotation.Nonnull; + +public class ImmutableInstruction25x extends ImmutableInstruction implements Instruction25x { + public static final Format FORMAT = Format.Format25x; + + protected final int parameterRegisterCount; + protected final int registerClosure; + protected final int registerD; + protected final int registerE; + protected final int registerF; + protected final int registerG; + + public ImmutableInstruction25x(@Nonnull Opcode opcode, + int parameterRegisterCount, + int registerClosure, + int registerD, + int registerE, + int registerF, + int registerG) { + super(opcode); + this.parameterRegisterCount = + Preconditions.check25xParameterRegisterCount(parameterRegisterCount); + this.registerClosure = Preconditions.checkNibbleRegister(registerClosure); + this.registerD = (parameterRegisterCount>0) ? + Preconditions.checkNibbleRegister(registerD) : 0; + this.registerE = (parameterRegisterCount>1) ? + Preconditions.checkNibbleRegister(registerE) : 0; + this.registerF = (parameterRegisterCount>2) ? + Preconditions.checkNibbleRegister(registerF) : 0; + this.registerG = (parameterRegisterCount>3) ? + Preconditions.checkNibbleRegister(registerG) : 0; + } + + public static ImmutableInstruction25x of(Instruction25x instruction) { + if (instruction instanceof ImmutableInstruction25x) { + return (ImmutableInstruction25x)instruction; + } + return new ImmutableInstruction25x( + instruction.getOpcode(), + instruction.getRegisterCount(), + instruction.getRegisterFixedC(), + instruction.getRegisterParameterD(), + instruction.getRegisterParameterE(), + instruction.getRegisterParameterF(), + instruction.getRegisterParameterG()); + } + + + @Override public int getParameterRegisterCount() { return parameterRegisterCount; } + @Override public int getRegisterCount() { return parameterRegisterCount + 1; } + + @Override public int getRegisterFixedC() { return registerClosure; } + @Override public int getRegisterParameterD() { return registerD; } + @Override public int getRegisterParameterE() { return registerE; } + @Override public int getRegisterParameterF() { return registerF; } + @Override public int getRegisterParameterG() { return registerG; } + + @Override public Format getFormat() { return FORMAT; } +} diff --git a/dexlib2/src/main/java/org/jf/dexlib2/util/Preconditions.java b/dexlib2/src/main/java/org/jf/dexlib2/util/Preconditions.java index ab86b652..51c083ca 100644 --- a/dexlib2/src/main/java/org/jf/dexlib2/util/Preconditions.java +++ b/dexlib2/src/main/java/org/jf/dexlib2/util/Preconditions.java @@ -133,6 +133,15 @@ public class Preconditions { return registerCount; } + public static int check25xParameterRegisterCount(int registerCount) { + if (registerCount < 0 || registerCount > 4) { + throw new IllegalArgumentException( + String.format("Invalid parameter register count: %d. " + + "Must be between 0 and 4, inclusive.", registerCount)); + } + return registerCount; + } + public static int checkRegisterRangeCount(int registerCount) { if ((registerCount & 0xFFFFFF00) != 0) { throw new IllegalArgumentException( diff --git a/dexlib2/src/main/java/org/jf/dexlib2/writer/DexWriter.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/DexWriter.java index 87a2ae6b..0650ab3c 100644 --- a/dexlib2/src/main/java/org/jf/dexlib2/writer/DexWriter.java +++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/DexWriter.java @@ -572,10 +572,12 @@ public abstract class DexWriter< } } - private void writeAnnotationSets(@Nonnull DexDataWriter writer) throws IOException { writer.align(); annotationSetSectionOffset = writer.getPosition(); + if (shouldCreateEmptyAnnotationSet()) { + writer.writeInt(0); + } for (Map.Entry<? extends AnnotationSetKey, Integer> entry: annotationSetSection.getItems()) { Collection<? extends AnnotationKey> annotations = Ordering.from(BaseAnnotation.BY_TYPE) .immutableSortedCopy(annotationSetSection.getAnnotations(entry.getKey())); @@ -613,6 +615,8 @@ public abstract class DexWriter< for (AnnotationSetKey annotationSetKey: parameterAnnotations) { if (annotationSetSection.getAnnotations(annotationSetKey).size() > 0) { writer.writeInt(annotationSetSection.getItemOffset(annotationSetKey)); + } else if (shouldCreateEmptyAnnotationSet()) { + writer.writeInt(annotationSetSectionOffset); } else { writer.writeInt(NO_OFFSET); } @@ -971,6 +975,9 @@ public abstract class DexWriter< case Format23x: instructionWriter.write((Instruction23x)instruction); break; + case Format25x: + instructionWriter.write((Instruction25x)instruction); + break; case Format30t: instructionWriter.write((Instruction30t)instruction); break; @@ -1115,7 +1122,7 @@ public abstract class DexWriter< if (annotationSection.getItems().size() > 0) { numItems++; } - if (annotationSetSection.getItems().size() > 0) { + if (annotationSetSection.getItems().size() > 0 || shouldCreateEmptyAnnotationSet()) { numItems++; } if (numAnnotationSetRefItems > 0) { @@ -1163,8 +1170,8 @@ public abstract class DexWriter< writeMapItem(writer, ItemType.TYPE_LIST, typeListSection.getItems().size(), typeListSectionOffset); writeMapItem(writer, ItemType.ENCODED_ARRAY_ITEM, numEncodedArrayItems, encodedArraySectionOffset); writeMapItem(writer, ItemType.ANNOTATION_ITEM, annotationSection.getItems().size(), annotationSectionOffset); - writeMapItem(writer, ItemType.ANNOTATION_SET_ITEM, annotationSetSection.getItems().size(), - annotationSetSectionOffset); + writeMapItem(writer, ItemType.ANNOTATION_SET_ITEM, + annotationSetSection.getItems().size() + (shouldCreateEmptyAnnotationSet() ? 1 : 0), annotationSetSectionOffset); writeMapItem(writer, ItemType.ANNOTATION_SET_REF_LIST, numAnnotationSetRefItems, annotationSetRefSectionOffset); writeMapItem(writer, ItemType.ANNOTATION_DIRECTORY_ITEM, numAnnotationDirectoryItems, annotationDirectorySectionOffset); @@ -1226,4 +1233,11 @@ public abstract class DexWriter< writer.writeInt(0); } } + + private boolean shouldCreateEmptyAnnotationSet() { + // Workaround for a crash in Dalvik VM before Jelly Bean MR1 (4.2) + // which is triggered by NO_OFFSET in parameter annotation list. + // (https://code.google.com/p/android/issues/detail?id=35304) + return (api < 17); + } } diff --git a/dexlib2/src/main/java/org/jf/dexlib2/writer/EncodedValueWriter.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/EncodedValueWriter.java index 53d77b39..def326c6 100644 --- a/dexlib2/src/main/java/org/jf/dexlib2/writer/EncodedValueWriter.java +++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/EncodedValueWriter.java @@ -31,7 +31,9 @@ package org.jf.dexlib2.writer; +import com.google.common.collect.Ordering; import org.jf.dexlib2.ValueType; +import org.jf.dexlib2.base.BaseAnnotationElement; import org.jf.dexlib2.iface.reference.FieldReference; import org.jf.dexlib2.iface.reference.MethodReference; @@ -40,7 +42,8 @@ import java.io.IOException; import java.util.Collection; public abstract class EncodedValueWriter<StringKey, TypeKey, FieldRefKey extends FieldReference, - MethodRefKey extends MethodReference, AnnotationElement, EncodedValue> { + MethodRefKey extends MethodReference, AnnotationElement extends org.jf.dexlib2.iface.AnnotationElement, + EncodedValue> { @Nonnull private final DexDataWriter writer; @Nonnull private final StringSection<StringKey, ?> stringSection; @Nonnull private final TypeSection<?, TypeKey, ?> typeSection; @@ -70,7 +73,11 @@ public abstract class EncodedValueWriter<StringKey, TypeKey, FieldRefKey extends writer.writeEncodedValueHeader(ValueType.ANNOTATION, 0); writer.writeUleb128(typeSection.getItemIndex(annotationType)); writer.writeUleb128(elements.size()); - for (AnnotationElement element: elements) { + + Collection<? extends AnnotationElement> sortedElements = Ordering.from(BaseAnnotationElement.BY_NAME) + .immutableSortedCopy(elements); + + for (AnnotationElement element: sortedElements) { writer.writeUleb128(stringSection.getItemIndex(annotationSection.getElementName(element))); writeEncodedValue(annotationSection.getElementValue(element)); } diff --git a/dexlib2/src/main/java/org/jf/dexlib2/writer/InstructionWriter.java b/dexlib2/src/main/java/org/jf/dexlib2/writer/InstructionWriter.java index 17d31337..c9aa73a1 100644 --- a/dexlib2/src/main/java/org/jf/dexlib2/writer/InstructionWriter.java +++ b/dexlib2/src/main/java/org/jf/dexlib2/writer/InstructionWriter.java @@ -31,6 +31,8 @@ package org.jf.dexlib2.writer; +import com.google.common.collect.Ordering; +import com.google.common.primitives.Ints; import org.jf.dexlib2.ReferenceType; import org.jf.dexlib2.iface.instruction.ReferenceInstruction; import org.jf.dexlib2.iface.instruction.SwitchElement; @@ -43,6 +45,7 @@ import org.jf.util.ExceptionWithContext; import javax.annotation.Nonnull; import java.io.IOException; +import java.util.Comparator; import java.util.List; public class InstructionWriter<StringRef extends StringReference, TypeRef extends TypeReference, @@ -317,6 +320,19 @@ public class InstructionWriter<StringRef extends StringReference, TypeRef extend } } + public void write(@Nonnull Instruction25x instruction) { + try { + writer.write(instruction.getOpcode().value); + writer.write(packNibbles( + instruction.getRegisterParameterG(), instruction.getParameterRegisterCount())); + writer.write(packNibbles( + instruction.getRegisterFixedC(), instruction.getRegisterParameterD())); + writer.write(packNibbles( + instruction.getRegisterParameterE(), instruction.getRegisterParameterF())); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + } public void write(@Nonnull Instruction3rc instruction) { try { writer.write(instruction.getOpcode().value); @@ -378,7 +394,8 @@ public class InstructionWriter<StringRef extends StringReference, TypeRef extend try { writer.writeUbyte(0); writer.writeUbyte(instruction.getOpcode().value >> 8); - List<? extends SwitchElement> elements = instruction.getSwitchElements(); + List<? extends SwitchElement> elements = Ordering.from(switchElementComparator).immutableSortedCopy( + instruction.getSwitchElements()); writer.writeUshort(elements.size()); for (SwitchElement element: elements) { writer.writeInt(element.getKey()); @@ -391,6 +408,12 @@ public class InstructionWriter<StringRef extends StringReference, TypeRef extend } } + private final Comparator<SwitchElement> switchElementComparator = new Comparator<SwitchElement>() { + @Override public int compare(SwitchElement element1, SwitchElement element2) { + return Ints.compare(element1.getKey(), element2.getKey()); + } + }; + public void write(@Nonnull PackedSwitchPayload instruction) { try { writer.writeUbyte(0); diff --git a/dexlib2/src/accessorTest/java/org/jf/dexlib2/AccessorTest.java b/dexlib2/src/test/java/org/jf/dexlib2/AccessorTest.java index 13e7b30f..924d3fd3 100644 --- a/dexlib2/src/accessorTest/java/org/jf/dexlib2/AccessorTest.java +++ b/dexlib2/src/test/java/org/jf/dexlib2/AccessorTest.java @@ -79,7 +79,7 @@ public class AccessorTest { public void testAccessors() throws IOException { URL url = AccessorTest.class.getClassLoader().getResource("accessorTest.dex"); Assert.assertNotNull(url); - DexFile f = DexFileFactory.loadDexFile(url.getFile(), 15); + DexFile f = DexFileFactory.loadDexFile(url.getFile(), 15, false); SyntheticAccessorResolver sar = new SyntheticAccessorResolver(f.getClasses()); diff --git a/dexlib2/src/test/java/org/jf/dexlib2/analysis/CustomMethodInlineTableTest.java b/dexlib2/src/test/java/org/jf/dexlib2/analysis/CustomMethodInlineTableTest.java index 686b2101..65a82f05 100644 --- a/dexlib2/src/test/java/org/jf/dexlib2/analysis/CustomMethodInlineTableTest.java +++ b/dexlib2/src/test/java/org/jf/dexlib2/analysis/CustomMethodInlineTableTest.java @@ -69,7 +69,7 @@ public class CustomMethodInlineTableTest { DexFile dexFile = new ImmutableDexFile(ImmutableList.of(classDef)); ClassPath classPath = ClassPath.fromClassPath(ImmutableList.<String>of(), ImmutableList.<String>of(), dexFile, - 15); + 15, false); InlineMethodResolver inlineMethodResolver = new CustomInlineMethodResolver(classPath, "Lblah;->blah()V"); MethodAnalyzer methodAnalyzer = new MethodAnalyzer(classPath, method, inlineMethodResolver); @@ -96,7 +96,7 @@ public class CustomMethodInlineTableTest { DexFile dexFile = new ImmutableDexFile(ImmutableList.of(classDef)); ClassPath classPath = ClassPath.fromClassPath(ImmutableList.<String>of(), ImmutableList.<String>of(), dexFile, - 15); + 15, false); InlineMethodResolver inlineMethodResolver = new CustomInlineMethodResolver(classPath, "Lblah;->blah()V"); MethodAnalyzer methodAnalyzer = new MethodAnalyzer(classPath, method, inlineMethodResolver); @@ -123,7 +123,7 @@ public class CustomMethodInlineTableTest { DexFile dexFile = new ImmutableDexFile(ImmutableList.of(classDef)); ClassPath classPath = ClassPath.fromClassPath(ImmutableList.<String>of(), ImmutableList.<String>of(), dexFile, - 15); + 15, false); InlineMethodResolver inlineMethodResolver = new CustomInlineMethodResolver(classPath, "Lblah;->blah()V"); MethodAnalyzer methodAnalyzer = new MethodAnalyzer(classPath, method, inlineMethodResolver); diff --git a/dexlib2/src/test/java/org/jf/dexlib2/writer/DexWriterTest.java b/dexlib2/src/test/java/org/jf/dexlib2/writer/DexWriterTest.java index b2cc52db..8ba975a1 100644 --- a/dexlib2/src/test/java/org/jf/dexlib2/writer/DexWriterTest.java +++ b/dexlib2/src/test/java/org/jf/dexlib2/writer/DexWriterTest.java @@ -41,10 +41,12 @@ import org.jf.dexlib2.dexbacked.DexBackedDexFile; import org.jf.dexlib2.iface.Annotation; import org.jf.dexlib2.iface.AnnotationElement; import org.jf.dexlib2.iface.ClassDef; +import org.jf.dexlib2.iface.value.AnnotationEncodedValue; import org.jf.dexlib2.immutable.ImmutableAnnotation; import org.jf.dexlib2.immutable.ImmutableAnnotationElement; import org.jf.dexlib2.immutable.ImmutableClassDef; import org.jf.dexlib2.immutable.ImmutableDexFile; +import org.jf.dexlib2.immutable.value.ImmutableAnnotationEncodedValue; import org.jf.dexlib2.immutable.value.ImmutableNullEncodedValue; import org.jf.dexlib2.writer.io.MemoryDataStore; import org.jf.dexlib2.writer.pool.DexPool; @@ -75,7 +77,7 @@ public class DexWriterTest { throw new RuntimeException(ex); } - DexBackedDexFile dexFile = new DexBackedDexFile(new Opcodes(15), dataStore.getData()); + DexBackedDexFile dexFile = new DexBackedDexFile(new Opcodes(15, false), dataStore.getData()); ClassDef dbClassDef = Iterables.getFirst(dexFile.getClasses(), null); Assert.assertNotNull(dbClassDef); Annotation dbAnnotation = Iterables.getFirst(dbClassDef.getAnnotations(), null); @@ -87,4 +89,48 @@ public class DexWriterTest { Assert.assertEquals("blah", dbElements.get(0).getName()); Assert.assertEquals("zabaglione", dbElements.get(1).getName()); } + + @Test + public void testEncodedAnnotationElementOrder() { + // Elements are out of order wrt to the element name + ImmutableSet<ImmutableAnnotationElement> encodedElements = + ImmutableSet.of(new ImmutableAnnotationElement("zabaglione", ImmutableNullEncodedValue.INSTANCE), + new ImmutableAnnotationElement("blah", ImmutableNullEncodedValue.INSTANCE)); + + ImmutableAnnotationEncodedValue encodedAnnotations = + new ImmutableAnnotationEncodedValue("Lan/encoded/annotation", encodedElements); + + ImmutableSet<ImmutableAnnotationElement> elements = + ImmutableSet.of(new ImmutableAnnotationElement("encoded_annotation", encodedAnnotations)); + + ImmutableAnnotation annotation = new ImmutableAnnotation(AnnotationVisibility.RUNTIME, + "Lorg/test/anno;", elements); + + ImmutableClassDef classDef = new ImmutableClassDef("Lorg/test/blah;", + 0, "Ljava/lang/Object;", null, null, ImmutableSet.of(annotation), null, null); + + MemoryDataStore dataStore = new MemoryDataStore(); + + try { + DexPool.writeTo(dataStore, new ImmutableDexFile(ImmutableSet.of(classDef))); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + + DexBackedDexFile dexFile = new DexBackedDexFile(new Opcodes(15, false), dataStore.getData()); + ClassDef dbClassDef = Iterables.getFirst(dexFile.getClasses(), null); + Assert.assertNotNull(dbClassDef); + Annotation dbAnnotation = Iterables.getFirst(dbClassDef.getAnnotations(), null); + Assert.assertNotNull(dbAnnotation); + + AnnotationElement element = Iterables.getFirst(dbAnnotation.getElements(), null); + AnnotationEncodedValue dbAnnotationEncodedValue = (AnnotationEncodedValue)element.getValue(); + + List<AnnotationElement> dbElements = Lists.newArrayList(dbAnnotationEncodedValue.getElements()); + + // Ensure that the elements were written out in sorted order + Assert.assertEquals(2, dbElements.size()); + Assert.assertEquals("blah", dbElements.get(0).getName()); + Assert.assertEquals("zabaglione", dbElements.get(1).getName()); + } } diff --git a/dexlib2/src/test/java/org/jf/dexlib2/writer/JumboStringConversionTest.java b/dexlib2/src/test/java/org/jf/dexlib2/writer/JumboStringConversionTest.java index 38015e74..7e504a17 100644 --- a/dexlib2/src/test/java/org/jf/dexlib2/writer/JumboStringConversionTest.java +++ b/dexlib2/src/test/java/org/jf/dexlib2/writer/JumboStringConversionTest.java @@ -92,7 +92,7 @@ public class JumboStringConversionTest { MemoryDataStore dexStore = new MemoryDataStore(); dexBuilder.writeTo(dexStore); - DexBackedDexFile dexFile = new DexBackedDexFile(new Opcodes(15), dexStore.getData()); + DexBackedDexFile dexFile = new DexBackedDexFile(new Opcodes(15, false), dexStore.getData()); ClassDef classDef = Iterables.getFirst(dexFile.getClasses(), null); Assert.assertNotNull(classDef); @@ -189,7 +189,7 @@ public class JumboStringConversionTest { MemoryDataStore dexStore = new MemoryDataStore(); dexBuilder.writeTo(dexStore); - DexBackedDexFile dexFile = new DexBackedDexFile(new Opcodes(15), dexStore.getData()); + DexBackedDexFile dexFile = new DexBackedDexFile(new Opcodes(15, false), dexStore.getData()); ClassDef classDef = Iterables.getFirst(dexFile.getClasses(), null); Assert.assertNotNull(classDef); diff --git a/examples/HelloWorldLambda/HelloWorldFunctionalInterface.smali b/examples/HelloWorldLambda/HelloWorldFunctionalInterface.smali new file mode 100644 index 00000000..c2ada47d --- /dev/null +++ b/examples/HelloWorldLambda/HelloWorldFunctionalInterface.smali @@ -0,0 +1,8 @@ +# Functional interface used by HelloWorld.smali +# Required in order to reify the lambda with create-lambda or unbox-lambda instructions + +.class public abstract interface LHelloWorldFunctionalInterface; +.super Ljava/lang/Object; + +.method public abstract applyFourStrings(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V +.end method diff --git a/examples/HelloWorldLambda/HelloWorldLambda.smali b/examples/HelloWorldLambda/HelloWorldLambda.smali new file mode 100644 index 00000000..36c8e49c --- /dev/null +++ b/examples/HelloWorldLambda/HelloWorldLambda.smali @@ -0,0 +1,57 @@ +.class public LHelloWorldLambda; + +#Ye olde hello world application (with lambdas!) +#To assemble and run this on a phone or emulator: +# +#java -jar smali.jar -o classes.dex HelloWorldLambda.smali HelloWorldFunctionalInterface.smali +#zip HelloWorld.zip classes.dex +#adb push HelloWorld.zip /data/local +#adb shell dalvikvm -cp /data/local/HelloWorld.zip HelloWorld +# +#if you get out of memory type errors when running smali.jar, try +#java -Xmx512m -jar smali.jar HelloWorldLambda.smali +#instead + +.super Ljava/lang/Object; + +.method public static main([Ljava/lang/String;)V + .registers 9 # 1 parameter, 8 locals + + sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream; + + const-string v1, "Hello World!" + const-string v2, "How" # vD + const-string v3, "are" # vE + const-string v4, "you" # vF + const-string v5, "doing?" # vG + + capture-variable v1, "helloworld" + + # TODO: do I need to pass the type of the lambda's functional interface here as a type id? + create-lambda v1, LHelloWorldLambda;->doHelloWorld(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + # Method descriptor is not required here, because only the single-abstract method is ever invoked. + invoke-lambda v1, {v2, v3, v4, v5} + + box-lambda v6, v1 # The type of v6 is now 'LHelloWorldFunctionalInterface;' + invoke-virtual {v6, v2, v3, v4, v5}, LHelloWorldFunctionalInterface;->applyFourStrings(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + + # FIXME: should be \HelloWorldFunctionalInterface; instead of L...; + + # TODO: do we really need the type descriptor here at all? + unbox-lambda v7, v6, LHelloWorldFunctionalInterface; # The type of v7 is now \HelloWorldFunctionalInterface; + invoke-lambda v7, {v2, v3, v4, v5} + + return-void +.end method + +.method public static doHelloWorld(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + .registers 6 # 4 parameters, 2 locals + + # This helloworld variable is brought to you by the variable liberation front + liberate-variable v0, p0, "helloworld" + + sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream; + invoke-virtual {v1, v0}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V + + return-void +.end method diff --git a/gradle.properties b/gradle.properties index 335740c6..567bbb91 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1,2 @@ -org.gradle.daemon=true
\ No newline at end of file +org.gradle.daemon=true +org.gradle.parallel=true
\ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar Binary files differindex 58385981..085a1cdc 100644 --- a/gradle/wrapper/gradle-wrapper.jar +++ b/gradle/wrapper/gradle-wrapper.jar diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 3f70b064..7e793277 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Sun Feb 02 12:47:14 PST 2014 +#Sun Mar 01 19:20:47 PST 2015 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=http\://services.gradle.org/distributions/gradle-1.10-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.3-all.zip diff --git a/smali/build.gradle b/smali/build.gradle index 0f43d756..0e5cbf2d 100644 --- a/smali/build.gradle +++ b/smali/build.gradle @@ -29,25 +29,28 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -configurations { - antlr3 - jflex - proguard -} - -ext.antlrSource = 'src/main/antlr3' -ext.antlrOutput = file("${buildDir}/generated-sources/antlr3") +apply plugin: 'antlr' +apply plugin: 'jflex' -ext.jflexSource = "src/main/jflex" -ext.jflexOutput = file("${buildDir}/generated-sources/jflex") - -ext.testAntlrSource = 'src/test/antlr3' -ext.testAntlrOutput = file("${buildDir}/generated-test-sources/antlr3") +buildscript { + repositories { + mavenCentral() + } + dependencies { + classpath depends.jflex_plugin + classpath depends.proguard_gradle + } +} -sourceSets.main.java.srcDir antlrOutput -sourceSets.main.java.srcDir jflexOutput +configurations { + // Remove the full antlr library that's added by the antlr plugin. We manually + // add the smaller antlr_runtime library instead + compile.exclude group: 'org.antlr', module: 'antlr' -sourceSets.test.java.srcDir testAntlrOutput + // The jflex lexer doesn't have any runtime dependencies, so remove the dependency + // that gets added by the jflex plugin + compile.exclude group: 'de.jflex', module: 'jflex' +} idea { module { @@ -73,68 +76,10 @@ dependencies { testCompile depends.junit - antlr3 depends.antlr + antlr depends.antlr jflex depends.jflex - proguard depends.proguard } -task generateParserAntlrSource(type: JavaExec) { - inputs.dir file(antlrSource) - outputs.dir file(antlrOutput) - - mkdir(antlrOutput) - def grammars = fileTree(antlrSource).include('**/smaliParser.g') - - classpath = configurations.antlr3 - main = 'org.antlr.Tool' - args '-fo', relativePath("${antlrOutput}/org/jf/smali") - args grammars.files -} - -task generateTreeWalkerAntlrSource(type: JavaExec) { - inputs.dir file(antlrSource) - outputs.dir file(antlrOutput) - - mkdir(antlrOutput) - def grammars = fileTree(antlrSource).include('**/smaliTreeWalker.g') - - classpath = configurations.antlr3 - main = 'org.antlr.Tool' - args '-fo', relativePath("${antlrOutput}/org/jf/smali") - args grammars.files -} - -task generateTestAntlrSource(type: JavaExec) { - inputs.dir file(testAntlrSource) - outputs.dir file(testAntlrOutput) - - mkdir(testAntlrOutput) - def grammars = fileTree(testAntlrSource).include('**/*.g') - - classpath = configurations.antlr3 - main = 'org.antlr.Tool' - args '-fo', relativePath("${testAntlrOutput}/org/jf/smali") - args grammars.files.join(' ') -} - -task generateJflexSource(type: JavaExec) { - inputs.dir file(jflexSource) - outputs.dir file(jflexOutput) - - mkdir(jflexOutput) - def grammars = fileTree(jflexSource).include('**/*.flex') - - classpath = configurations.jflex - main = 'JFlex.Main' - args '-q' - args '-d', relativePath("${jflexOutput}/org/jf/smali") - args grammars.files.join(' ') - environment LC_ALL: "en_US" -} - -compileJava.dependsOn generateParserAntlrSource, generateTreeWalkerAntlrSource, generateJflexSource -compileTestJava.dependsOn generateTestAntlrSource - processResources.inputs.property('version', version) processResources.expand('version': version) @@ -149,17 +94,29 @@ task fatJar(type: Jar, dependsOn: jar) { from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } } manifest { - attributes("Main-Class": "org.jf.smali.main") + attributes('Main-Class': 'org.jf.smali.main') } doLast { - if (!System.getProperty("os.name").toLowerCase().contains("windows")) { + if (!System.getProperty('os.name').toLowerCase().contains('windows')) { ant.symlink(link: file("${destinationDir}/smali.jar"), resource: archivePath, overwrite: true) } } } tasks.getByPath('build').dependsOn(fatJar) +generateTestGrammarSource { + outputDirectory = new File(outputDirectory, 'org/jf/smali') +} + +generateGrammarSource { + outputDirectory = new File(outputDirectory, 'org/jf/smali') +} + +generateJFlexSource { + outputDirectory = new File(outputDirectory, 'org/jf/smali') +} + uploadArchives { repositories.mavenDeployer { pom.project { @@ -171,22 +128,23 @@ uploadArchives { } } -task proguard(type: JavaExec, dependsOn: fatJar) { - def outFile = fatJar.destinationDir.getPath() + '/' + fatJar.baseName + '-' + fatJar.version + '-small' + '.' + fatJar.extension - inputs.file fatJar.archivePath - outputs.file outFile - - classpath = configurations.proguard - main = 'proguard.ProGuard' - args "-injars ${fatJar.archivePath}(!**/TestStringTemplate*.class)" - args "-outjars ${outFile}" - args "-libraryjars ${System.properties['java.home']}/lib/rt.jar" - args '-dontobfuscate' - args '-dontoptimize' - args '-keep public class org.jf.smali.main { public static void main(java.lang.String[]); }' - args '-keepclassmembers enum * { public static **[] values(); public static ** valueOf(java.lang.String); }' - args '-dontwarn com.google.common.**' - args '-dontnote com.google.common.**' +task proguard(type: proguard.gradle.ProGuardTask, dependsOn: fatJar) { + def outFile = fatJar.destinationDir.getPath() + '/' + fatJar.baseName + '-' + + fatJar.version + '-small' + '.' + fatJar.extension + + injars fatJar.archivePath + outjars outFile + + libraryjars "${System.properties['java.home']}/lib/rt.jar" + + dontobfuscate + dontoptimize + + keep 'public class org.jf.smali.main { public static void main(java.lang.String[]); }' + keepclassmembers 'enum * { public static **[] values(); public static ** valueOf(java.lang.String); }' + + dontwarn 'com.google.common.**' + dontnote 'com.google.common.**' } tasks.getByPath(':release').dependsOn(proguard) diff --git a/smali/src/main/antlr3/smaliParser.g b/smali/src/main/antlr/smaliParser.g index d057b4a4..6d07452c 100644 --- a/smali/src/main/antlr3/smaliParser.g +++ b/smali/src/main/antlr/smaliParser.g @@ -86,6 +86,8 @@ tokens { INSTRUCTION_FORMAT21c_FIELD_ODEX; INSTRUCTION_FORMAT21c_STRING; INSTRUCTION_FORMAT21c_TYPE; + INSTRUCTION_FORMAT21c_LAMBDA; + INSTRUCTION_FORMAT21c_METHOD; INSTRUCTION_FORMAT21ih; INSTRUCTION_FORMAT21lh; INSTRUCTION_FORMAT21s; @@ -94,12 +96,14 @@ tokens { INSTRUCTION_FORMAT22c_FIELD; INSTRUCTION_FORMAT22c_FIELD_ODEX; INSTRUCTION_FORMAT22c_TYPE; + INSTRUCTION_FORMAT22c_STRING; INSTRUCTION_FORMAT22cs_FIELD; INSTRUCTION_FORMAT22s; INSTRUCTION_FORMAT22s_OR_ID; INSTRUCTION_FORMAT22t; INSTRUCTION_FORMAT22x; INSTRUCTION_FORMAT23x; + INSTRUCTION_FORMAT25x; INSTRUCTION_FORMAT30t; INSTRUCTION_FORMAT31c; INSTRUCTION_FORMAT31i; @@ -209,6 +213,8 @@ tokens { I_STATEMENT_FORMAT21c_TYPE; I_STATEMENT_FORMAT21c_FIELD; I_STATEMENT_FORMAT21c_STRING; + I_STATEMENT_FORMAT21c_LAMBDA; + I_STATEMENT_FORMAT21c_METHOD; I_STATEMENT_FORMAT21ih; I_STATEMENT_FORMAT21lh; I_STATEMENT_FORMAT21s; @@ -216,10 +222,12 @@ tokens { I_STATEMENT_FORMAT22b; I_STATEMENT_FORMAT22c_FIELD; I_STATEMENT_FORMAT22c_TYPE; + I_STATEMENT_FORMAT22c_STRING; I_STATEMENT_FORMAT22s; I_STATEMENT_FORMAT22t; I_STATEMENT_FORMAT22x; I_STATEMENT_FORMAT23x; + I_STATEMENT_FORMAT25x; I_STATEMENT_FORMAT30t; I_STATEMENT_FORMAT31c; I_STATEMENT_FORMAT31i; @@ -252,7 +260,7 @@ import org.jf.dexlib2.Opcodes; private boolean verboseErrors = false; private boolean allowOdex = false; private int apiLevel = 15; - private Opcodes opcodes = new Opcodes(apiLevel); + private Opcodes opcodes = new Opcodes(apiLevel, false); public void setVerboseErrors(boolean verboseErrors) { this.verboseErrors = verboseErrors; @@ -262,8 +270,8 @@ import org.jf.dexlib2.Opcodes; this.allowOdex = allowOdex; } - public void setApiLevel(int apiLevel) { - this.opcodes = new Opcodes(apiLevel); + public void setApiLevel(int apiLevel, boolean experimental) { + this.opcodes = new Opcodes(apiLevel, experimental); this.apiLevel = apiLevel; } @@ -561,14 +569,18 @@ simple_name | INSTRUCTION_FORMAT21c_FIELD_ODEX -> SIMPLE_NAME[$INSTRUCTION_FORMAT21c_FIELD_ODEX] | INSTRUCTION_FORMAT21c_STRING -> SIMPLE_NAME[$INSTRUCTION_FORMAT21c_STRING] | INSTRUCTION_FORMAT21c_TYPE -> SIMPLE_NAME[$INSTRUCTION_FORMAT21c_TYPE] + | INSTRUCTION_FORMAT21c_LAMBDA -> SIMPLE_NAME[$INSTRUCTION_FORMAT21c_LAMBDA] + | INSTRUCTION_FORMAT21c_METHOD -> SIMPLE_NAME[$INSTRUCTION_FORMAT21c_METHOD] | INSTRUCTION_FORMAT21t -> SIMPLE_NAME[$INSTRUCTION_FORMAT21t] | INSTRUCTION_FORMAT22c_FIELD -> SIMPLE_NAME[$INSTRUCTION_FORMAT22c_FIELD] | INSTRUCTION_FORMAT22c_FIELD_ODEX -> SIMPLE_NAME[$INSTRUCTION_FORMAT22c_FIELD_ODEX] | INSTRUCTION_FORMAT22c_TYPE -> SIMPLE_NAME[$INSTRUCTION_FORMAT22c_TYPE] + | INSTRUCTION_FORMAT22c_STRING -> SIMPLE_NAME[$INSTRUCTION_FORMAT22c_STRING] | INSTRUCTION_FORMAT22cs_FIELD -> SIMPLE_NAME[$INSTRUCTION_FORMAT22cs_FIELD] | INSTRUCTION_FORMAT22s_OR_ID -> SIMPLE_NAME[$INSTRUCTION_FORMAT22s_OR_ID] | INSTRUCTION_FORMAT22t -> SIMPLE_NAME[$INSTRUCTION_FORMAT22t] | INSTRUCTION_FORMAT23x -> SIMPLE_NAME[$INSTRUCTION_FORMAT23x] + | INSTRUCTION_FORMAT25x -> SIMPLE_NAME[$INSTRUCTION_FORMAT25x] | INSTRUCTION_FORMAT31i_OR_ID -> SIMPLE_NAME[$INSTRUCTION_FORMAT31i_OR_ID] | INSTRUCTION_FORMAT31t -> SIMPLE_NAME[$INSTRUCTION_FORMAT31t] | INSTRUCTION_FORMAT35c_METHOD -> SIMPLE_NAME[$INSTRUCTION_FORMAT35c_METHOD] @@ -806,6 +818,8 @@ instruction | insn_format21c_field_odex | insn_format21c_string | insn_format21c_type + | insn_format21c_lambda + | insn_format21c_method | insn_format21ih | insn_format21lh | insn_format21s @@ -814,11 +828,13 @@ instruction | insn_format22c_field | insn_format22c_field_odex | insn_format22c_type + | insn_format22c_string | insn_format22cs_field | insn_format22s | insn_format22t | insn_format22x | insn_format23x + | insn_format25x | insn_format30t | insn_format31c | insn_format31i @@ -912,6 +928,16 @@ insn_format21c_type INSTRUCTION_FORMAT21c_TYPE REGISTER COMMA nonvoid_type_descriptor -> ^(I_STATEMENT_FORMAT21c_TYPE[$start, "I_STATEMENT_FORMAT21c"] INSTRUCTION_FORMAT21c_TYPE REGISTER nonvoid_type_descriptor); +insn_format21c_lambda + : //e.g. capture-variable v1, "foobar" + INSTRUCTION_FORMAT21c_LAMBDA REGISTER COMMA STRING_LITERAL + -> ^(I_STATEMENT_FORMAT21c_LAMBDA[$start, "I_STATEMENT_FORMAT21c_LAMBDA"] INSTRUCTION_FORMAT21c_LAMBDA REGISTER STRING_LITERAL); + +insn_format21c_method + : //e.g. create-lambda v1, java/io/PrintStream/print(Ljava/lang/Stream;)V + INSTRUCTION_FORMAT21c_METHOD REGISTER COMMA method_reference + -> ^(I_STATEMENT_FORMAT21c_METHOD[$start, "I_STATEMENT_FORMAT21c_METHOD"] INSTRUCTION_FORMAT21c_METHOD REGISTER method_reference); + insn_format21ih : //e.g. const/high16 v1, 1234 INSTRUCTION_FORMAT21ih REGISTER COMMA fixed_32bit_literal @@ -957,6 +983,11 @@ insn_format22c_type INSTRUCTION_FORMAT22c_TYPE REGISTER COMMA REGISTER COMMA nonvoid_type_descriptor -> ^(I_STATEMENT_FORMAT22c_TYPE[$start, "I_STATEMENT_FORMAT22c_TYPE"] INSTRUCTION_FORMAT22c_TYPE REGISTER REGISTER nonvoid_type_descriptor); +insn_format22c_string + : //e.g. liberate-variable v0, v1, "baz" + INSTRUCTION_FORMAT22c_STRING REGISTER COMMA REGISTER COMMA STRING_LITERAL + -> ^(I_STATEMENT_FORMAT22c_STRING[$start, "I_STATEMENT_FORMAT22c_STRING"] INSTRUCTION_FORMAT22c_STRING REGISTER REGISTER STRING_LITERAL); + insn_format22cs_field : //e.g. iget-quick v0, v1, field@0xc INSTRUCTION_FORMAT22cs_FIELD REGISTER COMMA REGISTER COMMA FIELD_OFFSET @@ -984,6 +1015,11 @@ insn_format23x INSTRUCTION_FORMAT23x REGISTER COMMA REGISTER COMMA REGISTER -> ^(I_STATEMENT_FORMAT23x[$start, "I_STATEMENT_FORMAT23x"] INSTRUCTION_FORMAT23x REGISTER REGISTER REGISTER); +insn_format25x + : //e.g. invoke-lambda vClosure, {vA, vB, vC, vD} -- up to 4 parameters + the closure. + INSTRUCTION_FORMAT25x REGISTER COMMA OPEN_BRACE register_list CLOSE_BRACE + -> ^(I_STATEMENT_FORMAT25x[$start, "I_STATEMENT_FORMAT25x"] INSTRUCTION_FORMAT25x REGISTER register_list); + insn_format30t : //e.g. goto/32 endloop: INSTRUCTION_FORMAT30t label_ref diff --git a/smali/src/main/antlr3/smaliTreeWalker.g b/smali/src/main/antlr/smaliTreeWalker.g index f49f10ef..8eed2b20 100644 --- a/smali/src/main/antlr3/smaliTreeWalker.g +++ b/smali/src/main/antlr/smaliTreeWalker.g @@ -77,15 +77,15 @@ import java.util.*; public String classType; private boolean verboseErrors = false; private int apiLevel = 15; - private Opcodes opcodes = new Opcodes(apiLevel); + private Opcodes opcodes = new Opcodes(apiLevel, false); private DexBuilder dexBuilder; public void setDexBuilder(DexBuilder dexBuilder) { this.dexBuilder = dexBuilder; } - public void setApiLevel(int apiLevel) { - this.opcodes = new Opcodes(apiLevel); + public void setApiLevel(int apiLevel, boolean experimental) { + this.opcodes = new Opcodes(apiLevel, experimental); this.apiLevel = apiLevel; } @@ -532,7 +532,7 @@ registers_directive returns[boolean isLocalsDirective, int registers] ^(( I_REGISTERS {$isLocalsDirective = false;} | I_LOCALS {$isLocalsDirective = true;} ) - short_integral_literal {$registers = $short_integral_literal.value;} + short_integral_literal {$registers = $short_integral_literal.value & 0xFFFF;} ); label_def @@ -674,6 +674,22 @@ register_list returns[byte[\] registers, byte registerCount] $registers[$registerCount++] = parseRegister_nibble($REGISTER.text); })*); +register_list4 returns[byte[\] registers, byte registerCount] + @init + { + $registers = new byte[4]; + $registerCount = 0; + } + : ^(I_REGISTER_LIST + (REGISTER + { + if ($registerCount == 4) { + throw new SemanticException(input, $I_REGISTER_LIST, "A list4 of registers can only have a maximum of 4 " + + "registers. Use the <op>/range alternate opcode instead."); + } + $registers[$registerCount++] = parseRegister_nibble($REGISTER.text); + })*); + register_range returns[int startRegister, int endRegister] : ^(I_REGISTER_RANGE (startReg=REGISTER endReg=REGISTER?)?) { @@ -726,6 +742,8 @@ instruction | insn_format21c_field | insn_format21c_string | insn_format21c_type + | insn_format21c_lambda + | insn_format21c_method | insn_format21ih | insn_format21lh | insn_format21s @@ -733,10 +751,12 @@ instruction | insn_format22b | insn_format22c_field | insn_format22c_type + | insn_format22c_string | insn_format22s | insn_format22t | insn_format22x | insn_format23x + | insn_format25x | insn_format30t | insn_format31c | insn_format31i @@ -861,6 +881,30 @@ insn_format21c_type dexBuilder.internTypeReference($nonvoid_type_descriptor.type))); }; +insn_format21c_lambda + : //e.g. capture-variable v1, "foobar" + ^(I_STATEMENT_FORMAT21c_LAMBDA INSTRUCTION_FORMAT21c_LAMBDA REGISTER string_literal) + { + Opcode opcode = opcodes.getOpcodeByName($INSTRUCTION_FORMAT21c_LAMBDA.text); + short regA = parseRegister_byte($REGISTER.text); + + $method::methodBuilder.addInstruction(new BuilderInstruction21c(opcode, regA, + dexBuilder.internStringReference($string_literal.value))); + }; + +insn_format21c_method + : //e.g. create-lambda v1, java/io/PrintStream/print(Ljava/lang/Stream;)V + ^(I_STATEMENT_FORMAT21c_METHOD INSTRUCTION_FORMAT21c_METHOD REGISTER method_reference) + { + Opcode opcode = opcodes.getOpcodeByName($INSTRUCTION_FORMAT21c_METHOD.text); + short regA = parseRegister_byte($REGISTER.text); + + ImmutableMethodReference methodReference = $method_reference.methodReference; + + $method::methodBuilder.addInstruction(new BuilderInstruction21c(opcode, regA, + dexBuilder.internMethodReference(methodReference))); + }; + insn_format21ih : //e.g. const/high16 v1, 1234 ^(I_STATEMENT_FORMAT21ih INSTRUCTION_FORMAT21ih REGISTER fixed_32bit_literal) @@ -947,6 +991,18 @@ insn_format22c_type dexBuilder.internTypeReference($nonvoid_type_descriptor.type))); }; +insn_format22c_string + : //e.g. liberate-variable v0, v1, "baz" + ^(I_STATEMENT_FORMAT22c_STRING INSTRUCTION_FORMAT22c_STRING registerA=REGISTER registerB=REGISTER string_literal) + { + Opcode opcode = opcodes.getOpcodeByName($INSTRUCTION_FORMAT22c_STRING.text); + byte regA = parseRegister_nibble($registerA.text); + byte regB = parseRegister_nibble($registerB.text); + + $method::methodBuilder.addInstruction(new BuilderInstruction22c(opcode, regA, regB, + dexBuilder.internStringReference($string_literal.value))); + }; + insn_format22s : //e.g. add-int/lit16 v0, v1, 12345 ^(I_STATEMENT_FORMAT22s INSTRUCTION_FORMAT22s registerA=REGISTER registerB=REGISTER short_integral_literal) @@ -994,6 +1050,23 @@ insn_format23x $method::methodBuilder.addInstruction(new BuilderInstruction23x(opcode, regA, regB, regC)); }; +insn_format25x + : //e.g. invoke-lambda vClosure, {vD, vE, vF, vG} -- up to 4 parameters + the closure. + ^(I_STATEMENT_FORMAT25x INSTRUCTION_FORMAT25x REGISTER register_list4) + { + Opcode opcode = opcodes.getOpcodeByName($INSTRUCTION_FORMAT25x.text); + + byte closureRegister = parseRegister_nibble($REGISTER.text); + + //this depends on the fact that register_list4 returns a byte[4] + byte[] registers = $register_list4.registers; + int parameterRegisterCount = $register_list4.registerCount; // don't count closure register + + $method::methodBuilder.addInstruction(new BuilderInstruction25x(opcode, + parameterRegisterCount, closureRegister, registers[0], registers[1], + registers[2], registers[3])); + }; + insn_format30t : //e.g. goto/32 endloop: ^(I_STATEMENT_FORMAT30t INSTRUCTION_FORMAT30t label_ref) diff --git a/smali/src/main/java/org/jf/smali/SmaliTestUtils.java b/smali/src/main/java/org/jf/smali/SmaliTestUtils.java index 9fd73473..26de0089 100644 --- a/smali/src/main/java/org/jf/smali/SmaliTestUtils.java +++ b/smali/src/main/java/org/jf/smali/SmaliTestUtils.java @@ -48,10 +48,16 @@ import java.io.Reader; import java.io.StringReader; public class SmaliTestUtils { + public static ClassDef compileSmali(String smaliText) throws RecognitionException, IOException { + return compileSmali(smaliText, 15, false); + } + + public static ClassDef compileSmali(String smaliText, int apiLevel, boolean experimental) + throws RecognitionException, IOException { CommonTokenStream tokens; LexerErrorInterface lexer; - DexBuilder dexBuilder = DexBuilder.makeDexBuilder(15); + DexBuilder dexBuilder = DexBuilder.makeDexBuilder(apiLevel); Reader reader = new StringReader(smaliText); @@ -61,7 +67,7 @@ public class SmaliTestUtils { smaliParser parser = new smaliParser(tokens); parser.setVerboseErrors(true); parser.setAllowOdex(false); - parser.setApiLevel(15); + parser.setApiLevel(apiLevel, experimental); smaliParser.smali_file_return result = parser.smali_file(); @@ -75,6 +81,7 @@ public class SmaliTestUtils { treeStream.setTokenStream(tokens); smaliTreeWalker dexGen = new smaliTreeWalker(treeStream); + dexGen.setApiLevel(apiLevel, experimental); dexGen.setVerboseErrors(true); dexGen.setDexBuilder(dexBuilder); dexGen.smali_file(); @@ -87,7 +94,8 @@ public class SmaliTestUtils { dexBuilder.writeTo(dataStore); - DexBackedDexFile dexFile = new DexBackedDexFile(new Opcodes(15), dataStore.getData()); + DexBackedDexFile dexFile = new DexBackedDexFile( + new Opcodes(apiLevel, experimental), dataStore.getData()); return Iterables.getFirst(dexFile.getClasses(), null); } diff --git a/smali/src/main/java/org/jf/smali/main.java b/smali/src/main/java/org/jf/smali/main.java index c6095bf3..31f65a88 100644 --- a/smali/src/main/java/org/jf/smali/main.java +++ b/smali/src/main/java/org/jf/smali/main.java @@ -34,6 +34,7 @@ import org.antlr.runtime.Token; import org.antlr.runtime.TokenSource; import org.antlr.runtime.tree.CommonTree; import org.antlr.runtime.tree.CommonTreeNodeStream; +import org.antlr.runtime.tree.TreeNodeStream; import org.apache.commons.cli.*; import org.jf.dexlib2.writer.builder.DexBuilder; import org.jf.dexlib2.writer.io.FileDataStore; @@ -110,6 +111,7 @@ public class main { boolean allowOdex = false; boolean verboseErrors = false; boolean printTokens = false; + boolean experimental = false; int apiLevel = 15; @@ -142,6 +144,9 @@ public class main { case 'x': allowOdex = true; break; + case 'X': + experimental = true; + break; case 'a': apiLevel = Integer.parseInt(commandLine.getOptionValue("a")); break; @@ -198,11 +203,12 @@ public class main { final boolean finalPrintTokens = printTokens; final boolean finalAllowOdex = allowOdex; final int finalApiLevel = apiLevel; + final boolean finalExperimental = experimental; for (final File file: filesToProcess) { tasks.add(executor.submit(new Callable<Boolean>() { @Override public Boolean call() throws Exception { return assembleSmaliFile(file, dexBuilder, finalVerboseErrors, finalPrintTokens, - finalAllowOdex, finalApiLevel); + finalAllowOdex, finalApiLevel, finalExperimental); } })); } @@ -252,7 +258,8 @@ public class main { } private static boolean assembleSmaliFile(File smaliFile, DexBuilder dexBuilder, boolean verboseErrors, - boolean printTokens, boolean allowOdex, int apiLevel) + boolean printTokens, boolean allowOdex, int apiLevel, + boolean experimental) throws Exception { CommonTokenStream tokens; @@ -267,7 +274,7 @@ public class main { if (printTokens) { tokens.getTokens(); - + for (int i=0; i<tokens.size(); i++) { Token token = tokens.get(i); if (token.getChannel() == smaliParser.HIDDEN) { @@ -276,12 +283,14 @@ public class main { System.out.println(smaliParser.tokenNames[token.getType()] + ": " + token.getText()); } + + System.out.flush(); } smaliParser parser = new smaliParser(tokens); parser.setVerboseErrors(verboseErrors); parser.setAllowOdex(allowOdex); - parser.setApiLevel(apiLevel); + parser.setApiLevel(apiLevel, experimental); smaliParser.smali_file_return result = parser.smali_file(); @@ -294,7 +303,13 @@ public class main { CommonTreeNodeStream treeStream = new CommonTreeNodeStream(t); treeStream.setTokenStream(tokens); + if (printTokens) { + System.out.println(t.toStringTree()); + } + smaliTreeWalker dexGen = new smaliTreeWalker(treeStream); + dexGen.setApiLevel(apiLevel, experimental); + dexGen.setVerboseErrors(verboseErrors); dexGen.setDexBuilder(dexBuilder); dexGen.smali_file(); @@ -363,6 +378,11 @@ public class main { .withArgName("API_LEVEL") .create("a"); + Option experimentalOption = OptionBuilder.withLongOpt("experimental") + .withDescription("enable experimental opcodes to be assembled, even if they " + + " aren't necessarily supported by the Android runtime yet") + .create("X"); + Option jobsOption = OptionBuilder.withLongOpt("jobs") .withDescription("The number of threads to use. Defaults to the number of cores available, up to a " + "maximum of 6") @@ -383,6 +403,7 @@ public class main { basicOptions.addOption(outputOption); basicOptions.addOption(allowOdexOption); basicOptions.addOption(apiLevelOption); + basicOptions.addOption(experimentalOption); basicOptions.addOption(jobsOption); debugOptions.addOption(verboseErrorsOption); diff --git a/smali/src/main/jflex/smaliLexer.flex b/smali/src/main/jflex/smaliLexer.jflex index df571e66..bc17362f 100644 --- a/smali/src/main/jflex/smaliLexer.flex +++ b/smali/src/main/jflex/smaliLexer.jflex @@ -461,6 +461,14 @@ Type = {PrimitiveType} | {ClassDescriptor} | {ArrayDescriptor} return newToken(INSTRUCTION_FORMAT21c_TYPE); } + "capture-variable" { // e.g. 'capture-variable vB, <string id>' + return newToken(INSTRUCTION_FORMAT21c_LAMBDA); + } + + "create-lambda" { // e.g. 'create-lambda vClosure, <method id>' + return newToken(INSTRUCTION_FORMAT21c_METHOD); + } + "const/high16" { return newToken(INSTRUCTION_FORMAT21ih); } @@ -492,10 +500,13 @@ Type = {PrimitiveType} | {ClassDescriptor} | {ArrayDescriptor} return newToken(INSTRUCTION_FORMAT22c_FIELD_ODEX); } - "instance-of" | "new-array" { + "instance-of" | "new-array" | "unbox-lambda" { return newToken(INSTRUCTION_FORMAT22c_TYPE); } + "liberate-variable" { + return newToken(INSTRUCTION_FORMAT22c_STRING); + } "iget-quick" | "iget-wide-quick" | "iget-object-quick" | "iput-quick" | "iput-wide-quick" | "iput-object-quick" { return newToken(INSTRUCTION_FORMAT22cs_FIELD); } @@ -513,7 +524,7 @@ Type = {PrimitiveType} | {ClassDescriptor} | {ArrayDescriptor} return newToken(INSTRUCTION_FORMAT22t); } - "move/from16" | "move-wide/from16" | "move-object/from16" { + "move/from16" | "move-wide/from16" | "move-object/from16" | "box-lambda" { return newToken(INSTRUCTION_FORMAT22x); } @@ -527,6 +538,10 @@ Type = {PrimitiveType} | {ClassDescriptor} | {ArrayDescriptor} return newToken(INSTRUCTION_FORMAT23x); } + "invoke-lambda" { // e.g. invoke-lambda vClosure, {vD, vE, vF, vG} -- at most 4 params + return newToken(INSTRUCTION_FORMAT25x); + } + "goto/32" { return newToken(INSTRUCTION_FORMAT30t); } @@ -640,5 +655,5 @@ Type = {PrimitiveType} | {ClassDescriptor} | {ArrayDescriptor} "." { return invalidToken("Invalid directive"); } "." [a-zA-z\-_] { return invalidToken("Invalid directive"); } "." [a-zA-z\-_] [a-zA-z0-9\-_]* { return invalidToken("Invalid directive"); } - . { return invalidToken("Invalid text"); } + [^] { return invalidToken("Invalid text"); } } diff --git a/smali/src/test/antlr3/org/jf/smali/expectedTokensTestGrammar.g b/smali/src/test/antlr/org/jf/smali/expectedTokensTestGrammar.g index 6780b970..6780b970 100644 --- a/smali/src/test/antlr3/org/jf/smali/expectedTokensTestGrammar.g +++ b/smali/src/test/antlr/org/jf/smali/expectedTokensTestGrammar.g diff --git a/smali/src/test/java/LexerTest.java b/smali/src/test/java/LexerTest.java index c1b70435..d156015a 100644 --- a/smali/src/test/java/LexerTest.java +++ b/smali/src/test/java/LexerTest.java @@ -40,6 +40,7 @@ import org.junit.Test; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; import java.util.HashMap; import java.util.List; @@ -158,7 +159,7 @@ public class LexerTest { if (smaliStream == null) { Assert.fail("Could not load " + smaliFile); } - smaliFlexLexer lexer = new smaliFlexLexer(smaliStream); + smaliFlexLexer lexer = new smaliFlexLexer(new InputStreamReader(smaliStream)); lexer.setSourceFile(new File(test + ".smali")); lexer.setSuppressErrors(true); diff --git a/smali/src/test/resources/LexerTest/InstructionTest.smali b/smali/src/test/resources/LexerTest/InstructionTest.smali index 2247adc0..f6dcbb1e 100644 --- a/smali/src/test/resources/LexerTest/InstructionTest.smali +++ b/smali/src/test/resources/LexerTest/InstructionTest.smali @@ -84,6 +84,8 @@ const-string check-cast new-instance const-class +capture-variable +create-lambda const/high16 const-wide/high16 const/16 @@ -122,6 +124,8 @@ iput-wide-volatile iput-object-volatile instance-of new-array +unbox-lambda +liberate-variable iget-quick iget-wide-quick iget-object-quick @@ -144,6 +148,7 @@ if-le move/from16 move-wide/from16 move-object/from16 +box-lambda cmpl-float cmpg-float cmpl-double @@ -194,6 +199,7 @@ add-double sub-double mul-double div-double +invoke-lambda goto/32 const-string/jumbo const diff --git a/smali/src/test/resources/LexerTest/InstructionTest.tokens b/smali/src/test/resources/LexerTest/InstructionTest.tokens index a5574ab4..fb5503b5 100644 --- a/smali/src/test/resources/LexerTest/InstructionTest.tokens +++ b/smali/src/test/resources/LexerTest/InstructionTest.tokens @@ -84,6 +84,8 @@ INSTRUCTION_FORMAT21c_STRING("const-string") INSTRUCTION_FORMAT21c_TYPE("check-cast") INSTRUCTION_FORMAT21c_TYPE("new-instance") INSTRUCTION_FORMAT21c_TYPE("const-class") +INSTRUCTION_FORMAT21c_LAMBDA("capture-variable") +INSTRUCTION_FORMAT21c_METHOD("create-lambda") INSTRUCTION_FORMAT21ih("const/high16") INSTRUCTION_FORMAT21lh("const-wide/high16") INSTRUCTION_FORMAT21s("const/16") @@ -122,6 +124,8 @@ INSTRUCTION_FORMAT22c_FIELD_ODEX("iput-wide-volatile") INSTRUCTION_FORMAT22c_FIELD_ODEX("iput-object-volatile") INSTRUCTION_FORMAT22c_TYPE("instance-of") INSTRUCTION_FORMAT22c_TYPE("new-array") +INSTRUCTION_FORMAT22c_TYPE("unbox-lambda") +INSTRUCTION_FORMAT22c_STRING("liberate-variable") INSTRUCTION_FORMAT22cs_FIELD("iget-quick") INSTRUCTION_FORMAT22cs_FIELD("iget-wide-quick") INSTRUCTION_FORMAT22cs_FIELD("iget-object-quick") @@ -144,6 +148,7 @@ INSTRUCTION_FORMAT22t("if-le") INSTRUCTION_FORMAT22x("move/from16") INSTRUCTION_FORMAT22x("move-wide/from16") INSTRUCTION_FORMAT22x("move-object/from16") +INSTRUCTION_FORMAT22x("box-lambda") INSTRUCTION_FORMAT23x("cmpl-float") INSTRUCTION_FORMAT23x("cmpg-float") INSTRUCTION_FORMAT23x("cmpl-double") @@ -194,6 +199,7 @@ INSTRUCTION_FORMAT23x("add-double") INSTRUCTION_FORMAT23x("sub-double") INSTRUCTION_FORMAT23x("mul-double") INSTRUCTION_FORMAT23x("div-double") +INSTRUCTION_FORMAT25x("invoke-lambda") INSTRUCTION_FORMAT30t("goto/32") INSTRUCTION_FORMAT31c("const-string/jumbo") INSTRUCTION_FORMAT31i_OR_ID("const") diff --git a/util/src/main/java/org/jf/util/ClassFileNameHandler.java b/util/src/main/java/org/jf/util/ClassFileNameHandler.java index 67037817..a223d30e 100644 --- a/util/src/main/java/org/jf/util/ClassFileNameHandler.java +++ b/util/src/main/java/org/jf/util/ClassFileNameHandler.java @@ -68,7 +68,7 @@ public class ClassFileNameHandler { public ClassFileNameHandler(File path, String fileExtension) { this.top = new DirectoryEntry(path); this.fileExtension = fileExtension; - this.modifyWindowsReservedFilenames = testForWindowsReservedFileNames(path); + this.modifyWindowsReservedFilenames = isWindows(); } // for testing @@ -229,27 +229,8 @@ public class ClassFileNameHandler { return sb.toString(); } - private static boolean testForWindowsReservedFileNames(File path) { - String[] reservedNames = new String[]{"aux", "con", "com1", "com9", "lpt1", "com9"}; - - for (String reservedName: reservedNames) { - File f = new File(path, reservedName + ".smali"); - if (f.exists()) { - continue; - } - - try { - FileWriter writer = new FileWriter(f); - writer.write("test"); - writer.flush(); - writer.close(); - f.delete(); //doesn't throw IOException - } catch (IOException ex) { - //if an exception occurred, it's likely that we're on a windows system. - return true; - } - } - return false; + private static boolean isWindows() { + return System.getProperty("os.name").startsWith("Windows"); } private static Pattern reservedFileNameRegex = Pattern.compile("^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])(\\..*)?$", diff --git a/util/src/main/java/org/jf/util/IndentingWriter.java b/util/src/main/java/org/jf/util/IndentingWriter.java index 95d6c320..8e4ca628 100644 --- a/util/src/main/java/org/jf/util/IndentingWriter.java +++ b/util/src/main/java/org/jf/util/IndentingWriter.java @@ -120,7 +120,7 @@ public class IndentingWriter extends Writer { int pos = start; while (pos < end) { pos = str.indexOf('\n', start); - if (pos == -1) { + if (pos == -1 || pos >= end) { writeLine(str, start, end-start); return; } else { diff --git a/util/src/main/java/org/jf/util/TextUtils.java b/util/src/main/java/org/jf/util/TextUtils.java index a01e68e7..66a1082d 100644 --- a/util/src/main/java/org/jf/util/TextUtils.java +++ b/util/src/main/java/org/jf/util/TextUtils.java @@ -50,10 +50,33 @@ public class TextUtils { @Nonnull public static String normalizeWhitespace(@Nonnull String source) { + // Go to native system new lines so that ^/$ work correctly + source = normalizeNewlines(source); + + // Remove all suffix/prefix whitespace + Pattern pattern = Pattern.compile("((^[ \t]+)|([ \t]+$))", Pattern.MULTILINE); + Matcher matcher = pattern.matcher(source); + source = matcher.replaceAll(""); + + // Remove all empty lines + Pattern pattern2 = Pattern.compile("^\r?\n?", Pattern.MULTILINE); + Matcher matcher2 = pattern2.matcher(source); + source = matcher2.replaceAll(""); + + // Remove a trailing new line, if present + Pattern pattern3 = Pattern.compile("\r?\n?$"); + Matcher matcher3 = pattern3.matcher(source); + source = matcher3.replaceAll(""); + + // Go back to unix-style \n newlines source = normalizeNewlines(source, "\n"); + return source; + } - Pattern pattern = Pattern.compile("(\n[ \t]*)+"); + @Nonnull + public static String stripComments(@Nonnull String source) { + Pattern pattern = Pattern.compile("#(.*)"); Matcher matcher = pattern.matcher(source); - return matcher.replaceAll("\n"); + return matcher.replaceAll(""); } } diff --git a/util/src/test/java/org/jf/util/TextUtilsTest.java b/util/src/test/java/org/jf/util/TextUtilsTest.java new file mode 100644 index 00000000..ef14e03a --- /dev/null +++ b/util/src/test/java/org/jf/util/TextUtilsTest.java @@ -0,0 +1,53 @@ +/* + * Copyright 2015, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "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 COPYRIGHT + * OWNER OR CONTRIBUTORS 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.jf.util; + +import org.junit.Assert; +import org.junit.Test; + +public class TextUtilsTest { + @Test + public void testStripComments() { + Assert.assertEquals("", TextUtils.stripComments("#world")); + Assert.assertEquals("hello", TextUtils.stripComments("hello#world")); + Assert.assertEquals("multi\nline", TextUtils.stripComments("multi#hello world\nline#world")); + } + + @Test + public void testNormalizeWhitespace() { + Assert.assertEquals("", TextUtils.normalizeWhitespace(" ")); + Assert.assertEquals("hello", TextUtils.normalizeWhitespace("hello ")); + Assert.assertEquals("hello", TextUtils.normalizeWhitespace(" hello")); + Assert.assertEquals("hello", TextUtils.normalizeWhitespace(" hello ")); + Assert.assertEquals("hello\nworld", TextUtils.normalizeWhitespace("hello \n \n world")); + } +}
\ No newline at end of file |