diff --git a/baksmali/src/main/java/org/jf/baksmali/baksmali.java b/baksmali/src/main/java/org/jf/baksmali/baksmali.java index dbc26485..b8911e77 100644 --- a/baksmali/src/main/java/org/jf/baksmali/baksmali.java +++ b/baksmali/src/main/java/org/jf/baksmali/baksmali.java @@ -33,6 +33,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import org.jf.baksmali.Adaptors.ClassDefinition; import org.jf.dexlib2.analysis.ClassPath; +import org.jf.dexlib2.analysis.CustomInlineMethodResolver; import org.jf.dexlib2.dexbacked.DexBackedOdexFile; import org.jf.dexlib2.iface.ClassDef; import org.jf.dexlib2.iface.DexFile; @@ -87,10 +88,9 @@ public class baksmali { options.classPath = ClassPath.fromClassPath(Arrays.asList(classPathDirs), Iterables.concat(bootClassPaths, extraBootClassPaths), dexFile); - // TODO: uncomment - /*if (inlineTable != null) { - inlineResolver = new CustomInlineMethodResolver(inlineTable); - }*/ + if (inlineTable != null) { + options.inlineResolver = new CustomInlineMethodResolver(options.classPath, new File(inlineTable)); + } } catch (Exception ex) { System.err.println("\n\nError occured while loading boot class path files. Aborting."); ex.printStackTrace(System.err); diff --git a/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassProto.java b/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassProto.java index fdd48a1a..cb3dca38 100644 --- a/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassProto.java +++ b/dexlib2/src/main/java/org/jf/dexlib2/analysis/ClassProto.java @@ -70,7 +70,7 @@ public class ClassProto implements TypeProto { @Nonnull @Override public String getType() { return type; } @Nonnull - protected ClassDef getClassDef() { + public ClassDef getClassDef() { if (classDef == null) { classDef = classPath.getClassDef(type); } diff --git a/dexlib2/src/main/java/org/jf/dexlib2/analysis/CustomInlineMethodResolver.java b/dexlib2/src/main/java/org/jf/dexlib2/analysis/CustomInlineMethodResolver.java new file mode 100644 index 00000000..c88eb082 --- /dev/null +++ b/dexlib2/src/main/java/org/jf/dexlib2/analysis/CustomInlineMethodResolver.java @@ -0,0 +1,138 @@ +/* + * Copyright 2013, 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.analysis; + +import com.google.common.io.Files; +import org.jf.dexlib2.iface.ClassDef; +import org.jf.dexlib2.iface.Method; +import org.jf.dexlib2.iface.instruction.InlineIndexInstruction; +import org.jf.dexlib2.immutable.ImmutableMethod; +import org.jf.dexlib2.immutable.ImmutableMethodParameter; +import org.jf.dexlib2.immutable.reference.ImmutableMethodReference; +import org.jf.dexlib2.immutable.util.ParamUtil; + +import javax.annotation.Nonnull; +import java.io.*; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class CustomInlineMethodResolver extends InlineMethodResolver { + @Nonnull private final ClassPath classPath; + @Nonnull private final Method[] inlineMethods; + + public CustomInlineMethodResolver(@Nonnull ClassPath classPath, @Nonnull String inlineTable) { + this.classPath = classPath; + + StringReader reader = new StringReader(inlineTable); + List lines = new ArrayList(); + + BufferedReader br = new BufferedReader(reader); + + try { + String line = br.readLine(); + + while (line != null) { + if (line.length() > 0) { + lines.add(line); + } + + line = br.readLine(); + } + } catch (IOException ex) { + throw new RuntimeException("Error while parsing inline table", ex); + } + + inlineMethods = new Method[lines.size()]; + + for (int i=0; i= inlineMethods.length) { + throw new RuntimeException("Invalid method index: " + methodIndex); + } + return inlineMethods[methodIndex]; + } + + private static final Pattern longMethodPattern = Pattern.compile("(L[^;]+;)->([^(]+)\\(([^)]*)\\)(.+)"); + + @Nonnull + private Method parseAndResolveInlineMethod(@Nonnull String inlineMethod) { + Matcher m = longMethodPattern.matcher(inlineMethod); + if (!m.matches()) { + assert false; + throw new RuntimeException("Invalid method descriptor: " + inlineMethod); + } + + String className = m.group(1); + String methodName = m.group(2); + Iterable methodParams = ParamUtil.parseParamString(m.group(3)); + String methodRet = m.group(4); + ImmutableMethodReference methodRef = new ImmutableMethodReference(className, methodName, methodParams, + methodRet); + + int accessFlags = 0; + + boolean resolved = false; + TypeProto typeProto = classPath.getClass(className); + if (typeProto instanceof ClassProto) { + ClassDef classDef = ((ClassProto)typeProto).getClassDef(); + for (Method method: classDef.getMethods()) { + if (method.equals(methodRef)) { + resolved = true; + accessFlags = method.getAccessFlags(); + break; + } + } + } + + if (!resolved) { + throw new RuntimeException("Cannot resolve inline method: " + inlineMethod); + } + + return new ImmutableMethod(className, methodName, methodParams, methodRet, accessFlags, null, null); + } +} diff --git a/dexlib2/src/main/java/org/jf/dexlib2/analysis/InlineMethodResolver.java b/dexlib2/src/main/java/org/jf/dexlib2/analysis/InlineMethodResolver.java index f11c54ff..4b2e7e64 100644 --- a/dexlib2/src/main/java/org/jf/dexlib2/analysis/InlineMethodResolver.java +++ b/dexlib2/src/main/java/org/jf/dexlib2/analysis/InlineMethodResolver.java @@ -46,7 +46,7 @@ public abstract class InlineMethodResolver { // We can't use, e.g. AccessFlags.STATIC.value, because we need them to be a constant in order to use them as cases // in switch statements public static final int STATIC = 0x8; // AccessFlags.STATIC.value; - public static final int VIRTUAL = 0x1; // AccessFlags.PRIVATE.value; + public static final int VIRTUAL = 0x1; // AccessFlags.PUBLIC.value; public static final int DIRECT = 0x2; // AccessFlags.PRIVATE.value; @Nonnull @@ -116,7 +116,6 @@ public abstract class InlineMethodResolver { private final Method fastIndexOfMethod; private final Method isEmptyMethod; - public InlineMethodResolver_version36() { //The 5th and 6th entries differ between froyo and gingerbread. We have to look at the parameters being //passed to distinguish between them. diff --git a/dexlib2/src/main/java/org/jf/dexlib2/analysis/MethodAnalyzer.java b/dexlib2/src/main/java/org/jf/dexlib2/analysis/MethodAnalyzer.java index 7433d459..7cc47fa2 100644 --- a/dexlib2/src/main/java/org/jf/dexlib2/analysis/MethodAnalyzer.java +++ b/dexlib2/src/main/java/org/jf/dexlib2/analysis/MethodAnalyzer.java @@ -31,7 +31,10 @@ package org.jf.dexlib2.analysis; +import com.google.common.base.Function; import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import org.jf.dexlib2.AccessFlags; import org.jf.dexlib2.Opcode; import org.jf.dexlib2.iface.*; import org.jf.dexlib2.iface.instruction.*; @@ -45,7 +48,6 @@ import org.jf.dexlib2.util.MethodUtil; import org.jf.dexlib2.util.ReferenceUtil; import org.jf.dexlib2.util.TypeUtils; import org.jf.util.BitSetUtils; -import org.jf.util.ExceptionWithContext; import org.jf.util.SparseArray; import javax.annotation.Nonnull; @@ -298,6 +300,17 @@ public class MethodAnalyzer { return analyzedInstructions.getValues(); } + public List getInstructions() { + return Lists.transform(analyzedInstructions.getValues(), new Function() { + @Nullable @Override public Instruction apply(@Nullable AnalyzedInstruction input) { + if (input == null) { + return null; + } + return input.instruction; + } + }); + } + @Nullable public AnalysisException getAnalysisException() { return analysisException; @@ -1406,19 +1419,13 @@ public class MethodAnalyzer { Method resolvedMethod = inlineResolver.resolveExecuteInline(analyzedInstruction); Opcode deodexedOpcode; - switch (resolvedMethod.getAccessFlags()) { - case InlineMethodResolver.DIRECT: - deodexedOpcode = Opcode.INVOKE_DIRECT; - break; - case InlineMethodResolver.STATIC: - deodexedOpcode = Opcode.INVOKE_STATIC; - break; - case InlineMethodResolver.VIRTUAL: - deodexedOpcode = Opcode.INVOKE_VIRTUAL; - break; - default: - throw new ExceptionWithContext("Unexpected access flags on resolved inline method: %d, %s", - resolvedMethod.getAccessFlags(), ReferenceUtil.getReferenceString(resolvedMethod)); + int acccessFlags = resolvedMethod.getAccessFlags(); + if (AccessFlags.STATIC.isSet(acccessFlags)) { + deodexedOpcode = Opcode.INVOKE_STATIC; + } else if (AccessFlags.PRIVATE.isSet(acccessFlags)) { + deodexedOpcode = Opcode.INVOKE_DIRECT; + } else { + deodexedOpcode = Opcode.INVOKE_VIRTUAL; } Instruction35c deodexedInstruction = new ImmutableInstruction35c(deodexedOpcode, instruction.getRegisterCount(), @@ -1437,19 +1444,14 @@ public class MethodAnalyzer { Instruction3rmi instruction = (Instruction3rmi)analyzedInstruction.instruction; Method resolvedMethod = inlineResolver.resolveExecuteInline(analyzedInstruction); - Opcode deodexedOpcode = null; - switch (resolvedMethod.getAccessFlags()) { - case InlineMethodResolver.DIRECT: - deodexedOpcode = Opcode.INVOKE_DIRECT_RANGE; - break; - case InlineMethodResolver.STATIC: - deodexedOpcode = Opcode.INVOKE_STATIC_RANGE; - break; - case InlineMethodResolver.VIRTUAL: - deodexedOpcode = Opcode.INVOKE_VIRTUAL_RANGE; - break; - default: - assert false; + Opcode deodexedOpcode; + int acccessFlags = resolvedMethod.getAccessFlags(); + if (AccessFlags.STATIC.isSet(acccessFlags)) { + deodexedOpcode = Opcode.INVOKE_STATIC_RANGE; + } else if (AccessFlags.PRIVATE.isSet(acccessFlags)) { + deodexedOpcode = Opcode.INVOKE_DIRECT_RANGE; + } else { + deodexedOpcode = Opcode.INVOKE_VIRTUAL_RANGE; } Instruction3rc deodexedInstruction = new ImmutableInstruction3rc(deodexedOpcode, instruction.getRegisterCount(), diff --git a/dexlib2/src/main/java/org/jf/dexlib2/immutable/ImmutableMethod.java b/dexlib2/src/main/java/org/jf/dexlib2/immutable/ImmutableMethod.java index aab5ea03..8d8d0692 100644 --- a/dexlib2/src/main/java/org/jf/dexlib2/immutable/ImmutableMethod.java +++ b/dexlib2/src/main/java/org/jf/dexlib2/immutable/ImmutableMethod.java @@ -45,7 +45,6 @@ import org.jf.util.ImmutableUtils; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import java.util.List; import java.util.Set; public class ImmutableMethod extends BaseMethodReference implements Method { @@ -59,7 +58,7 @@ public class ImmutableMethod extends BaseMethodReference implements Method { public ImmutableMethod(@Nonnull String definingClass, @Nonnull String name, - @Nullable List parameters, + @Nullable Iterable parameters, @Nonnull String returnType, int accessFlags, @Nullable Set annotations, diff --git a/dexlib2/src/main/java/org/jf/dexlib2/immutable/reference/ImmutableMethodReference.java b/dexlib2/src/main/java/org/jf/dexlib2/immutable/reference/ImmutableMethodReference.java index 6669230c..fed09980 100644 --- a/dexlib2/src/main/java/org/jf/dexlib2/immutable/reference/ImmutableMethodReference.java +++ b/dexlib2/src/main/java/org/jf/dexlib2/immutable/reference/ImmutableMethodReference.java @@ -39,7 +39,6 @@ import org.jf.util.ImmutableUtils; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import java.util.List; public class ImmutableMethodReference extends BaseMethodReference implements ImmutableReference { @Nonnull protected final String definingClass; @@ -49,11 +48,11 @@ public class ImmutableMethodReference extends BaseMethodReference implements Imm public ImmutableMethodReference(@Nonnull String definingClass, @Nonnull String name, - @Nullable List parameters, + @Nullable Iterable parameters, @Nonnull String returnType) { this.definingClass = definingClass; this.name = name; - this.parameters = ImmutableList.copyOf(parameters); + this.parameters = CharSequenceConverter.immutableStringList(parameters); this.returnType = returnType; } @@ -75,7 +74,7 @@ public class ImmutableMethodReference extends BaseMethodReference implements Imm return new ImmutableMethodReference( methodReference.getDefiningClass(), methodReference.getName(), - CharSequenceConverter.immutableStringList(methodReference.getParameterTypes()), + methodReference.getParameterTypes(), methodReference.getReturnType()); } diff --git a/dexlib2/src/main/java/org/jf/dexlib2/immutable/util/CharSequenceConverter.java b/dexlib2/src/main/java/org/jf/dexlib2/immutable/util/CharSequenceConverter.java index f5ae85f1..596e8e36 100644 --- a/dexlib2/src/main/java/org/jf/dexlib2/immutable/util/CharSequenceConverter.java +++ b/dexlib2/src/main/java/org/jf/dexlib2/immutable/util/CharSequenceConverter.java @@ -36,15 +36,14 @@ import org.jf.util.ImmutableConverter; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import java.util.List; public final class CharSequenceConverter { private CharSequenceConverter() { } @Nonnull - public static ImmutableList immutableStringList(@Nullable List list) { - return CONVERTER.toList(list); + public static ImmutableList immutableStringList(@Nullable Iterable iterable) { + return CONVERTER.toList(iterable); } private static final ImmutableConverter CONVERTER = diff --git a/dexlib2/src/test/java/org/jf/dexlib2/analysis/CustomMethodInlineTableTest.java b/dexlib2/src/test/java/org/jf/dexlib2/analysis/CustomMethodInlineTableTest.java new file mode 100644 index 00000000..f37e3ba4 --- /dev/null +++ b/dexlib2/src/test/java/org/jf/dexlib2/analysis/CustomMethodInlineTableTest.java @@ -0,0 +1,133 @@ +/* + * Copyright 2013, 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.analysis; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import org.jf.dexlib2.AccessFlags; +import org.jf.dexlib2.Opcode; +import org.jf.dexlib2.iface.ClassDef; +import org.jf.dexlib2.iface.DexFile; +import org.jf.dexlib2.iface.instruction.Instruction; +import org.jf.dexlib2.iface.instruction.formats.Instruction35c; +import org.jf.dexlib2.iface.reference.MethodReference; +import org.jf.dexlib2.immutable.ImmutableClassDef; +import org.jf.dexlib2.immutable.ImmutableDexFile; +import org.jf.dexlib2.immutable.ImmutableMethod; +import org.jf.dexlib2.immutable.ImmutableMethodImplementation; +import org.jf.dexlib2.immutable.instruction.ImmutableInstruction; +import org.jf.dexlib2.immutable.instruction.ImmutableInstruction10x; +import org.jf.dexlib2.immutable.instruction.ImmutableInstruction35mi; +import org.junit.Assert; +import org.junit.Test; + +import java.util.List; + +public class CustomMethodInlineTableTest { + @Test + public void testCustomMethodInlineTable_Virtual() { + List instructions = Lists.newArrayList( + new ImmutableInstruction35mi(Opcode.EXECUTE_INLINE, 1, 0, 0, 0, 0, 0, 0), + new ImmutableInstruction10x(Opcode.RETURN_VOID)); + + ImmutableMethodImplementation methodImpl = new ImmutableMethodImplementation(1, instructions, null, null); + ImmutableMethod method = new ImmutableMethod("Lblah;", "blah", null, "V", AccessFlags.PUBLIC.getValue(), null, + methodImpl); + + ClassDef classDef = new ImmutableClassDef("Lblah;", AccessFlags.PUBLIC.getValue(), "Ljava/lang/Object;", null, + null, null, null, ImmutableList.of(method)); + + DexFile dexFile = new ImmutableDexFile(ImmutableList.of(classDef)); + + ClassPath classPath = ClassPath.fromClassPath(ImmutableList.of(), ImmutableList.of(), dexFile); + InlineMethodResolver inlineMethodResolver = new CustomInlineMethodResolver(classPath, "Lblah;->blah()V"); + MethodAnalyzer methodAnalyzer = new MethodAnalyzer(classPath, method, inlineMethodResolver); + + Instruction deodexedInstruction = methodAnalyzer.getInstructions().get(0); + Assert.assertEquals(Opcode.INVOKE_VIRTUAL, deodexedInstruction.getOpcode()); + + MethodReference methodReference = (MethodReference)((Instruction35c)deodexedInstruction).getReference(); + Assert.assertEquals(method, methodReference); + } + + @Test + public void testCustomMethodInlineTable_Static() { + List instructions = Lists.newArrayList( + new ImmutableInstruction35mi(Opcode.EXECUTE_INLINE, 1, 0, 0, 0, 0, 0, 0), + new ImmutableInstruction10x(Opcode.RETURN_VOID)); + + ImmutableMethodImplementation methodImpl = new ImmutableMethodImplementation(1, instructions, null, null); + ImmutableMethod method = new ImmutableMethod("Lblah;", "blah", null, "V", AccessFlags.STATIC.getValue(), null, + methodImpl); + + ClassDef classDef = new ImmutableClassDef("Lblah;", AccessFlags.PUBLIC.getValue(), "Ljava/lang/Object;", null, + null, null, null, ImmutableList.of(method)); + + DexFile dexFile = new ImmutableDexFile(ImmutableList.of(classDef)); + + ClassPath classPath = ClassPath.fromClassPath(ImmutableList.of(), ImmutableList.of(), dexFile); + InlineMethodResolver inlineMethodResolver = new CustomInlineMethodResolver(classPath, "Lblah;->blah()V"); + MethodAnalyzer methodAnalyzer = new MethodAnalyzer(classPath, method, inlineMethodResolver); + + Instruction deodexedInstruction = methodAnalyzer.getInstructions().get(0); + Assert.assertEquals(Opcode.INVOKE_STATIC, deodexedInstruction.getOpcode()); + + MethodReference methodReference = (MethodReference)((Instruction35c)deodexedInstruction).getReference(); + Assert.assertEquals(method, methodReference); + } + + @Test + public void testCustomMethodInlineTable_Direct() { + List instructions = Lists.newArrayList( + new ImmutableInstruction35mi(Opcode.EXECUTE_INLINE, 1, 0, 0, 0, 0, 0, 0), + new ImmutableInstruction10x(Opcode.RETURN_VOID)); + + ImmutableMethodImplementation methodImpl = new ImmutableMethodImplementation(1, instructions, null, null); + ImmutableMethod method = new ImmutableMethod("Lblah;", "blah", null, "V", AccessFlags.PRIVATE.getValue(), null, + methodImpl); + + ClassDef classDef = new ImmutableClassDef("Lblah;", AccessFlags.PUBLIC.getValue(), "Ljava/lang/Object;", null, + null, null, null, ImmutableList.of(method)); + + DexFile dexFile = new ImmutableDexFile(ImmutableList.of(classDef)); + + ClassPath classPath = ClassPath.fromClassPath(ImmutableList.of(), ImmutableList.of(), dexFile); + InlineMethodResolver inlineMethodResolver = new CustomInlineMethodResolver(classPath, "Lblah;->blah()V"); + MethodAnalyzer methodAnalyzer = new MethodAnalyzer(classPath, method, inlineMethodResolver); + + Instruction deodexedInstruction = methodAnalyzer.getInstructions().get(0); + Assert.assertEquals(Opcode.INVOKE_DIRECT, deodexedInstruction.getOpcode()); + + MethodReference methodReference = (MethodReference)((Instruction35c)deodexedInstruction).getReference(); + Assert.assertEquals(method, methodReference); + } +}