From 2772be8e9d616a2846ca8848ecb0a1ba5dfa776e Mon Sep 17 00:00:00 2001 From: Ben Gruver Date: Sat, 19 Jul 2014 18:53:53 -0700 Subject: [PATCH] Implement implicit method/field references in smali --- .../smali/src/main/antlr3/smaliParser.g | 49 ++-- .../smali/src/main/antlr3/smaliTreeWalker.g | 60 +++-- .../java/org/jf/smali/SmaliTestUtils.java | 94 +++++++ .../src/test/java/ImplicitReferenceTest.java | 242 ++++++++++++++++++ 4 files changed, 396 insertions(+), 49 deletions(-) create mode 100644 smali/src/main/java/org/jf/smali/SmaliTestUtils.java create mode 100644 smali/src/test/java/ImplicitReferenceTest.java diff --git a/brut.apktool.smali/smali/src/main/antlr3/smaliParser.g b/brut.apktool.smali/smali/src/main/antlr3/smaliParser.g index 37ae21d7..d057b4a4 100644 --- a/brut.apktool.smali/smali/src/main/antlr3/smaliParser.g +++ b/brut.apktool.smali/smali/src/main/antlr3/smaliParser.g @@ -686,22 +686,21 @@ enum_literal type_field_method_literal : reference_type_descriptor - ( ARROW - ( member_name COLON nonvoid_type_descriptor -> ^(I_ENCODED_FIELD reference_type_descriptor member_name nonvoid_type_descriptor) - | member_name method_prototype -> ^(I_ENCODED_METHOD reference_type_descriptor member_name method_prototype) + | ( (reference_type_descriptor ARROW)? + ( member_name COLON nonvoid_type_descriptor -> ^(I_ENCODED_FIELD reference_type_descriptor? member_name nonvoid_type_descriptor) + | member_name method_prototype -> ^(I_ENCODED_METHOD reference_type_descriptor? member_name method_prototype) ) - | -> reference_type_descriptor ) | PRIMITIVE_TYPE | VOID_TYPE; -fully_qualified_method - : reference_type_descriptor ARROW member_name method_prototype - -> reference_type_descriptor member_name method_prototype; +method_reference + : (reference_type_descriptor ARROW)? member_name method_prototype + -> reference_type_descriptor? member_name method_prototype; -fully_qualified_field - : reference_type_descriptor ARROW member_name COLON nonvoid_type_descriptor - -> reference_type_descriptor member_name nonvoid_type_descriptor; +field_reference + : (reference_type_descriptor ARROW)? member_name COLON nonvoid_type_descriptor + -> reference_type_descriptor? member_name nonvoid_type_descriptor; label : COLON simple_name -> ^(I_LABEL[$COLON, "I_LABEL"] simple_name); @@ -717,7 +716,7 @@ register_range : (startreg=REGISTER (DOTDOT endreg=REGISTER)?)? -> ^(I_REGISTER_RANGE[$start, "I_REGISTER_RANGE"] $startreg? $endreg?); verification_error_reference - : CLASS_DESCRIPTOR | fully_qualified_field | fully_qualified_method; + : CLASS_DESCRIPTOR | field_reference | method_reference; catch_directive : CATCH_DIRECTIVE nonvoid_type_descriptor OPEN_BRACE from=label_ref DOTDOT to=label_ref CLOSE_BRACE using=label_ref @@ -890,18 +889,18 @@ insn_format20t insn_format21c_field : //e.g. sget-object v0, java/lang/System/out LJava/io/PrintStream; - INSTRUCTION_FORMAT21c_FIELD REGISTER COMMA fully_qualified_field - -> ^(I_STATEMENT_FORMAT21c_FIELD[$start, "I_STATEMENT_FORMAT21c_FIELD"] INSTRUCTION_FORMAT21c_FIELD REGISTER fully_qualified_field); + INSTRUCTION_FORMAT21c_FIELD REGISTER COMMA field_reference + -> ^(I_STATEMENT_FORMAT21c_FIELD[$start, "I_STATEMENT_FORMAT21c_FIELD"] INSTRUCTION_FORMAT21c_FIELD REGISTER field_reference); insn_format21c_field_odex : //e.g. sget-object-volatile v0, java/lang/System/out LJava/io/PrintStream; - INSTRUCTION_FORMAT21c_FIELD_ODEX REGISTER COMMA fully_qualified_field + INSTRUCTION_FORMAT21c_FIELD_ODEX REGISTER COMMA field_reference { if (!allowOdex || opcodes.getOpcodeByName($INSTRUCTION_FORMAT21c_FIELD_ODEX.text) == null || apiLevel >= 14) { throwOdexedInstructionException(input, $INSTRUCTION_FORMAT21c_FIELD_ODEX.text); } } - -> ^(I_STATEMENT_FORMAT21c_FIELD[$start, "I_STATEMENT_FORMAT21c_FIELD"] INSTRUCTION_FORMAT21c_FIELD_ODEX REGISTER fully_qualified_field); + -> ^(I_STATEMENT_FORMAT21c_FIELD[$start, "I_STATEMENT_FORMAT21c_FIELD"] INSTRUCTION_FORMAT21c_FIELD_ODEX REGISTER field_reference); insn_format21c_string : //e.g. const-string v1, "Hello World!" @@ -940,18 +939,18 @@ insn_format22b insn_format22c_field : //e.g. iput-object v1, v0 org/jf/HelloWorld2/HelloWorld2.helloWorld Ljava/lang/String; - INSTRUCTION_FORMAT22c_FIELD REGISTER COMMA REGISTER COMMA fully_qualified_field - -> ^(I_STATEMENT_FORMAT22c_FIELD[$start, "I_STATEMENT_FORMAT22c_FIELD"] INSTRUCTION_FORMAT22c_FIELD REGISTER REGISTER fully_qualified_field); + INSTRUCTION_FORMAT22c_FIELD REGISTER COMMA REGISTER COMMA field_reference + -> ^(I_STATEMENT_FORMAT22c_FIELD[$start, "I_STATEMENT_FORMAT22c_FIELD"] INSTRUCTION_FORMAT22c_FIELD REGISTER REGISTER field_reference); insn_format22c_field_odex : //e.g. iput-object-volatile v1, v0 org/jf/HelloWorld2/HelloWorld2.helloWorld Ljava/lang/String; - INSTRUCTION_FORMAT22c_FIELD_ODEX REGISTER COMMA REGISTER COMMA fully_qualified_field + INSTRUCTION_FORMAT22c_FIELD_ODEX REGISTER COMMA REGISTER COMMA field_reference { if (!allowOdex || opcodes.getOpcodeByName($INSTRUCTION_FORMAT22c_FIELD_ODEX.text) == null || apiLevel >= 14) { throwOdexedInstructionException(input, $INSTRUCTION_FORMAT22c_FIELD_ODEX.text); } } - -> ^(I_STATEMENT_FORMAT22c_FIELD[$start, "I_STATEMENT_FORMAT22c_FIELD"] INSTRUCTION_FORMAT22c_FIELD_ODEX REGISTER REGISTER fully_qualified_field); + -> ^(I_STATEMENT_FORMAT22c_FIELD[$start, "I_STATEMENT_FORMAT22c_FIELD"] INSTRUCTION_FORMAT22c_FIELD_ODEX REGISTER REGISTER field_reference); insn_format22c_type : //e.g. instance-of v0, v1, Ljava/lang/String; @@ -1012,8 +1011,8 @@ insn_format32x insn_format35c_method : //e.g. invoke-virtual {v0,v1} java/io/PrintStream/print(Ljava/lang/Stream;)V - INSTRUCTION_FORMAT35c_METHOD OPEN_BRACE register_list CLOSE_BRACE COMMA fully_qualified_method - -> ^(I_STATEMENT_FORMAT35c_METHOD[$start, "I_STATEMENT_FORMAT35c_METHOD"] INSTRUCTION_FORMAT35c_METHOD register_list fully_qualified_method); + INSTRUCTION_FORMAT35c_METHOD OPEN_BRACE register_list CLOSE_BRACE COMMA method_reference + -> ^(I_STATEMENT_FORMAT35c_METHOD[$start, "I_STATEMENT_FORMAT35c_METHOD"] INSTRUCTION_FORMAT35c_METHOD register_list method_reference); insn_format35c_type : //e.g. filled-new-array {v0,v1}, I @@ -1022,7 +1021,7 @@ insn_format35c_type insn_format35c_method_odex : //e.g. invoke-direct {p0}, Ljava/lang/Object;->()V - INSTRUCTION_FORMAT35c_METHOD_ODEX OPEN_BRACE register_list CLOSE_BRACE COMMA fully_qualified_method + INSTRUCTION_FORMAT35c_METHOD_ODEX OPEN_BRACE register_list CLOSE_BRACE COMMA method_reference { throwOdexedInstructionException(input, $INSTRUCTION_FORMAT35c_METHOD_ODEX.text); }; @@ -1043,12 +1042,12 @@ insn_format35ms_method insn_format3rc_method : //e.g. invoke-virtual/range {v25..v26}, java/lang/StringBuilder/append(Ljava/lang/String;)Ljava/lang/StringBuilder; - INSTRUCTION_FORMAT3rc_METHOD OPEN_BRACE register_range CLOSE_BRACE COMMA fully_qualified_method - -> ^(I_STATEMENT_FORMAT3rc_METHOD[$start, "I_STATEMENT_FORMAT3rc_METHOD"] INSTRUCTION_FORMAT3rc_METHOD register_range fully_qualified_method); + INSTRUCTION_FORMAT3rc_METHOD OPEN_BRACE register_range CLOSE_BRACE COMMA method_reference + -> ^(I_STATEMENT_FORMAT3rc_METHOD[$start, "I_STATEMENT_FORMAT3rc_METHOD"] INSTRUCTION_FORMAT3rc_METHOD register_range method_reference); insn_format3rc_method_odex : //e.g. invoke-object-init/range {p0}, Ljava/lang/Object;->()V - INSTRUCTION_FORMAT3rc_METHOD_ODEX OPEN_BRACE register_list CLOSE_BRACE COMMA fully_qualified_method + INSTRUCTION_FORMAT3rc_METHOD_ODEX OPEN_BRACE register_list CLOSE_BRACE COMMA method_reference { throwOdexedInstructionException(input, $INSTRUCTION_FORMAT3rc_METHOD_ODEX.text); }; diff --git a/brut.apktool.smali/smali/src/main/antlr3/smaliTreeWalker.g b/brut.apktool.smali/smali/src/main/antlr3/smaliTreeWalker.g index 105ee220..f49f10ef 100644 --- a/brut.apktool.smali/smali/src/main/antlr3/smaliTreeWalker.g +++ b/brut.apktool.smali/smali/src/main/antlr3/smaliTreeWalker.g @@ -501,17 +501,29 @@ method_type_list returns[List types] )*; -fully_qualified_method returns[ImmutableMethodReference methodReference] - : reference_type_descriptor SIMPLE_NAME method_prototype +method_reference returns[ImmutableMethodReference methodReference] + : reference_type_descriptor? SIMPLE_NAME method_prototype { - $methodReference = new ImmutableMethodReference($reference_type_descriptor.type, $SIMPLE_NAME.text, + String type; + if ($reference_type_descriptor.type == null) { + type = classType; + } else { + type = $reference_type_descriptor.type; + } + $methodReference = new ImmutableMethodReference(type, $SIMPLE_NAME.text, $method_prototype.parameters, $method_prototype.returnType); }; -fully_qualified_field returns[ImmutableFieldReference fieldReference] - : reference_type_descriptor SIMPLE_NAME nonvoid_type_descriptor +field_reference returns[ImmutableFieldReference fieldReference] + : reference_type_descriptor? SIMPLE_NAME nonvoid_type_descriptor { - $fieldReference = new ImmutableFieldReference($reference_type_descriptor.type, $SIMPLE_NAME.text, + String type; + if ($reference_type_descriptor.type == null) { + type = classType; + } else { + type = $reference_type_descriptor.type; + } + $fieldReference = new ImmutableFieldReference(type, $SIMPLE_NAME.text, $nonvoid_type_descriptor.type); }; @@ -688,13 +700,13 @@ verification_error_reference returns[ImmutableReference reference] { $reference = new ImmutableTypeReference($CLASS_DESCRIPTOR.text); } - | fully_qualified_field + | field_reference { - $reference = $fully_qualified_field.fieldReference; + $reference = $field_reference.fieldReference; } - | fully_qualified_method + | method_reference { - $reference = $fully_qualified_method.methodReference; + $reference = $method_reference.methodReference; }; verification_error_type returns[int verificationError] @@ -816,12 +828,12 @@ insn_format20t insn_format21c_field : //e.g. sget_object v0, java/lang/System/out LJava/io/PrintStream; - ^(I_STATEMENT_FORMAT21c_FIELD inst=(INSTRUCTION_FORMAT21c_FIELD | INSTRUCTION_FORMAT21c_FIELD_ODEX) REGISTER fully_qualified_field) + ^(I_STATEMENT_FORMAT21c_FIELD inst=(INSTRUCTION_FORMAT21c_FIELD | INSTRUCTION_FORMAT21c_FIELD_ODEX) REGISTER field_reference) { Opcode opcode = opcodes.getOpcodeByName($inst.text); short regA = parseRegister_byte($REGISTER.text); - ImmutableFieldReference fieldReference = $fully_qualified_field.fieldReference; + ImmutableFieldReference fieldReference = $field_reference.fieldReference; $method::methodBuilder.addInstruction(new BuilderInstruction21c(opcode, regA, dexBuilder.internFieldReference(fieldReference))); @@ -911,13 +923,13 @@ insn_format22b insn_format22c_field : //e.g. iput-object v1, v0, org/jf/HelloWorld2/HelloWorld2.helloWorld Ljava/lang/String; - ^(I_STATEMENT_FORMAT22c_FIELD inst=(INSTRUCTION_FORMAT22c_FIELD | INSTRUCTION_FORMAT22c_FIELD_ODEX) registerA=REGISTER registerB=REGISTER fully_qualified_field) + ^(I_STATEMENT_FORMAT22c_FIELD inst=(INSTRUCTION_FORMAT22c_FIELD | INSTRUCTION_FORMAT22c_FIELD_ODEX) registerA=REGISTER registerB=REGISTER field_reference) { Opcode opcode = opcodes.getOpcodeByName($inst.text); byte regA = parseRegister_nibble($registerA.text); byte regB = parseRegister_nibble($registerB.text); - ImmutableFieldReference fieldReference = $fully_qualified_field.fieldReference; + ImmutableFieldReference fieldReference = $field_reference.fieldReference; $method::methodBuilder.addInstruction(new BuilderInstruction22c(opcode, regA, regB, dexBuilder.internFieldReference(fieldReference))); @@ -1038,7 +1050,7 @@ insn_format32x insn_format35c_method : //e.g. invoke-virtual {v0,v1} java/io/PrintStream/print(Ljava/lang/Stream;)V - ^(I_STATEMENT_FORMAT35c_METHOD INSTRUCTION_FORMAT35c_METHOD register_list fully_qualified_method) + ^(I_STATEMENT_FORMAT35c_METHOD INSTRUCTION_FORMAT35c_METHOD register_list method_reference) { Opcode opcode = opcodes.getOpcodeByName($INSTRUCTION_FORMAT35c_METHOD.text); @@ -1046,7 +1058,7 @@ insn_format35c_method byte[] registers = $register_list.registers; byte registerCount = $register_list.registerCount; - ImmutableMethodReference methodReference = $fully_qualified_method.methodReference; + ImmutableMethodReference methodReference = $method_reference.methodReference; $method::methodBuilder.addInstruction(new BuilderInstruction35c(opcode, registerCount, registers[0], registers[1], registers[2], registers[3], registers[4], dexBuilder.internMethodReference(methodReference))); @@ -1068,7 +1080,7 @@ insn_format35c_type insn_format3rc_method : //e.g. invoke-virtual/range {v25..v26} java/lang/StringBuilder/append(Ljava/lang/String;)Ljava/lang/StringBuilder; - ^(I_STATEMENT_FORMAT3rc_METHOD INSTRUCTION_FORMAT3rc_METHOD register_range fully_qualified_method) + ^(I_STATEMENT_FORMAT3rc_METHOD INSTRUCTION_FORMAT3rc_METHOD register_range method_reference) { Opcode opcode = opcodes.getOpcodeByName($INSTRUCTION_FORMAT3rc_METHOD.text); int startRegister = $register_range.startRegister; @@ -1076,7 +1088,7 @@ insn_format3rc_method int registerCount = endRegister-startRegister+1; - ImmutableMethodReference methodReference = $fully_qualified_method.methodReference; + ImmutableMethodReference methodReference = $method_reference.methodReference; $method::methodBuilder.addInstruction(new BuilderInstruction3rc(opcode, startRegister, registerCount, dexBuilder.internMethodReference(methodReference))); @@ -1259,19 +1271,19 @@ subannotation returns[String annotationType, List elements] }; field_literal returns[FieldReference value] - : ^(I_ENCODED_FIELD fully_qualified_field) + : ^(I_ENCODED_FIELD field_reference) { - $value = $fully_qualified_field.fieldReference; + $value = $field_reference.fieldReference; }; method_literal returns[MethodReference value] - : ^(I_ENCODED_METHOD fully_qualified_method) + : ^(I_ENCODED_METHOD method_reference) { - $value = $fully_qualified_method.methodReference; + $value = $method_reference.methodReference; }; enum_literal returns[FieldReference value] - : ^(I_ENCODED_ENUM fully_qualified_field) + : ^(I_ENCODED_ENUM field_reference) { - $value = $fully_qualified_field.fieldReference; + $value = $field_reference.fieldReference; }; diff --git a/smali/src/main/java/org/jf/smali/SmaliTestUtils.java b/smali/src/main/java/org/jf/smali/SmaliTestUtils.java new file mode 100644 index 00000000..9fd73473 --- /dev/null +++ b/smali/src/main/java/org/jf/smali/SmaliTestUtils.java @@ -0,0 +1,94 @@ +/* + * 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.smali; + +import com.google.common.collect.Iterables; +import org.antlr.runtime.CommonTokenStream; +import org.antlr.runtime.RecognitionException; +import org.antlr.runtime.TokenSource; +import org.antlr.runtime.tree.CommonTree; +import org.antlr.runtime.tree.CommonTreeNodeStream; +import org.jf.dexlib2.Opcodes; +import org.jf.dexlib2.dexbacked.DexBackedDexFile; +import org.jf.dexlib2.iface.ClassDef; +import org.jf.dexlib2.writer.builder.DexBuilder; +import org.jf.dexlib2.writer.io.MemoryDataStore; + +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; + +public class SmaliTestUtils { + public static ClassDef compileSmali(String smaliText) throws RecognitionException, IOException { + CommonTokenStream tokens; + LexerErrorInterface lexer; + DexBuilder dexBuilder = DexBuilder.makeDexBuilder(15); + + Reader reader = new StringReader(smaliText); + + lexer = new smaliFlexLexer(reader); + tokens = new CommonTokenStream((TokenSource)lexer); + + smaliParser parser = new smaliParser(tokens); + parser.setVerboseErrors(true); + parser.setAllowOdex(false); + parser.setApiLevel(15); + + smaliParser.smali_file_return result = parser.smali_file(); + + if(parser.getNumberOfSyntaxErrors() > 0 || lexer.getNumberOfSyntaxErrors() > 0) { + throw new RuntimeException("Error occured while compiling text"); + } + + CommonTree t = result.getTree(); + + CommonTreeNodeStream treeStream = new CommonTreeNodeStream(t); + treeStream.setTokenStream(tokens); + + smaliTreeWalker dexGen = new smaliTreeWalker(treeStream); + dexGen.setVerboseErrors(true); + dexGen.setDexBuilder(dexBuilder); + dexGen.smali_file(); + + if (dexGen.getNumberOfSyntaxErrors() > 0) { + throw new RuntimeException("Error occured while compiling text"); + } + + MemoryDataStore dataStore = new MemoryDataStore(); + + dexBuilder.writeTo(dataStore); + + DexBackedDexFile dexFile = new DexBackedDexFile(new Opcodes(15), dataStore.getData()); + + return Iterables.getFirst(dexFile.getClasses(), null); + } +} diff --git a/smali/src/test/java/ImplicitReferenceTest.java b/smali/src/test/java/ImplicitReferenceTest.java new file mode 100644 index 00000000..b080c8a5 --- /dev/null +++ b/smali/src/test/java/ImplicitReferenceTest.java @@ -0,0 +1,242 @@ +/* + * 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. + */ + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import junit.framework.Assert; +import org.antlr.runtime.RecognitionException; +import org.jf.dexlib2.Opcode; +import org.jf.dexlib2.ValueType; +import org.jf.dexlib2.iface.ClassDef; +import org.jf.dexlib2.iface.Field; +import org.jf.dexlib2.iface.Method; +import org.jf.dexlib2.iface.MethodImplementation; +import org.jf.dexlib2.iface.instruction.Instruction; +import org.jf.dexlib2.iface.instruction.formats.Instruction21c; +import org.jf.dexlib2.iface.instruction.formats.Instruction35c; +import org.jf.dexlib2.iface.reference.FieldReference; +import org.jf.dexlib2.iface.reference.MethodReference; +import org.jf.dexlib2.iface.value.FieldEncodedValue; +import org.jf.dexlib2.iface.value.MethodEncodedValue; +import org.jf.dexlib2.iface.value.TypeEncodedValue; +import org.jf.smali.SmaliTestUtils; +import org.junit.Test; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +/** + * Tests for method/field references that use an implicit type + */ +public class ImplicitReferenceTest extends SmaliTestUtils { + @Test + public void testImplicitMethodReference() throws RecognitionException, IOException { + 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}, toString()V\n" + + " invoke-static {p0}, V()V\n" + + " invoke-static {p0}, I()V\n" + + " return-void\n" + + ".end method"); + + Method mainMethod = null; + for (Method method: classDef.getMethods()) { + if (method.getName().equals("main")) { + mainMethod = method; + } + } + Assert.assertNotNull(mainMethod); + + MethodImplementation methodImpl = mainMethod.getImplementation(); + Assert.assertNotNull(methodImpl); + + List instructions = Lists.newArrayList(methodImpl.getInstructions()); + + Instruction35c instruction = (Instruction35c)instructions.get(0); + Assert.assertNotNull(instruction); + Assert.assertEquals(Opcode.INVOKE_STATIC, instruction.getOpcode()); + MethodReference method = (MethodReference)instruction.getReference(); + Assert.assertEquals(classDef.getType(), method.getDefiningClass()); + Assert.assertEquals("toString", method.getName()); + + instruction = (Instruction35c)instructions.get(1); + Assert.assertNotNull(instruction); + Assert.assertEquals(Opcode.INVOKE_STATIC, instruction.getOpcode()); + method = (MethodReference)instruction.getReference(); + Assert.assertEquals(classDef.getType(), method.getDefiningClass()); + Assert.assertEquals("V", method.getName()); + + instruction = (Instruction35c)instructions.get(2); + Assert.assertNotNull(instruction); + Assert.assertEquals(Opcode.INVOKE_STATIC, instruction.getOpcode()); + method = (MethodReference)instruction.getReference(); + Assert.assertEquals(classDef.getType(), method.getDefiningClass()); + Assert.assertEquals("I", method.getName()); + } + + @Test + public void testImplicitMethodLiteral() throws RecognitionException, IOException { + ClassDef classDef = SmaliTestUtils.compileSmali("" + + ".class public LHelloWorld;\n" + + ".super Ljava/lang/Object;\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"); + + Map fields = Maps.newHashMap(); + for (Field field: classDef.getFields()) { + fields.put(field.getName(), field); + } + + Field field = fields.get("field1"); + Assert.assertNotNull(field); + Assert.assertNotNull(field.getInitialValue()); + Assert.assertEquals(ValueType.METHOD, field.getInitialValue().getValueType()); + MethodEncodedValue methodEncodedValue = (MethodEncodedValue)field.getInitialValue(); + Assert.assertEquals(classDef.getType(), methodEncodedValue.getValue().getDefiningClass()); + Assert.assertEquals("toString", methodEncodedValue.getValue().getName()); + + field = fields.get("field2"); + Assert.assertNotNull(field); + Assert.assertNotNull(field.getInitialValue()); + Assert.assertEquals(ValueType.METHOD, field.getInitialValue().getValueType()); + methodEncodedValue = (MethodEncodedValue)field.getInitialValue(); + Assert.assertEquals(classDef.getType(), methodEncodedValue.getValue().getDefiningClass()); + Assert.assertEquals("V", methodEncodedValue.getValue().getName()); + + field = fields.get("field3"); + Assert.assertNotNull(field); + Assert.assertNotNull(field.getInitialValue()); + Assert.assertEquals(ValueType.METHOD, field.getInitialValue().getValueType()); + methodEncodedValue = (MethodEncodedValue)field.getInitialValue(); + Assert.assertEquals(classDef.getType(), methodEncodedValue.getValue().getDefiningClass()); + Assert.assertEquals("I", methodEncodedValue.getValue().getName()); + + field = fields.get("field4"); + Assert.assertNotNull(field); + Assert.assertNotNull(field.getInitialValue()); + Assert.assertEquals(ValueType.TYPE, field.getInitialValue().getValueType()); + TypeEncodedValue typeEncodedValue = (TypeEncodedValue)field.getInitialValue(); + Assert.assertEquals("I", typeEncodedValue.getValue()); + } + + @Test + public void testImplicitFieldReference() throws RecognitionException, IOException { + 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-object v0, someField:I\n" + + " sget-object v0, V:I\n" + + " sget-object v0, I:I\n" + + " return-void\n" + + ".end method"); + + Method mainMethod = null; + for (Method method: classDef.getMethods()) { + if (method.getName().equals("main")) { + mainMethod = method; + } + } + Assert.assertNotNull(mainMethod); + + MethodImplementation methodImpl = mainMethod.getImplementation(); + Assert.assertNotNull(methodImpl); + + List instructions = Lists.newArrayList(methodImpl.getInstructions()); + + Instruction21c instruction = (Instruction21c)instructions.get(0); + Assert.assertNotNull(instruction); + Assert.assertEquals(Opcode.SGET_OBJECT, instruction.getOpcode()); + FieldReference field = (FieldReference)instruction.getReference(); + Assert.assertEquals(classDef.getType(), field.getDefiningClass()); + Assert.assertEquals("someField", field.getName()); + + instruction = (Instruction21c)instructions.get(1); + Assert.assertNotNull(instruction); + Assert.assertEquals(Opcode.SGET_OBJECT, instruction.getOpcode()); + field = (FieldReference)instruction.getReference(); + Assert.assertEquals(classDef.getType(), field.getDefiningClass()); + Assert.assertEquals("V", field.getName()); + + instruction = (Instruction21c)instructions.get(2); + Assert.assertNotNull(instruction); + Assert.assertEquals(Opcode.SGET_OBJECT, instruction.getOpcode()); + field = (FieldReference)instruction.getReference(); + Assert.assertEquals(classDef.getType(), field.getDefiningClass()); + Assert.assertEquals("I", field.getName()); + } + + @Test + public void testImplicitFieldLiteral() throws RecognitionException, IOException { + ClassDef classDef = SmaliTestUtils.compileSmali("" + + ".class public LHelloWorld;\n" + + ".super Ljava/lang/Object;\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"); + + Map fields = Maps.newHashMap(); + for (Field field: classDef.getFields()) { + fields.put(field.getName(), field); + } + + Field field = fields.get("field1"); + Assert.assertNotNull(field); + Assert.assertNotNull(field.getInitialValue()); + Assert.assertEquals(ValueType.FIELD, field.getInitialValue().getValueType()); + FieldEncodedValue fieldEncodedValue = (FieldEncodedValue)field.getInitialValue(); + Assert.assertEquals(classDef.getType(), fieldEncodedValue.getValue().getDefiningClass()); + Assert.assertEquals("someField", fieldEncodedValue.getValue().getName()); + + field = fields.get("field2"); + Assert.assertNotNull(field); + Assert.assertNotNull(field.getInitialValue()); + Assert.assertEquals(ValueType.FIELD, field.getInitialValue().getValueType()); + fieldEncodedValue = (FieldEncodedValue)field.getInitialValue(); + Assert.assertEquals(classDef.getType(), fieldEncodedValue.getValue().getDefiningClass()); + Assert.assertEquals("V", fieldEncodedValue.getValue().getName()); + + field = fields.get("field3"); + Assert.assertNotNull(field); + Assert.assertNotNull(field.getInitialValue()); + Assert.assertEquals(ValueType.FIELD, field.getInitialValue().getValueType()); + fieldEncodedValue = (FieldEncodedValue)field.getInitialValue(); + Assert.assertEquals(classDef.getType(), fieldEncodedValue.getValue().getDefiningClass()); + Assert.assertEquals("I", fieldEncodedValue.getValue().getName()); + } +}