diff --git a/baksmali/src/test/java/org/jf/baksmali/ImplicitReferenceTest.java b/baksmali/src/test/java/org/jf/baksmali/ImplicitReferenceTest.java new file mode 100644 index 00000000..0cda27a1 --- /dev/null +++ b/baksmali/src/test/java/org/jf/baksmali/ImplicitReferenceTest.java @@ -0,0 +1,322 @@ +/* + * Copyright 2014, 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 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("" + + ".class public LHelloWorld;\n" + + ".super Ljava/lang/Object;\n" + + ".method public static main([Ljava/lang/String;)V\n" + + " .registers 1\n" + + " invoke-static {p0}, LHelloWorld;->toString()V\n" + + " invoke-static {p0}, LHelloWorld;->V()V\n" + + " invoke-static {p0}, LHelloWorld;->I()V\n" + + " return-void\n" + + ".end method"); + + String expected = "" + + ".class public LHelloWorld;\n" + + ".super Ljava/lang/Object;\n" + + "# direct methods\n" + + ".method public static main([Ljava/lang/String;)V\n" + + ".registers 1\n" + + "invoke-static {p0}, toString()V\n" + + "invoke-static {p0}, V()V\n" + + "invoke-static {p0}, I()V\n" + + "return-void\n" + + ".end method\n"; + + 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())); + } + + @Test + public void testExplicitMethodReferences() throws IOException, RecognitionException { + ClassDef classDef = SmaliTestUtils.compileSmali("" + + ".class public LHelloWorld;\n" + + ".super Ljava/lang/Object;\n" + + ".method public static main([Ljava/lang/String;)V\n" + + " .registers 1\n" + + " invoke-static {p0}, LHelloWorld;->toString()V\n" + + " invoke-static {p0}, LHelloWorld;->V()V\n" + + " invoke-static {p0}, LHelloWorld;->I()V\n" + + " return-void\n" + + ".end method"); + + String expected = "" + + ".class public LHelloWorld;\n" + + ".super Ljava/lang/Object;\n" + + "# direct methods\n" + + ".method public static main([Ljava/lang/String;)V\n" + + " .registers 1\n" + + " invoke-static {p0}, LHelloWorld;->toString()V\n" + + " invoke-static {p0}, LHelloWorld;->V()V\n" + + " invoke-static {p0}, LHelloWorld;->I()V\n" + + " return-void\n" + + ".end method\n"; + + 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())); + } + + @Test + public void testImplicitMethodLiterals() throws IOException, RecognitionException { + ClassDef classDef = SmaliTestUtils.compileSmali("" + + ".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"); + + String expected = "" + + ".class public LHelloWorld;\n" + + ".super Ljava/lang/Object;\n" + + "# static fields\n" + + ".field public static field1:Ljava/lang/reflect/Method; = toString()V\n" + + ".field public static field2:Ljava/lang/reflect/Method; = V()V\n" + + ".field public static field3:Ljava/lang/reflect/Method; = I()V\n" + + ".field public static field4:Ljava/lang/Class; = I\n"; + + 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())); + } + + @Test + public void testExplicitMethodLiterals() throws IOException, RecognitionException { + ClassDef classDef = SmaliTestUtils.compileSmali("" + + ".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"); + + String expected = "" + + ".class public LHelloWorld;\n" + + ".super Ljava/lang/Object;\n" + + "# static fields\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\n"; + + 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())); + } + + @Test + public void testImplicitFieldReferences() throws IOException, RecognitionException { + ClassDef classDef = SmaliTestUtils.compileSmali("" + + ".class public LHelloWorld;\n" + + ".super Ljava/lang/Object;\n" + + ".method public static main([Ljava/lang/String;)V\n" + + " .registers 1\n" + + " sget v0, LHelloWorld;->someField:I\n" + + " sget v0, LHelloWorld;->I:I\n" + + " sget v0, LHelloWorld;->V:I\n" + + " return-void\n" + + ".end method"); + + String expected = "" + + ".class public LHelloWorld;\n" + + ".super Ljava/lang/Object;\n" + + "# direct methods\n" + + ".method public static main([Ljava/lang/String;)V\n" + + " .registers 1\n" + + " sget p0, someField:I\n" + + " sget p0, I:I\n" + + " sget p0, V:I\n" + + " return-void\n" + + ".end method\n"; + + 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())); + } + + @Test + public void testExplicitFieldReferences() throws IOException, RecognitionException { + ClassDef classDef = SmaliTestUtils.compileSmali("" + + ".class public LHelloWorld;\n" + + ".super Ljava/lang/Object;\n" + + ".method public static main([Ljava/lang/String;)V\n" + + " .registers 1\n" + + " sget v0, LHelloWorld;->someField:I\n" + + " sget v0, LHelloWorld;->I:I\n" + + " sget v0, LHelloWorld;->V:I\n" + + " return-void\n" + + ".end method"); + + String expected = "" + + ".class public LHelloWorld;\n" + + ".super Ljava/lang/Object;\n" + + "# direct methods\n" + + ".method public static main([Ljava/lang/String;)V\n" + + " .registers 1\n" + + " sget p0, LHelloWorld;->someField:I\n" + + " sget p0, LHelloWorld;->I:I\n" + + " sget p0, LHelloWorld;->V:I\n" + + " return-void\n" + + ".end method\n"; + + 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())); + } + + @Test + public void testImplicitFieldLiterals() throws IOException, RecognitionException { + ClassDef classDef = SmaliTestUtils.compileSmali("" + + ".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"); + + String expected = "" + + ".class public LHelloWorld;\n" + + ".super Ljava/lang/Object;\n" + + "# static fields\n" + + ".field public static field1:Ljava/lang/reflect/Field; = someField:I\n" + + ".field public static field2:Ljava/lang/reflect/Field; = V:I\n" + + ".field public static field3:Ljava/lang/reflect/Field; = I:I\n"; + + 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())); + } + + @Test + public void testExplicitFieldLiterals() throws IOException, RecognitionException { + ClassDef classDef = SmaliTestUtils.compileSmali("" + + ".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"); + + String expected = "" + + ".class public LHelloWorld;\n" + + ".super Ljava/lang/Object;\n" + + "# static fields\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\n"; + + 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())); + } +} diff --git a/brut.apktool.smali/baksmali/build.gradle b/brut.apktool.smali/baksmali/build.gradle index 9686504e..7ec7d487 100644 --- a/brut.apktool.smali/baksmali/build.gradle +++ b/brut.apktool.smali/baksmali/build.gradle @@ -40,6 +40,7 @@ dependencies { compile depends.guava testCompile depends.junit + testCompile project(':smali') proguard depends.proguard } diff --git a/brut.apktool.smali/baksmali/src/main/java/org/jf/baksmali/Adaptors/AnnotationFormatter.java b/brut.apktool.smali/baksmali/src/main/java/org/jf/baksmali/Adaptors/AnnotationFormatter.java index 2b9614f1..1310f191 100644 --- a/brut.apktool.smali/baksmali/src/main/java/org/jf/baksmali/Adaptors/AnnotationFormatter.java +++ b/brut.apktool.smali/baksmali/src/main/java/org/jf/baksmali/Adaptors/AnnotationFormatter.java @@ -33,13 +33,16 @@ import org.jf.dexlib2.AnnotationVisibility; import org.jf.dexlib2.iface.Annotation; import org.jf.util.IndentingWriter; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; import java.io.IOException; import java.util.Collection; public class AnnotationFormatter { - public static void writeTo(IndentingWriter writer, - Collection annotations) throws IOException { + public static void writeTo(@Nonnull IndentingWriter writer, + @Nonnull Collection annotations, + @Nullable String containingClass) throws IOException { boolean first = true; for (Annotation annotation: annotations) { if (!first) { @@ -47,18 +50,19 @@ public class AnnotationFormatter { } first = false; - writeTo(writer, annotation); + writeTo(writer, annotation, containingClass); } } - public static void writeTo(IndentingWriter writer, Annotation annotation) throws IOException { + public static void writeTo(@Nonnull IndentingWriter writer, @Nonnull Annotation annotation, + @Nullable String containingClass) throws IOException { writer.write(".annotation "); writer.write(AnnotationVisibility.getVisibility(annotation.getVisibility())); writer.write(' '); writer.write(annotation.getType()); writer.write('\n'); - AnnotationEncodedValueAdaptor.writeElementsTo(writer, annotation.getElements()); + AnnotationEncodedValueAdaptor.writeElementsTo(writer, annotation.getElements(), containingClass); writer.write(".end annotation\n"); } diff --git a/brut.apktool.smali/baksmali/src/main/java/org/jf/baksmali/Adaptors/ClassDefinition.java b/brut.apktool.smali/baksmali/src/main/java/org/jf/baksmali/Adaptors/ClassDefinition.java index a4eb6913..9c171f49 100644 --- a/brut.apktool.smali/baksmali/src/main/java/org/jf/baksmali/Adaptors/ClassDefinition.java +++ b/brut.apktool.smali/baksmali/src/main/java/org/jf/baksmali/Adaptors/ClassDefinition.java @@ -165,7 +165,13 @@ public class ClassDefinition { if (classAnnotations.size() != 0) { writer.write("\n\n"); writer.write("# annotations\n"); - AnnotationFormatter.writeTo(writer, classAnnotations); + + String containingClass = null; + if (options.useImplicitReferences) { + containingClass = classDef.getType(); + } + + AnnotationFormatter.writeTo(writer, classAnnotations, containingClass); } } @@ -199,7 +205,7 @@ public class ClassDefinition { } else { setInStaticConstructor = fieldsSetInStaticConstructor.contains(fieldString); } - FieldDefinition.writeTo(fieldWriter, field, setInStaticConstructor); + FieldDefinition.writeTo(options, fieldWriter, field, setInStaticConstructor); } return writtenFields; } @@ -237,7 +243,7 @@ public class ClassDefinition { writer.write("# There is both a static and instance field with this signature.\n" + "# You will need to rename one of these fields, including all references.\n"); } - FieldDefinition.writeTo(fieldWriter, field, false); + FieldDefinition.writeTo(options, fieldWriter, field, false); } } @@ -261,7 +267,7 @@ public class ClassDefinition { writer.write('\n'); // TODO: check for method validation errors - String methodString = ReferenceUtil.getShortMethodDescriptor(method); + String methodString = ReferenceUtil.getMethodDescriptor(method, true); IndentingWriter methodWriter = writer; if (!writtenMethods.add(methodString)) { @@ -300,7 +306,7 @@ public class ClassDefinition { writer.write('\n'); // TODO: check for method validation errors - String methodString = ReferenceUtil.getShortMethodDescriptor(method); + String methodString = ReferenceUtil.getMethodDescriptor(method, true); IndentingWriter methodWriter = writer; if (!writtenMethods.add(methodString)) { diff --git a/brut.apktool.smali/baksmali/src/main/java/org/jf/baksmali/Adaptors/EncodedValue/AnnotationEncodedValueAdaptor.java b/brut.apktool.smali/baksmali/src/main/java/org/jf/baksmali/Adaptors/EncodedValue/AnnotationEncodedValueAdaptor.java index 66cd506c..e8f12a29 100644 --- a/brut.apktool.smali/baksmali/src/main/java/org/jf/baksmali/Adaptors/EncodedValue/AnnotationEncodedValueAdaptor.java +++ b/brut.apktool.smali/baksmali/src/main/java/org/jf/baksmali/Adaptors/EncodedValue/AnnotationEncodedValueAdaptor.java @@ -32,28 +32,32 @@ import org.jf.dexlib2.iface.AnnotationElement; import org.jf.dexlib2.iface.value.AnnotationEncodedValue; import org.jf.util.IndentingWriter; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; import java.io.IOException; import java.util.Collection; public abstract class AnnotationEncodedValueAdaptor { - public static void writeTo(IndentingWriter writer, AnnotationEncodedValue annotationEncodedValue) - throws IOException { + public static void writeTo(@Nonnull IndentingWriter writer, + @Nonnull AnnotationEncodedValue annotationEncodedValue, + @Nullable String containingClass) throws IOException { writer.write(".subannotation "); writer.write(annotationEncodedValue.getType()); writer.write('\n'); - writeElementsTo(writer, annotationEncodedValue.getElements()); + writeElementsTo(writer, annotationEncodedValue.getElements(), containingClass); writer.write(".end subannotation"); } - public static void writeElementsTo(IndentingWriter writer, - Collection annotationElements) throws IOException { + public static void writeElementsTo(@Nonnull IndentingWriter writer, + @Nonnull Collection annotationElements, + @Nullable String containingClass) throws IOException { writer.indent(4); for (AnnotationElement annotationElement: annotationElements) { writer.write(annotationElement.getName()); writer.write(" = "); - EncodedValueAdaptor.writeTo(writer, annotationElement.getValue()); + EncodedValueAdaptor.writeTo(writer, annotationElement.getValue(), containingClass); writer.write('\n'); } writer.deindent(4); diff --git a/brut.apktool.smali/baksmali/src/main/java/org/jf/baksmali/Adaptors/EncodedValue/ArrayEncodedValueAdaptor.java b/brut.apktool.smali/baksmali/src/main/java/org/jf/baksmali/Adaptors/EncodedValue/ArrayEncodedValueAdaptor.java index 8da1b044..eb079b30 100644 --- a/brut.apktool.smali/baksmali/src/main/java/org/jf/baksmali/Adaptors/EncodedValue/ArrayEncodedValueAdaptor.java +++ b/brut.apktool.smali/baksmali/src/main/java/org/jf/baksmali/Adaptors/EncodedValue/ArrayEncodedValueAdaptor.java @@ -28,15 +28,19 @@ package org.jf.baksmali.Adaptors.EncodedValue; -import org.jf.util.IndentingWriter; import org.jf.dexlib2.iface.value.ArrayEncodedValue; import org.jf.dexlib2.iface.value.EncodedValue; +import org.jf.util.IndentingWriter; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; import java.io.IOException; import java.util.Collection; public class ArrayEncodedValueAdaptor { - public static void writeTo(IndentingWriter writer, ArrayEncodedValue arrayEncodedValue) throws IOException { + public static void writeTo(@Nonnull IndentingWriter writer, + @Nonnull ArrayEncodedValue arrayEncodedValue, + @Nullable String containingClass) throws IOException { writer.write('{'); Collection values = arrayEncodedValue.getValue(); if (values.size() == 0) { @@ -53,7 +57,7 @@ public class ArrayEncodedValueAdaptor { } first = false; - EncodedValueAdaptor.writeTo(writer, encodedValue); + EncodedValueAdaptor.writeTo(writer, encodedValue, containingClass); } writer.deindent(4); writer.write("\n}"); diff --git a/brut.apktool.smali/baksmali/src/main/java/org/jf/baksmali/Adaptors/EncodedValue/EncodedValueAdaptor.java b/brut.apktool.smali/baksmali/src/main/java/org/jf/baksmali/Adaptors/EncodedValue/EncodedValueAdaptor.java index f4d442e0..bce1ff7a 100644 --- a/brut.apktool.smali/baksmali/src/main/java/org/jf/baksmali/Adaptors/EncodedValue/EncodedValueAdaptor.java +++ b/brut.apktool.smali/baksmali/src/main/java/org/jf/baksmali/Adaptors/EncodedValue/EncodedValueAdaptor.java @@ -29,22 +29,26 @@ package org.jf.baksmali.Adaptors.EncodedValue; import org.jf.baksmali.Adaptors.ReferenceFormatter; +import org.jf.baksmali.Renderers.*; import org.jf.dexlib2.ValueType; import org.jf.dexlib2.iface.value.*; import org.jf.dexlib2.util.ReferenceUtil; import org.jf.util.IndentingWriter; -import org.jf.baksmali.Renderers.*; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; import java.io.IOException; public abstract class EncodedValueAdaptor { - public static void writeTo(IndentingWriter writer, EncodedValue encodedValue) throws IOException { + public static void writeTo(@Nonnull IndentingWriter writer, @Nonnull EncodedValue encodedValue, + @Nullable String containingClass) + throws IOException { switch (encodedValue.getValueType()) { case ValueType.ANNOTATION: - AnnotationEncodedValueAdaptor.writeTo(writer, (AnnotationEncodedValue)encodedValue); + AnnotationEncodedValueAdaptor.writeTo(writer, (AnnotationEncodedValue)encodedValue, containingClass); return; case ValueType.ARRAY: - ArrayEncodedValueAdaptor.writeTo(writer, (ArrayEncodedValue)encodedValue); + ArrayEncodedValueAdaptor.writeTo(writer, (ArrayEncodedValue)encodedValue, containingClass); return; case ValueType.BOOLEAN: BooleanRenderer.writeTo(writer, ((BooleanEncodedValue)encodedValue).getValue()); @@ -59,11 +63,21 @@ public abstract class EncodedValueAdaptor { DoubleRenderer.writeTo(writer, ((DoubleEncodedValue)encodedValue).getValue()); return; case ValueType.ENUM: + EnumEncodedValue enumEncodedValue = (EnumEncodedValue)encodedValue; + boolean useImplicitReference = false; + if (enumEncodedValue.getValue().getDefiningClass().equals(containingClass)) { + useImplicitReference = true; + } writer.write(".enum "); - ReferenceUtil.writeFieldDescriptor(writer, ((EnumEncodedValue)encodedValue).getValue()); + ReferenceUtil.writeFieldDescriptor(writer, enumEncodedValue.getValue(), useImplicitReference); return; case ValueType.FIELD: - ReferenceUtil.writeFieldDescriptor(writer, ((FieldEncodedValue)encodedValue).getValue()); + FieldEncodedValue fieldEncodedValue = (FieldEncodedValue)encodedValue; + useImplicitReference = false; + if (fieldEncodedValue.getValue().getDefiningClass().equals(containingClass)) { + useImplicitReference = true; + } + ReferenceUtil.writeFieldDescriptor(writer, fieldEncodedValue.getValue(), useImplicitReference); return; case ValueType.FLOAT: FloatRenderer.writeTo(writer, ((FloatEncodedValue)encodedValue).getValue()); @@ -75,7 +89,12 @@ public abstract class EncodedValueAdaptor { LongRenderer.writeTo(writer, ((LongEncodedValue)encodedValue).getValue()); return; case ValueType.METHOD: - ReferenceUtil.writeMethodDescriptor(writer, ((MethodEncodedValue)encodedValue).getValue()); + MethodEncodedValue methodEncodedValue = (MethodEncodedValue)encodedValue; + useImplicitReference = false; + if (methodEncodedValue.getValue().getDefiningClass().equals(containingClass)) { + useImplicitReference = true; + } + ReferenceUtil.writeMethodDescriptor(writer, methodEncodedValue.getValue(), useImplicitReference); return; case ValueType.NULL: writer.write("null"); diff --git a/brut.apktool.smali/baksmali/src/main/java/org/jf/baksmali/Adaptors/FieldDefinition.java b/brut.apktool.smali/baksmali/src/main/java/org/jf/baksmali/Adaptors/FieldDefinition.java index a71089d8..ae017914 100644 --- a/brut.apktool.smali/baksmali/src/main/java/org/jf/baksmali/Adaptors/FieldDefinition.java +++ b/brut.apktool.smali/baksmali/src/main/java/org/jf/baksmali/Adaptors/FieldDefinition.java @@ -29,6 +29,7 @@ package org.jf.baksmali.Adaptors; import org.jf.baksmali.Adaptors.EncodedValue.EncodedValueAdaptor; +import org.jf.baksmali.baksmaliOptions; import org.jf.dexlib2.AccessFlags; import org.jf.dexlib2.iface.Annotation; import org.jf.dexlib2.iface.Field; @@ -40,7 +41,8 @@ import java.io.IOException; import java.util.Collection; public class FieldDefinition { - public static void writeTo(IndentingWriter writer, Field field, boolean setInStaticConstructor) throws IOException { + public static void writeTo(baksmaliOptions options, IndentingWriter writer, Field field, + boolean setInStaticConstructor) throws IOException { EncodedValue initialValue = field.getInitialValue(); int accessFlags = field.getAccessFlags(); @@ -64,7 +66,13 @@ public class FieldDefinition { writer.write(field.getType()); if (initialValue != null) { writer.write(" = "); - EncodedValueAdaptor.writeTo(writer, initialValue); + + String containingClass = null; + if (options.useImplicitReferences) { + containingClass = field.getDefiningClass(); + } + + EncodedValueAdaptor.writeTo(writer, initialValue, containingClass); } writer.write('\n'); @@ -72,7 +80,13 @@ public class FieldDefinition { Collection annotations = field.getAnnotations(); if (annotations.size() > 0) { writer.indent(4); - AnnotationFormatter.writeTo(writer, annotations); + + String containingClass = null; + if (options.useImplicitReferences) { + containingClass = field.getDefiningClass(); + } + + AnnotationFormatter.writeTo(writer, annotations, containingClass); writer.deindent(4); writer.write(".end field\n"); } 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 300efd81..b0fdaf2b 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,6 +41,8 @@ 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; @@ -102,7 +104,13 @@ public class InstructionMethodItem extends MethodItem { ReferenceInstruction referenceInstruction = (ReferenceInstruction)instruction; try { Reference reference = referenceInstruction.getReference(); - referenceString = ReferenceUtil.getReferenceString(reference); + + String classContext = null; + if (methodDef.classDef.options.useImplicitReferences) { + classContext = methodDef.method.getDefiningClass(); + } + + referenceString = ReferenceUtil.getReferenceString(reference, classContext); assert referenceString != null; } catch (InvalidItemIndex ex) { writer.write("#"); diff --git a/brut.apktool.smali/baksmali/src/main/java/org/jf/baksmali/Adaptors/MethodDefinition.java b/brut.apktool.smali/baksmali/src/main/java/org/jf/baksmali/Adaptors/MethodDefinition.java index 5ce971f5..eb5caa67 100644 --- a/brut.apktool.smali/baksmali/src/main/java/org/jf/baksmali/Adaptors/MethodDefinition.java +++ b/brut.apktool.smali/baksmali/src/main/java/org/jf/baksmali/Adaptors/MethodDefinition.java @@ -56,6 +56,7 @@ import org.jf.util.IndentingWriter; import org.jf.util.SparseIntArray; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import java.io.IOException; import java.util.*; @@ -148,7 +149,13 @@ public class MethodDefinition { writer.indent(4); writeParameters(writer, method, methodParameters, options); - AnnotationFormatter.writeTo(writer, method.getAnnotations()); + + String containingClass = null; + if (options.useImplicitReferences) { + containingClass = method.getDefiningClass(); + } + AnnotationFormatter.writeTo(writer, method.getAnnotations(), containingClass); + writer.deindent(4); writer.write(".end method\n"); } @@ -191,7 +198,11 @@ public class MethodDefinition { parameterRegisterCount); } - AnnotationFormatter.writeTo(writer, method.getAnnotations()); + String containingClass = null; + if (classDef.options.useImplicitReferences) { + containingClass = method.getDefiningClass(); + } + AnnotationFormatter.writeTo(writer, method.getAnnotations(), containingClass); writer.write('\n'); @@ -264,7 +275,12 @@ public class MethodDefinition { writer.write("\n"); if (annotations.size() > 0) { writer.indent(4); - AnnotationFormatter.writeTo(writer, annotations); + + String containingClass = null; + if (options.useImplicitReferences) { + containingClass = method.getDefiningClass(); + } + AnnotationFormatter.writeTo(writer, annotations, containingClass); writer.deindent(4); writer.write(".end param\n"); } @@ -519,6 +535,14 @@ public class MethodDefinition { } } + @Nullable + private String getContainingClassForImplicitReference() { + if (classDef.options.useImplicitReferences) { + return classDef.classDef.getType(); + } + return null; + } + public static class LabelCache { protected HashMap labels = new HashMap(); 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 1517c68d..4abe5a11 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 @@ -74,6 +74,7 @@ public class baksmaliOptions { public boolean deodex = false; public boolean ignoreErrors = false; public boolean checkPackagePrivateAccess = false; + public boolean useImplicitReferences = true; public File customInlineDefinitions = null; public InlineMethodResolver inlineResolver = null; public int registerInfo = 0; 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 274f1441..e50172fe 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 @@ -205,6 +205,9 @@ public class main { String rif = commandLine.getOptionValue("i"); options.setResourceIdFiles(rif); break; + case 't': + options.useImplicitReferences = false; + break; case 'N': disassemble = false; break; @@ -420,6 +423,10 @@ public class main { .withArgName("FILES") .create("i"); + Option noImplicitReferencesOption = OptionBuilder.withLongOpt("no-implicit-references") + .withDescription("Don't use implicit (type-less) method and field references") + .create("t"); + Option dumpOption = OptionBuilder.withLongOpt("dump-to") .withDescription("dumps the given dex file into a single annotated dump file named FILE" + " (.dump by default), along with the normal disassembly") @@ -459,6 +466,7 @@ public class main { basicOptions.addOption(apiLevelOption); basicOptions.addOption(jobsOption); basicOptions.addOption(resourceIdFilesOption); + basicOptions.addOption(noImplicitReferencesOption); debugOptions.addOption(dumpOption); debugOptions.addOption(ignoreErrorsOption); 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 38219491..1fae6ae7 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 @@ -91,6 +91,7 @@ public class AnalysisTest { options.registerInfo = baksmaliOptions.ALL | baksmaliOptions.FULLMERGE; options.classPath = new ClassPath(); } + options.useImplicitReferences = false; for (ClassDef classDef: dexFile.getClasses()) { StringWriter stringWriter = new StringWriter(); diff --git a/brut.apktool.smali/dexlib2/src/main/java/org/jf/dexlib2/analysis/MethodAnalyzer.java b/brut.apktool.smali/dexlib2/src/main/java/org/jf/dexlib2/analysis/MethodAnalyzer.java index 9f4b7331..a6e60323 100644 --- a/brut.apktool.smali/dexlib2/src/main/java/org/jf/dexlib2/analysis/MethodAnalyzer.java +++ b/brut.apktool.smali/dexlib2/src/main/java/org/jf/dexlib2/analysis/MethodAnalyzer.java @@ -1637,7 +1637,7 @@ public class MethodAnalyzer { String superclass = methodClass.getSuperclass(); if (superclass == null) { throw new ExceptionWithContext("Couldn't find accessible class while resolving method %s", - ReferenceUtil.getShortMethodDescriptor(resolvedMethod)); + ReferenceUtil.getMethodDescriptor(resolvedMethod, true)); } methodClass = classPath.getClassDef(superclass); @@ -1648,7 +1648,7 @@ public class MethodAnalyzer { resolvedMethod = classPath.getClass(methodClass.getType()).getMethodByVtableIndex(methodIndex); if (resolvedMethod == null) { throw new ExceptionWithContext("Couldn't find accessible class while resolving method %s", - ReferenceUtil.getShortMethodDescriptor(resolvedMethod)); + ReferenceUtil.getMethodDescriptor(resolvedMethod, true)); } resolvedMethod = new ImmutableMethodReference(methodClass.getType(), resolvedMethod.getName(), resolvedMethod.getParameterTypes(), resolvedMethod.getReturnType()); diff --git a/brut.apktool.smali/dexlib2/src/main/java/org/jf/dexlib2/util/ReferenceUtil.java b/brut.apktool.smali/dexlib2/src/main/java/org/jf/dexlib2/util/ReferenceUtil.java index a81facf3..81b042ec 100644 --- a/brut.apktool.smali/dexlib2/src/main/java/org/jf/dexlib2/util/ReferenceUtil.java +++ b/brut.apktool.smali/dexlib2/src/main/java/org/jf/dexlib2/util/ReferenceUtil.java @@ -34,27 +34,22 @@ package org.jf.dexlib2.util; import org.jf.dexlib2.iface.reference.*; import org.jf.util.StringUtils; +import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.io.IOException; import java.io.Writer; public final class ReferenceUtil { - public static String getShortMethodDescriptor(MethodReference methodReference) { - StringBuilder sb = new StringBuilder(); - sb.append(methodReference.getName()); - sb.append('('); - for (CharSequence paramType: methodReference.getParameterTypes()) { - sb.append(paramType); - } - sb.append(')'); - sb.append(methodReference.getReturnType()); - return sb.toString(); + public static String getMethodDescriptor(MethodReference methodReference) { + return getMethodDescriptor(methodReference, false); } - public static String getMethodDescriptor(MethodReference methodReference) { + public static String getMethodDescriptor(MethodReference methodReference, boolean useImplicitReference) { StringBuilder sb = new StringBuilder(); - sb.append(methodReference.getDefiningClass()); - sb.append("->"); + if (!useImplicitReference) { + sb.append(methodReference.getDefiningClass()); + sb.append("->"); + } sb.append(methodReference.getName()); sb.append('('); for (CharSequence paramType: methodReference.getParameterTypes()) { @@ -66,8 +61,15 @@ public final class ReferenceUtil { } public static void writeMethodDescriptor(Writer writer, MethodReference methodReference) throws IOException { - writer.write(methodReference.getDefiningClass()); - writer.write("->"); + writeMethodDescriptor(writer, methodReference, false); + } + + public static void writeMethodDescriptor(Writer writer, MethodReference methodReference, + boolean useImplicitReference) throws IOException { + if (!useImplicitReference) { + writer.write(methodReference.getDefiningClass()); + writer.write("->"); + } writer.write(methodReference.getName()); writer.write('('); for (CharSequence paramType: methodReference.getParameterTypes()) { @@ -78,9 +80,15 @@ public final class ReferenceUtil { } public static String getFieldDescriptor(FieldReference fieldReference) { + return getFieldDescriptor(fieldReference, false); + } + + public static String getFieldDescriptor(FieldReference fieldReference, boolean useImplicitReference) { StringBuilder sb = new StringBuilder(); - sb.append(fieldReference.getDefiningClass()); - sb.append("->"); + if (!useImplicitReference) { + sb.append(fieldReference.getDefiningClass()); + sb.append("->"); + } sb.append(fieldReference.getName()); sb.append(':'); sb.append(fieldReference.getType()); @@ -96,15 +104,27 @@ public final class ReferenceUtil { } public static void writeFieldDescriptor(Writer writer, FieldReference fieldReference) throws IOException { - writer.write(fieldReference.getDefiningClass()); - writer.write("->"); + writeFieldDescriptor(writer, fieldReference, false); + } + + public static void writeFieldDescriptor(Writer writer, FieldReference fieldReference, + boolean implicitReference) throws IOException { + if (!implicitReference) { + writer.write(fieldReference.getDefiningClass()); + writer.write("->"); + } writer.write(fieldReference.getName()); writer.write(':'); writer.write(fieldReference.getType()); } @Nullable - public static String getReferenceString(Reference reference) { + public static String getReferenceString(@Nonnull Reference reference) { + return getReferenceString(reference, null); + } + + @Nullable + public static String getReferenceString(@Nonnull Reference reference, @Nullable String containingClass) { if (reference instanceof StringReference) { return String.format("\"%s\"", StringUtils.escapeString(((StringReference)reference).getString())); } @@ -112,10 +132,14 @@ public final class ReferenceUtil { return ((TypeReference)reference).getType(); } if (reference instanceof FieldReference) { - return getFieldDescriptor((FieldReference)reference); + FieldReference fieldReference = (FieldReference)reference; + boolean useImplicitReference = fieldReference.getDefiningClass().equals(containingClass); + return getFieldDescriptor((FieldReference)reference, useImplicitReference); } if (reference instanceof MethodReference) { - return getMethodDescriptor((MethodReference)reference); + MethodReference methodReference = (MethodReference)reference; + boolean useImplicitReference = methodReference.getDefiningClass().equals(containingClass); + return getMethodDescriptor((MethodReference)reference, useImplicitReference); } return null; } diff --git a/brut.apktool.smali/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/ClassPool.java b/brut.apktool.smali/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/ClassPool.java index 61ea02f7..f2440cdb 100644 --- a/brut.apktool.smali/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/ClassPool.java +++ b/brut.apktool.smali/dexlib2/src/main/java/org/jf/dexlib2/writer/pool/ClassPool.java @@ -116,7 +116,7 @@ public class ClassPool implements ClassSection methods = new HashSet(); for (PoolMethod method: poolClassDef.getMethods()) { - String methodDescriptor = ReferenceUtil.getShortMethodDescriptor(method); + String methodDescriptor = ReferenceUtil.getMethodDescriptor(method, true); if (!methods.add(methodDescriptor)) { throw new ExceptionWithContext("Multiple definitions for method %s->%s", poolClassDef.getType(), methodDescriptor); diff --git a/util/src/main/java/org/jf/util/TextUtils.java b/util/src/main/java/org/jf/util/TextUtils.java new file mode 100644 index 00000000..a01e68e7 --- /dev/null +++ b/util/src/main/java/org/jf/util/TextUtils.java @@ -0,0 +1,59 @@ +/* + * Copyright 2014, 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 javax.annotation.Nonnull; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class TextUtils { + private static String newline = System.getProperty("line.separator"); + + @Nonnull + public static String normalizeNewlines(@Nonnull String source) { + return normalizeNewlines(source, newline); + } + + @Nonnull + public static String normalizeNewlines(@Nonnull String source, String newlineValue) { + return source.replace("\r", "").replace("\n", newlineValue); + } + + @Nonnull + public static String normalizeWhitespace(@Nonnull String source) { + source = normalizeNewlines(source, "\n"); + + Pattern pattern = Pattern.compile("(\n[ \t]*)+"); + Matcher matcher = pattern.matcher(source); + return matcher.replaceAll("\n"); + } +}