diff --git a/brut.apktool.smali/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/InstructionMethodItem.java b/brut.apktool.smali/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/InstructionMethodItem.java index 8e6a9f09..7e080354 100644 --- a/brut.apktool.smali/baksmali/src/main/java/org/jf/baksmali/Adaptors/Format/InstructionMethodItem.java +++ b/brut.apktool.smali/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; @@ -300,6 +298,11 @@ public class InstructionMethodItem 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 +428,43 @@ public class InstructionMethodItem 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/brut.apktool.smali/baksmali/src/main/java/org/jf/baksmali/baksmali.java b/brut.apktool.smali/baksmali/src/main/java/org/jf/baksmali/baksmali.java index 765e77ec..47fa406d 100644 --- a/brut.apktool.smali/baksmali/src/main/java/org/jf/baksmali/baksmali.java +++ b/brut.apktool.smali/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/brut.apktool.smali/baksmali/src/main/java/org/jf/baksmali/baksmaliOptions.java b/brut.apktool.smali/baksmali/src/main/java/org/jf/baksmali/baksmaliOptions.java index ea97f45e..754cdf10 100644 --- a/brut.apktool.smali/baksmali/src/main/java/org/jf/baksmali/baksmaliOptions.java +++ b/brut.apktool.smali/baksmali/src/main/java/org/jf/baksmali/baksmaliOptions.java @@ -73,6 +73,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/brut.apktool.smali/baksmali/src/main/java/org/jf/baksmali/dump.java b/brut.apktool.smali/baksmali/src/main/java/org/jf/baksmali/dump.java index bd040e6d..1ef7df0e 100644 --- a/brut.apktool.smali/baksmali/src/main/java/org/jf/baksmali/dump.java +++ b/brut.apktool.smali/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/brut.apktool.smali/baksmali/src/main/java/org/jf/baksmali/main.java b/brut.apktool.smali/baksmali/src/main/java/org/jf/baksmali/main.java index 28c42030..71598fa6 100644 --- a/brut.apktool.smali/baksmali/src/main/java/org/jf/baksmali/main.java +++ b/brut.apktool.smali/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 getDefaultBootClassPathForApi(int apiLevel) { + private static List getDefaultBootClassPathForApi(int apiLevel, boolean experimental) { if (apiLevel < 9) { return Lists.newArrayList( "/system/framework/core.jar", diff --git a/brut.apktool.smali/baksmali/src/test/java/org/jf/baksmali/AnalysisTest.java b/brut.apktool.smali/baksmali/src/test/java/org/jf/baksmali/AnalysisTest.java index bf353e83..725032af 100644 --- a/brut.apktool.smali/baksmali/src/test/java/org/jf/baksmali/AnalysisTest.java +++ b/brut.apktool.smali/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/brut.apktool.smali/baksmali/src/test/java/org/jf/baksmali/BaksmaliTestUtils.java b/brut.apktool.smali/baksmali/src/test/java/org/jf/baksmali/BaksmaliTestUtils.java new file mode 100644 index 00000000..c009e83d --- /dev/null +++ b/brut.apktool.smali/baksmali/src/test/java/org/jf/baksmali/BaksmaliTestUtils.java @@ -0,0 +1,89 @@ +/* + * 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 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 java.io.IOException; +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); + + StringWriter stringWriter = new StringWriter(); + IndentingWriter writer = new IndentingWriter(stringWriter); + ClassDefinition classDefinition = new ClassDefinition(options, classDef); + classDefinition.writeTo(writer); + writer.close(); + + // Remove unnecessary whitespace and optionally strip all comments from smali file + String normalizedExpected = expected; + if (stripComments) { + normalizedExpected = TextUtils.stripComments(normalizedExpected); + } + normalizedExpected = TextUtils.normalizeWhitespace(normalizedExpected); + + String normalizedActual = stringWriter.toString(); + if (stripComments) { + normalizedActual = TextUtils.stripComments(normalizedActual); + } + normalizedActual = TextUtils.normalizeWhitespace(normalizedActual); + + // 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); + } + + // Static helpers class; do not instantiate. + private BaksmaliTestUtils() { throw new AssertionError(); } +} diff --git a/brut.apktool.smali/baksmali/src/test/java/org/jf/baksmali/ImplicitReferenceTest.java b/brut.apktool.smali/baksmali/src/test/java/org/jf/baksmali/ImplicitReferenceTest.java index 0cda27a1..1f2ae5bf 100644 --- a/brut.apktool.smali/baksmali/src/test/java/org/jf/baksmali/ImplicitReferenceTest.java +++ b/brut.apktool.smali/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/brut.apktool.smali/baksmali/src/test/java/org/jf/baksmali/LambdaTest.java b/brut.apktool.smali/baksmali/src/test/java/org/jf/baksmali/LambdaTest.java new file mode 100644 index 00000000..c4c6caea --- /dev/null +++ b/brut.apktool.smali/baksmali/src/test/java/org/jf/baksmali/LambdaTest.java @@ -0,0 +1,80 @@ +/* + * 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 org.antlr.runtime.RecognitionException; + +import org.junit.Assert; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; + +public class LambdaTest { + + @Test + public void testHelloWorldLambda() throws IOException, RecognitionException { + runTest("HelloWorldLambda"); + } + + private void runTest(String fileName) throws IOException, RecognitionException { + // Load file from resources as a stream + String smaliFile = String.format("LambdaTest%s%s.smali", File.separatorChar, fileName); + + InputStream smaliStream = LambdaTest.class.getClassLoader(). + getResourceAsStream(smaliFile); + if (smaliStream == null) { + Assert.fail("Could not load " + smaliFile); + } + + String source = readFully(smaliStream); + + // Run smali, baksmali, and then compare strings are equal (minus comments/whitespace) + 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 + BaksmaliTestUtils.assertSmaliCompiledEquals(source, source, options, true); + } + + + public static String readFully(InputStream inputStream) throws IOException { + return readFully(inputStream, "UTF-8"); + } + + public static String readFully(InputStream inputStream, String encoding) + throws IOException { + return new String(ByteStreams.toByteArray(inputStream), encoding); + } +} diff --git a/brut.apktool.smali/baksmali/src/test/resources/LambdaTest/HelloWorldLambda.smali b/brut.apktool.smali/baksmali/src/test/resources/LambdaTest/HelloWorldLambda.smali new file mode 100644 index 00000000..d70ced50 --- /dev/null +++ b/brut.apktool.smali/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/brut.apktool.smali/dexlib2/src/accessorTest/java/org/jf/dexlib2/AccessorTest.java b/brut.apktool.smali/dexlib2/src/accessorTest/java/org/jf/dexlib2/AccessorTest.java index 13e7b30f..924d3fd3 100644 --- a/brut.apktool.smali/dexlib2/src/accessorTest/java/org/jf/dexlib2/AccessorTest.java +++ b/brut.apktool.smali/dexlib2/src/accessorTest/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/brut.apktool.smali/dexlib2/src/main/java/org/jf/dexlib2/DexFileFactory.java b/brut.apktool.smali/dexlib2/src/main/java/org/jf/dexlib2/DexFileFactory.java index d08da036..85f8a426 100644 --- a/brut.apktool.smali/dexlib2/src/main/java/org/jf/dexlib2/DexFileFactory.java +++ b/brut.apktool.smali/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 { diff --git a/brut.apktool.smali/dexlib2/src/main/java/org/jf/dexlib2/Format.java b/brut.apktool.smali/dexlib2/src/main/java/org/jf/dexlib2/Format.java index d91b7432..ee34aa50 100644 --- a/brut.apktool.smali/dexlib2/src/main/java/org/jf/dexlib2/Format.java +++ b/brut.apktool.smali/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/brut.apktool.smali/dexlib2/src/main/java/org/jf/dexlib2/Opcode.java b/brut.apktool.smali/dexlib2/src/main/java/org/jf/dexlib2/Opcode.java index d0adee8c..3b082ee8 100644 --- a/brut.apktool.smali/dexlib2/src/main/java/org/jf/dexlib2/Opcode.java +++ b/brut.apktool.smali/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/brut.apktool.smali/dexlib2/src/main/java/org/jf/dexlib2/Opcodes.java b/brut.apktool.smali/dexlib2/src/main/java/org/jf/dexlib2/Opcodes.java index d6e5532e..dd876813 100644 --- a/brut.apktool.smali/dexlib2/src/main/java/org/jf/dexlib2/Opcodes.java +++ b/brut.apktool.smali/dexlib2/src/main/java/org/jf/dexlib2/Opcodes.java @@ -40,13 +40,14 @@ public class Opcodes { private final Opcode[] opcodesByValue; private final HashMap 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/brut.apktool.smali/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassPath.java b/brut.apktool.smali/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassPath.java index 854f24c9..de6a0c25 100644 --- a/brut.apktool.smali/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassPath.java +++ b/brut.apktool.smali/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassPath.java @@ -170,18 +170,18 @@ public class ClassPath { @Nonnull public static ClassPath fromClassPath(Iterable classPathDirs, Iterable 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 classPathDirs, Iterable classPath, DexFile dexFile, - int api, boolean checkPackagePrivateAccess) { + int api, boolean checkPackagePrivateAccess, boolean experimental) { ArrayList 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); @@ -192,7 +192,8 @@ public class ClassPath { @Nonnull private static DexFile loadClassPathEntry(@Nonnull Iterable 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(); @@ -227,7 +228,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/brut.apktool.smali/dexlib2/src/main/java/org/jf/dexlib2/analysis/DumpFields.java b/brut.apktool.smali/dexlib2/src/main/java/org/jf/dexlib2/analysis/DumpFields.java index d57ce961..2bb3e492 100644 --- a/brut.apktool.smali/dexlib2/src/main/java/org/jf/dexlib2/analysis/DumpFields.java +++ b/brut.apktool.smali/dexlib2/src/main/java/org/jf/dexlib2/analysis/DumpFields.java @@ -73,6 +73,7 @@ public class DumpFields { ArrayList bootClassPathDirs = Lists.newArrayList(); String outFile = "fields.txt"; int apiLevel = 15; + boolean experimental = false; for (int i=0; i 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/brut.apktool.smali/dexlib2/src/main/java/org/jf/dexlib2/analysis/DumpVtables.java b/brut.apktool.smali/dexlib2/src/main/java/org/jf/dexlib2/analysis/DumpVtables.java index 0d7656c3..193c0d39 100644 --- a/brut.apktool.smali/dexlib2/src/main/java/org/jf/dexlib2/analysis/DumpVtables.java +++ b/brut.apktool.smali/dexlib2/src/main/java/org/jf/dexlib2/analysis/DumpVtables.java @@ -71,6 +71,7 @@ public class DumpVtables { ArrayList bootClassPathDirs = Lists.newArrayList(); String outFile = "vtables.txt"; int apiLevel = 15; + boolean experimental = false; for (int i=0; i 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/brut.apktool.smali/dexlib2/src/main/java/org/jf/dexlib2/builder/MutableMethodImplementation.java b/brut.apktool.smali/dexlib2/src/main/java/org/jf/dexlib2/builder/MutableMethodImplementation.java index f734e018..84981221 100644 --- a/brut.apktool.smali/dexlib2/src/main/java/org/jf/dexlib2/builder/MutableMethodImplementation.java +++ b/brut.apktool.smali/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)); @@ -820,6 +828,18 @@ public class MutableMethodImplementation implements MethodImplementation { instruction.getReference()); } + @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( diff --git a/brut.apktool.smali/dexlib2/src/main/java/org/jf/dexlib2/builder/instruction/BuilderInstruction25x.java b/brut.apktool.smali/dexlib2/src/main/java/org/jf/dexlib2/builder/instruction/BuilderInstruction25x.java new file mode 100644 index 00000000..3783d2b6 --- /dev/null +++ b/brut.apktool.smali/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/brut.apktool.smali/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/instruction/DexBackedInstruction.java b/brut.apktool.smali/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/instruction/DexBackedInstruction.java index a4c29990..ac82f4b4 100644 --- a/brut.apktool.smali/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/instruction/DexBackedInstruction.java +++ b/brut.apktool.smali/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/brut.apktool.smali/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/instruction/DexBackedInstruction25x.java b/brut.apktool.smali/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/instruction/DexBackedInstruction25x.java new file mode 100644 index 00000000..80fb8767 --- /dev/null +++ b/brut.apktool.smali/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/brut.apktool.smali/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/raw/CodeItem.java b/brut.apktool.smali/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/raw/CodeItem.java index c3946976..9c79e270 100644 --- a/brut.apktool.smali/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/raw/CodeItem.java +++ b/brut.apktool.smali/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 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/brut.apktool.smali/dexlib2/src/main/java/org/jf/dexlib2/iface/instruction/OneFixedFourParameterRegisterInstruction.java b/brut.apktool.smali/dexlib2/src/main/java/org/jf/dexlib2/iface/instruction/OneFixedFourParameterRegisterInstruction.java new file mode 100644 index 00000000..c03bff77 --- /dev/null +++ b/brut.apktool.smali/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/brut.apktool.smali/dexlib2/src/main/java/org/jf/dexlib2/iface/instruction/formats/Instruction25x.java b/brut.apktool.smali/dexlib2/src/main/java/org/jf/dexlib2/iface/instruction/formats/Instruction25x.java new file mode 100644 index 00000000..51df2dee --- /dev/null +++ b/brut.apktool.smali/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/brut.apktool.smali/dexlib2/src/main/java/org/jf/dexlib2/immutable/instruction/ImmutableInstruction.java b/brut.apktool.smali/dexlib2/src/main/java/org/jf/dexlib2/immutable/instruction/ImmutableInstruction.java index 432f1930..ed50ef5b 100644 --- a/brut.apktool.smali/dexlib2/src/main/java/org/jf/dexlib2/immutable/instruction/ImmutableInstruction.java +++ b/brut.apktool.smali/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/brut.apktool.smali/dexlib2/src/main/java/org/jf/dexlib2/immutable/instruction/ImmutableInstruction25x.java b/brut.apktool.smali/dexlib2/src/main/java/org/jf/dexlib2/immutable/instruction/ImmutableInstruction25x.java new file mode 100644 index 00000000..2f31eaea --- /dev/null +++ b/brut.apktool.smali/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/brut.apktool.smali/dexlib2/src/main/java/org/jf/dexlib2/util/Preconditions.java b/brut.apktool.smali/dexlib2/src/main/java/org/jf/dexlib2/util/Preconditions.java index ab86b652..51c083ca 100644 --- a/brut.apktool.smali/dexlib2/src/main/java/org/jf/dexlib2/util/Preconditions.java +++ b/brut.apktool.smali/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/brut.apktool.smali/dexlib2/src/main/java/org/jf/dexlib2/writer/DexWriter.java b/brut.apktool.smali/dexlib2/src/main/java/org/jf/dexlib2/writer/DexWriter.java index 268df9b1..dd6ec9f1 100644 --- a/brut.apktool.smali/dexlib2/src/main/java/org/jf/dexlib2/writer/DexWriter.java +++ b/brut.apktool.smali/dexlib2/src/main/java/org/jf/dexlib2/writer/DexWriter.java @@ -976,6 +976,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; diff --git a/brut.apktool.smali/dexlib2/src/main/java/org/jf/dexlib2/writer/InstructionWriter.java b/brut.apktool.smali/dexlib2/src/main/java/org/jf/dexlib2/writer/InstructionWriter.java index 17d31337..23a77759 100644 --- a/brut.apktool.smali/dexlib2/src/main/java/org/jf/dexlib2/writer/InstructionWriter.java +++ b/brut.apktool.smali/dexlib2/src/main/java/org/jf/dexlib2/writer/InstructionWriter.java @@ -317,6 +317,19 @@ public class InstructionWriterof(), ImmutableList.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.of(), ImmutableList.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.of(), ImmutableList.of(), dexFile, - 15); + 15, false); InlineMethodResolver inlineMethodResolver = new CustomInlineMethodResolver(classPath, "Lblah;->blah()V"); MethodAnalyzer methodAnalyzer = new MethodAnalyzer(classPath, method, inlineMethodResolver); diff --git a/brut.apktool.smali/dexlib2/src/test/java/org/jf/dexlib2/writer/DexWriterTest.java b/brut.apktool.smali/dexlib2/src/test/java/org/jf/dexlib2/writer/DexWriterTest.java index b2cc52db..66abe06a 100644 --- a/brut.apktool.smali/dexlib2/src/test/java/org/jf/dexlib2/writer/DexWriterTest.java +++ b/brut.apktool.smali/dexlib2/src/test/java/org/jf/dexlib2/writer/DexWriterTest.java @@ -75,7 +75,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); diff --git a/brut.apktool.smali/dexlib2/src/test/java/org/jf/dexlib2/writer/JumboStringConversionTest.java b/brut.apktool.smali/dexlib2/src/test/java/org/jf/dexlib2/writer/JumboStringConversionTest.java index 38015e74..7e504a17 100644 --- a/brut.apktool.smali/dexlib2/src/test/java/org/jf/dexlib2/writer/JumboStringConversionTest.java +++ b/brut.apktool.smali/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/brut.apktool.smali/examples/HelloWorldLambda/HelloWorldFunctionalInterface.smali b/brut.apktool.smali/examples/HelloWorldLambda/HelloWorldFunctionalInterface.smali new file mode 100644 index 00000000..c2ada47d --- /dev/null +++ b/brut.apktool.smali/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/brut.apktool.smali/examples/HelloWorldLambda/HelloWorldLambda.smali b/brut.apktool.smali/examples/HelloWorldLambda/HelloWorldLambda.smali new file mode 100644 index 00000000..36c8e49c --- /dev/null +++ b/brut.apktool.smali/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/brut.apktool.smali/smali/src/main/antlr/smaliParser.g b/brut.apktool.smali/smali/src/main/antlr/smaliParser.g index d057b4a4..6d07452c 100644 --- a/brut.apktool.smali/smali/src/main/antlr/smaliParser.g +++ b/brut.apktool.smali/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/brut.apktool.smali/smali/src/main/antlr/smaliTreeWalker.g b/brut.apktool.smali/smali/src/main/antlr/smaliTreeWalker.g index f49f10ef..d319b2ca 100644 --- a/brut.apktool.smali/smali/src/main/antlr/smaliTreeWalker.g +++ b/brut.apktool.smali/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; } @@ -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 /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/brut.apktool.smali/smali/src/main/java/org/jf/smali/SmaliTestUtils.java b/brut.apktool.smali/smali/src/main/java/org/jf/smali/SmaliTestUtils.java index 9fd73473..26de0089 100644 --- a/brut.apktool.smali/smali/src/main/java/org/jf/smali/SmaliTestUtils.java +++ b/brut.apktool.smali/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/brut.apktool.smali/smali/src/main/java/org/jf/smali/main.java b/brut.apktool.smali/smali/src/main/java/org/jf/smali/main.java index c6095bf3..31f65a88 100644 --- a/brut.apktool.smali/smali/src/main/java/org/jf/smali/main.java +++ b/brut.apktool.smali/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() { @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' + return newToken(INSTRUCTION_FORMAT21c_LAMBDA); + } + + "create-lambda" { // e.g. 'create-lambda vClosure, ' + 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); } diff --git a/brut.apktool.smali/smali/src/test/resources/LexerTest/InstructionTest.smali b/brut.apktool.smali/smali/src/test/resources/LexerTest/InstructionTest.smali index 2247adc0..f6dcbb1e 100644 --- a/brut.apktool.smali/smali/src/test/resources/LexerTest/InstructionTest.smali +++ b/brut.apktool.smali/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/brut.apktool.smali/smali/src/test/resources/LexerTest/InstructionTest.tokens b/brut.apktool.smali/smali/src/test/resources/LexerTest/InstructionTest.tokens index a5574ab4..fb5503b5 100644 --- a/brut.apktool.smali/smali/src/test/resources/LexerTest/InstructionTest.tokens +++ b/brut.apktool.smali/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/brut.apktool.smali/util/src/main/java/org/jf/util/TextUtils.java b/brut.apktool.smali/util/src/main/java/org/jf/util/TextUtils.java index a01e68e7..784ee097 100644 --- a/brut.apktool.smali/util/src/main/java/org/jf/util/TextUtils.java +++ b/brut.apktool.smali/util/src/main/java/org/jf/util/TextUtils.java @@ -50,10 +50,28 @@ public class TextUtils { @Nonnull public static String normalizeWhitespace(@Nonnull String source) { - source = normalizeNewlines(source, "\n"); + // Go to native system new lines so that ^/$ work correctly + source = normalizeNewlines(source); - Pattern pattern = Pattern.compile("(\n[ \t]*)+"); + // Remove all suffix/prefix whitespace + Pattern pattern = Pattern.compile("((^[ \t]+)|([ \t]+))"); Matcher matcher = pattern.matcher(source); - return matcher.replaceAll("\n"); + source = matcher.replaceAll(""); + + // Remove all empty lines + Pattern pattern2 = Pattern.compile("^\r?\n?", Pattern.MULTILINE); + Matcher matcher2 = pattern2.matcher(source); + source = matcher2.replaceAll(""); + + // Go back to unix-style \n newlines + source = normalizeNewlines(source, "\n"); + return source; + } + + @Nonnull + public static String stripComments(@Nonnull String source) { + Pattern pattern = Pattern.compile("#(.*)"); + Matcher matcher = pattern.matcher(source); + return matcher.replaceAll(""); } } diff --git a/brut.apktool.smali/util/src/test/java/org/jf/util/TextUtilsTest.java b/brut.apktool.smali/util/src/test/java/org/jf/util/TextUtilsTest.java new file mode 100644 index 00000000..ef14e03a --- /dev/null +++ b/brut.apktool.smali/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