diff options
author | Ben Gruver <bgruv@google.com> | 2015-03-18 20:42:12 -0700 |
---|---|---|
committer | Ben Gruver <bgruv@google.com> | 2015-03-18 21:00:32 -0700 |
commit | 75bef01d100126e6f2e46614302fe5cad7c8287d (patch) | |
tree | bd0fe933ae742498109c7fb0d54fd3b3ae7a1637 /baksmali | |
parent | ddc7c35e1cfdddc7276d72e5e79c69bf932046e5 (diff) | |
parent | 2a0e4657ea73c67a44d1711da2539aa9c1a88524 (diff) | |
download | smali-75bef01d100126e6f2e46614302fe5cad7c8287d.tar.gz |
Merge branch 'master' into smalidea
Diffstat (limited to 'baksmali')
25 files changed, 1047 insertions, 142 deletions
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/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/ParamListMethodNameTest.java b/baksmali/src/test/java/org/jf/baksmali/ParamListMethodNameTest.java new file mode 100644 index 00000000..42f7239d --- /dev/null +++ b/baksmali/src/test/java/org/jf/baksmali/ParamListMethodNameTest.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 ParamListMethodNameTest extends IdenticalRoundtripTest { + + @Test + public void testParamListMethodName() { + runTest("ParamListMethodName"); + } +} diff --git a/baksmali/src/test/java/org/jf/baksmali/RoundtripTest.java b/baksmali/src/test/java/org/jf/baksmali/RoundtripTest.java index 266497f9..c9ff2d4d 100644 --- a/baksmali/src/test/java/org/jf/baksmali/RoundtripTest.java +++ b/baksmali/src/test/java/org/jf/baksmali/RoundtripTest.java @@ -32,38 +32,64 @@ package org.jf.baksmali; 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.Assert; -import org.junit.Test; +import javax.annotation.Nonnull; +import java.io.File; import java.io.IOException; -import java.io.StringWriter; -public class RoundtripTest { - @Test - public void testParamListMethodName() throws IOException, RecognitionException { - String text = "" + - ".class Lblah;\n" + - ".super Ljava/lang/Object;\n" + - "# virtual methods\n" + - ".method public abstract II()V\n" + - ".end method\n"; - ClassDef classDef = SmaliTestUtils.compileSmali(text); +/** + * 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); + } - baksmaliOptions options = new baksmaliOptions(); - options.useImplicitReferences = true; + @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()); + } - StringWriter stringWriter = new StringWriter(); - IndentingWriter writer = new IndentingWriter(stringWriter); - ClassDefinition classDefinition = new ClassDefinition(options, classDef); - classDefinition.writeTo(writer); - writer.close(); + 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)); + } - Assert.assertEquals(TextUtils.normalizeWhitespace(text), - TextUtils.normalizeWhitespace(stringWriter.toString())); + // 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/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/ParamListMethodNameTest/ParamListMethodName.smali b/baksmali/src/test/resources/ParamListMethodNameTest/ParamListMethodName.smali new file mode 100644 index 00000000..85717155 --- /dev/null +++ b/baksmali/src/test/resources/ParamListMethodNameTest/ParamListMethodName.smali @@ -0,0 +1,5 @@ +.class Lblah; +.super Ljava/lang/Object; + +.method public abstract II()V +.end method
\ No newline at end of file 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 |