mirror of
https://github.com/revanced/smali.git
synced 2025-05-29 12:20:11 +02:00
Add support for normalizing virtual methods
This is useful, for example, when comparing the result of deodexing with the original dex file, to remove the "false" differences caused by the different potential ways to reference a given virtual method.
This commit is contained in:
parent
c8c70ac58e
commit
827e2db34d
@ -366,7 +366,8 @@ public class MethodDefinition {
|
|||||||
private List<MethodItem> getMethodItems() {
|
private List<MethodItem> getMethodItems() {
|
||||||
ArrayList<MethodItem> methodItems = new ArrayList<MethodItem>();
|
ArrayList<MethodItem> methodItems = new ArrayList<MethodItem>();
|
||||||
|
|
||||||
if ((classDef.options.registerInfo != 0) || (classDef.options.deodex && needsAnalyzed())) {
|
if ((classDef.options.registerInfo != 0) || (classDef.options.normalizeVirtualMethods) ||
|
||||||
|
(classDef.options.deodex && needsAnalyzed())) {
|
||||||
addAnalyzedInstructionMethodItems(methodItems);
|
addAnalyzedInstructionMethodItems(methodItems);
|
||||||
} else {
|
} else {
|
||||||
addInstructionMethodItems(methodItems);
|
addInstructionMethodItems(methodItems);
|
||||||
@ -460,7 +461,7 @@ public class MethodDefinition {
|
|||||||
|
|
||||||
private void addAnalyzedInstructionMethodItems(List<MethodItem> methodItems) {
|
private void addAnalyzedInstructionMethodItems(List<MethodItem> methodItems) {
|
||||||
MethodAnalyzer methodAnalyzer = new MethodAnalyzer(classDef.options.classPath, method,
|
MethodAnalyzer methodAnalyzer = new MethodAnalyzer(classDef.options.classPath, method,
|
||||||
classDef.options.inlineResolver);
|
classDef.options.inlineResolver, classDef.options.normalizeVirtualMethods);
|
||||||
|
|
||||||
AnalysisException analysisException = methodAnalyzer.getAnalysisException();
|
AnalysisException analysisException = methodAnalyzer.getAnalysisException();
|
||||||
if (analysisException != null) {
|
if (analysisException != null) {
|
||||||
|
@ -44,19 +44,18 @@ import org.xml.sax.Attributes;
|
|||||||
import org.xml.sax.SAXException;
|
import org.xml.sax.SAXException;
|
||||||
import org.xml.sax.helpers.DefaultHandler;
|
import org.xml.sax.helpers.DefaultHandler;
|
||||||
|
|
||||||
|
import javax.xml.parsers.ParserConfigurationException;
|
||||||
|
import javax.xml.parsers.SAXParser;
|
||||||
|
import javax.xml.parsers.SAXParserFactory;
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.*;
|
||||||
|
|
||||||
import javax.xml.parsers.SAXParser;
|
|
||||||
import javax.xml.parsers.SAXParserFactory;
|
|
||||||
import javax.xml.parsers.ParserConfigurationException;
|
|
||||||
|
|
||||||
public class baksmali {
|
public class baksmali {
|
||||||
|
|
||||||
public static boolean disassembleDexFile(DexFile dexFile, final baksmaliOptions options) {
|
public static boolean disassembleDexFile(DexFile dexFile, final baksmaliOptions options) {
|
||||||
if (options.registerInfo != 0 || options.deodex) {
|
if (options.registerInfo != 0 || options.deodex || options.normalizeVirtualMethods) {
|
||||||
try {
|
try {
|
||||||
Iterable<String> extraClassPathEntries;
|
Iterable<String> extraClassPathEntries;
|
||||||
if (options.extraClassPathEntries != null) {
|
if (options.extraClassPathEntries != null) {
|
||||||
|
@ -76,6 +76,7 @@ public class baksmaliOptions {
|
|||||||
public boolean ignoreErrors = false;
|
public boolean ignoreErrors = false;
|
||||||
public boolean checkPackagePrivateAccess = false;
|
public boolean checkPackagePrivateAccess = false;
|
||||||
public boolean useImplicitReferences = false;
|
public boolean useImplicitReferences = false;
|
||||||
|
public boolean normalizeVirtualMethods = false;
|
||||||
public File customInlineDefinitions = null;
|
public File customInlineDefinitions = null;
|
||||||
public InlineMethodResolver inlineResolver = null;
|
public InlineMethodResolver inlineResolver = null;
|
||||||
public int registerInfo = 0;
|
public int registerInfo = 0;
|
||||||
|
@ -219,6 +219,9 @@ public class main {
|
|||||||
case 'k':
|
case 'k':
|
||||||
options.checkPackagePrivateAccess = true;
|
options.checkPackagePrivateAccess = true;
|
||||||
break;
|
break;
|
||||||
|
case 'n':
|
||||||
|
options.normalizeVirtualMethods = true;
|
||||||
|
break;
|
||||||
case 'N':
|
case 'N':
|
||||||
disassemble = false;
|
disassemble = false;
|
||||||
break;
|
break;
|
||||||
@ -282,7 +285,7 @@ public class main {
|
|||||||
options.deodex = false;
|
options.deodex = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!setBootClassPath && (options.deodex || options.registerInfo != 0)) {
|
if (!setBootClassPath && (options.deodex || options.registerInfo != 0 || options.normalizeVirtualMethods)) {
|
||||||
if (dexFile instanceof DexBackedOdexFile) {
|
if (dexFile instanceof DexBackedOdexFile) {
|
||||||
options.bootClassPathEntries = ((DexBackedOdexFile)dexFile).getDependencies();
|
options.bootClassPathEntries = ((DexBackedOdexFile)dexFile).getDependencies();
|
||||||
} else {
|
} else {
|
||||||
@ -457,6 +460,10 @@ public class main {
|
|||||||
"4.2.1.")
|
"4.2.1.")
|
||||||
.create("k");
|
.create("k");
|
||||||
|
|
||||||
|
Option normalizeVirtualMethods = OptionBuilder.withLongOpt("normalize-virtual-methods")
|
||||||
|
.withDescription("Normalize virtual method references to the reference the base method.")
|
||||||
|
.create("n");
|
||||||
|
|
||||||
Option dumpOption = OptionBuilder.withLongOpt("dump-to")
|
Option dumpOption = OptionBuilder.withLongOpt("dump-to")
|
||||||
.withDescription("dumps the given dex file into a single annotated dump file named FILE" +
|
.withDescription("dumps the given dex file into a single annotated dump file named FILE" +
|
||||||
" (<dexfile>.dump by default), along with the normal disassembly")
|
" (<dexfile>.dump by default), along with the normal disassembly")
|
||||||
@ -506,6 +513,7 @@ public class main {
|
|||||||
basicOptions.addOption(noImplicitReferencesOption);
|
basicOptions.addOption(noImplicitReferencesOption);
|
||||||
basicOptions.addOption(dexEntryOption);
|
basicOptions.addOption(dexEntryOption);
|
||||||
basicOptions.addOption(checkPackagePrivateAccessOption);
|
basicOptions.addOption(checkPackagePrivateAccessOption);
|
||||||
|
basicOptions.addOption(normalizeVirtualMethods);
|
||||||
|
|
||||||
debugOptions.addOption(dumpOption);
|
debugOptions.addOption(dumpOption);
|
||||||
debugOptions.addOption(ignoreErrorsOption);
|
debugOptions.addOption(ignoreErrorsOption);
|
||||||
|
@ -0,0 +1,70 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2015, Google Inc.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are
|
||||||
|
* met:
|
||||||
|
*
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
* copyright notice, this list of conditions and the following disclaimer
|
||||||
|
* in the documentation and/or other materials provided with the
|
||||||
|
* distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
* contributors may be used to endorse or promote products derived from
|
||||||
|
* this software without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jf.dexlib2.analysis;
|
||||||
|
|
||||||
|
import org.jf.dexlib2.AccessFlags;
|
||||||
|
import org.jf.dexlib2.analysis.util.TypeProtoUtils;
|
||||||
|
import org.jf.dexlib2.iface.ClassDef;
|
||||||
|
import org.jf.dexlib2.iface.Method;
|
||||||
|
import org.jf.dexlib2.util.MethodUtil;
|
||||||
|
import org.jf.dexlib2.util.TypeUtils;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
public class AnalyzedMethodUtil {
|
||||||
|
public static boolean canAccess(@Nonnull TypeProto type, @Nonnull Method virtualMethod, boolean checkPackagePrivate,
|
||||||
|
boolean checkProtected, boolean checkClass) {
|
||||||
|
if (checkPackagePrivate && MethodUtil.isPackagePrivate(virtualMethod)) {
|
||||||
|
String otherPackage = TypeUtils.getPackage(virtualMethod.getDefiningClass());
|
||||||
|
String thisPackage = TypeUtils.getPackage(type.getType());
|
||||||
|
if (!otherPackage.equals(thisPackage)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (checkProtected && (virtualMethod.getAccessFlags() & AccessFlags.PROTECTED.getValue()) != 0) {
|
||||||
|
if (!TypeProtoUtils.extendsFrom(type, virtualMethod.getDefiningClass())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (checkClass) {
|
||||||
|
ClassPath classPath = type.getClassPath();
|
||||||
|
ClassDef methodClassDef = classPath.getClassDef(virtualMethod.getDefiningClass());
|
||||||
|
if (!TypeUtils.canAccessClass(type.getType(), methodClassDef)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
@ -32,6 +32,7 @@
|
|||||||
package org.jf.dexlib2.analysis;
|
package org.jf.dexlib2.analysis;
|
||||||
|
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
|
import org.jf.dexlib2.iface.Method;
|
||||||
import org.jf.dexlib2.iface.reference.FieldReference;
|
import org.jf.dexlib2.iface.reference.FieldReference;
|
||||||
import org.jf.dexlib2.iface.reference.MethodReference;
|
import org.jf.dexlib2.iface.reference.MethodReference;
|
||||||
import org.jf.dexlib2.immutable.reference.ImmutableFieldReference;
|
import org.jf.dexlib2.immutable.reference.ImmutableFieldReference;
|
||||||
@ -160,7 +161,11 @@ public class ArrayProto implements TypeProto {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
public MethodReference getMethodByVtableIndex(int vtableIndex) {
|
public Method getMethodByVtableIndex(int vtableIndex) {
|
||||||
return classPath.getClass("Ljava/lang/Object;").getMethodByVtableIndex(vtableIndex);
|
return classPath.getClass("Ljava/lang/Object;").getMethodByVtableIndex(vtableIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override public int findMethodIndexInVtable(@Nonnull MethodReference method) {
|
||||||
|
return classPath.getClass("Ljava/lang/Object;").findMethodIndexInVtable(method);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -146,7 +146,7 @@ public class ClassPath {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
public TypeProto getClass(CharSequence type) {
|
public TypeProto getClass(@Nonnull CharSequence type) {
|
||||||
return loadedClasses.getUnchecked(type.toString());
|
return loadedClasses.getUnchecked(type.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,7 +34,10 @@ package org.jf.dexlib2.analysis;
|
|||||||
import com.google.common.base.Predicates;
|
import com.google.common.base.Predicates;
|
||||||
import com.google.common.base.Supplier;
|
import com.google.common.base.Supplier;
|
||||||
import com.google.common.base.Suppliers;
|
import com.google.common.base.Suppliers;
|
||||||
import com.google.common.collect.*;
|
import com.google.common.collect.FluentIterable;
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
import com.google.common.primitives.Ints;
|
import com.google.common.primitives.Ints;
|
||||||
import org.jf.dexlib2.AccessFlags;
|
import org.jf.dexlib2.AccessFlags;
|
||||||
import org.jf.dexlib2.analysis.util.TypeProtoUtils;
|
import org.jf.dexlib2.analysis.util.TypeProtoUtils;
|
||||||
@ -44,6 +47,7 @@ import org.jf.dexlib2.iface.Method;
|
|||||||
import org.jf.dexlib2.iface.reference.FieldReference;
|
import org.jf.dexlib2.iface.reference.FieldReference;
|
||||||
import org.jf.dexlib2.iface.reference.MethodReference;
|
import org.jf.dexlib2.iface.reference.MethodReference;
|
||||||
import org.jf.dexlib2.immutable.ImmutableMethod;
|
import org.jf.dexlib2.immutable.ImmutableMethod;
|
||||||
|
import org.jf.dexlib2.util.MethodUtil;
|
||||||
import org.jf.util.AlignmentUtils;
|
import org.jf.util.AlignmentUtils;
|
||||||
import org.jf.util.ExceptionWithContext;
|
import org.jf.util.ExceptionWithContext;
|
||||||
import org.jf.util.SparseArray;
|
import org.jf.util.SparseArray;
|
||||||
@ -346,7 +350,7 @@ public class ClassProto implements TypeProto {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
public MethodReference getMethodByVtableIndex(int vtableIndex) {
|
public Method getMethodByVtableIndex(int vtableIndex) {
|
||||||
List<Method> vtable = getVtable();
|
List<Method> vtable = getVtable();
|
||||||
if (vtableIndex < 0 || vtableIndex >= vtable.size()) {
|
if (vtableIndex < 0 || vtableIndex >= vtable.size()) {
|
||||||
return null;
|
return null;
|
||||||
@ -355,6 +359,20 @@ public class ClassProto implements TypeProto {
|
|||||||
return vtable.get(vtableIndex);
|
return vtable.get(vtableIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int findMethodIndexInVtable(@Nonnull MethodReference method) {
|
||||||
|
List<Method> vtable = getVtable();
|
||||||
|
for (int i=0; i<vtable.size(); i++) {
|
||||||
|
Method candidate = vtable.get(i);
|
||||||
|
if (MethodUtil.methodSignaturesMatch(candidate, method)) {
|
||||||
|
if (!classPath.shouldCheckPackagePrivateAccess() ||
|
||||||
|
AnalyzedMethodUtil.canAccess(this, candidate, true, false, false)) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
@Nonnull SparseArray<FieldReference> getInstanceFields() {
|
@Nonnull SparseArray<FieldReference> getInstanceFields() {
|
||||||
if (classPath.isArt) {
|
if (classPath.isArt) {
|
||||||
return artInstanceFieldsSupplier.get();
|
return artInstanceFieldsSupplier.get();
|
||||||
@ -790,8 +808,9 @@ public class ClassProto implements TypeProto {
|
|||||||
outer: for (Method virtualMethod: methods) {
|
outer: for (Method virtualMethod: methods) {
|
||||||
for (int i=0; i<vtable.size(); i++) {
|
for (int i=0; i<vtable.size(); i++) {
|
||||||
Method superMethod = vtable.get(i);
|
Method superMethod = vtable.get(i);
|
||||||
if (methodSignaturesMatch(superMethod, virtualMethod)) {
|
if (MethodUtil.methodSignaturesMatch(superMethod, virtualMethod)) {
|
||||||
if (!classPath.shouldCheckPackagePrivateAccess() || canAccess(superMethod)) {
|
if (!classPath.shouldCheckPackagePrivateAccess() ||
|
||||||
|
AnalyzedMethodUtil.canAccess(ClassProto.this, superMethod, true, false, false)) {
|
||||||
if (replaceExisting) {
|
if (replaceExisting) {
|
||||||
vtable.set(i, virtualMethod);
|
vtable.set(i, virtualMethod);
|
||||||
}
|
}
|
||||||
@ -803,37 +822,6 @@ public class ClassProto implements TypeProto {
|
|||||||
vtable.add(virtualMethod);
|
vtable.add(virtualMethod);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean methodSignaturesMatch(@Nonnull Method a, @Nonnull Method b) {
|
|
||||||
return (a.getName().equals(b.getName()) &&
|
|
||||||
a.getReturnType().equals(b.getReturnType()) &&
|
|
||||||
a.getParameters().equals(b.getParameters()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean canAccess(@Nonnull Method virtualMethod) {
|
|
||||||
if (!methodIsPackagePrivate(virtualMethod.getAccessFlags())) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
String otherPackage = getPackage(virtualMethod.getDefiningClass());
|
|
||||||
String ourPackage = getPackage(getClassDef().getType());
|
|
||||||
return otherPackage.equals(ourPackage);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nonnull
|
|
||||||
private String getPackage(@Nonnull String classType) {
|
|
||||||
int lastSlash = classType.lastIndexOf('/');
|
|
||||||
if (lastSlash < 0) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
return classType.substring(1, lastSlash);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean methodIsPackagePrivate(int accessFlags) {
|
|
||||||
return (accessFlags & (AccessFlags.PRIVATE.getValue() |
|
|
||||||
AccessFlags.PROTECTED.getValue() |
|
|
||||||
AccessFlags.PUBLIC.getValue())) == 0;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
private static byte getFieldType(@Nonnull FieldReference field) {
|
private static byte getFieldType(@Nonnull FieldReference field) {
|
||||||
|
@ -72,6 +72,8 @@ public class MethodAnalyzer {
|
|||||||
@Nonnull private final Method method;
|
@Nonnull private final Method method;
|
||||||
@Nonnull private final MethodImplementation methodImpl;
|
@Nonnull private final MethodImplementation methodImpl;
|
||||||
|
|
||||||
|
private final boolean normalizeVirtualMethods;
|
||||||
|
|
||||||
private final int paramRegisterCount;
|
private final int paramRegisterCount;
|
||||||
|
|
||||||
@Nonnull private final ClassPath classPath;
|
@Nonnull private final ClassPath classPath;
|
||||||
@ -93,9 +95,10 @@ public class MethodAnalyzer {
|
|||||||
private final AnalyzedInstruction startOfMethod;
|
private final AnalyzedInstruction startOfMethod;
|
||||||
|
|
||||||
public MethodAnalyzer(@Nonnull ClassPath classPath, @Nonnull Method method,
|
public MethodAnalyzer(@Nonnull ClassPath classPath, @Nonnull Method method,
|
||||||
@Nullable InlineMethodResolver inlineResolver) {
|
@Nullable InlineMethodResolver inlineResolver, boolean normalizeVirtualMethods) {
|
||||||
this.classPath = classPath;
|
this.classPath = classPath;
|
||||||
this.inlineResolver = inlineResolver;
|
this.inlineResolver = inlineResolver;
|
||||||
|
this.normalizeVirtualMethods = normalizeVirtualMethods;
|
||||||
|
|
||||||
this.method = method;
|
this.method = method;
|
||||||
|
|
||||||
@ -735,21 +738,32 @@ public class MethodAnalyzer {
|
|||||||
case SPUT_OBJECT:
|
case SPUT_OBJECT:
|
||||||
return true;
|
return true;
|
||||||
case INVOKE_VIRTUAL:
|
case INVOKE_VIRTUAL:
|
||||||
|
analyzeInvokeVirtual(analyzedInstruction, false);
|
||||||
|
return true;
|
||||||
case INVOKE_SUPER:
|
case INVOKE_SUPER:
|
||||||
|
analyzeInvokeVirtual(analyzedInstruction, false);
|
||||||
return true;
|
return true;
|
||||||
case INVOKE_DIRECT:
|
case INVOKE_DIRECT:
|
||||||
analyzeInvokeDirect(analyzedInstruction);
|
analyzeInvokeDirect(analyzedInstruction);
|
||||||
return true;
|
return true;
|
||||||
case INVOKE_STATIC:
|
case INVOKE_STATIC:
|
||||||
|
return true;
|
||||||
case INVOKE_INTERFACE:
|
case INVOKE_INTERFACE:
|
||||||
|
// TODO: normalize interfaces
|
||||||
|
return true;
|
||||||
case INVOKE_VIRTUAL_RANGE:
|
case INVOKE_VIRTUAL_RANGE:
|
||||||
|
analyzeInvokeVirtual(analyzedInstruction, true);
|
||||||
|
return true;
|
||||||
case INVOKE_SUPER_RANGE:
|
case INVOKE_SUPER_RANGE:
|
||||||
|
analyzeInvokeVirtual(analyzedInstruction, true);
|
||||||
return true;
|
return true;
|
||||||
case INVOKE_DIRECT_RANGE:
|
case INVOKE_DIRECT_RANGE:
|
||||||
analyzeInvokeDirectRange(analyzedInstruction);
|
analyzeInvokeDirectRange(analyzedInstruction);
|
||||||
return true;
|
return true;
|
||||||
case INVOKE_STATIC_RANGE:
|
case INVOKE_STATIC_RANGE:
|
||||||
|
return true;
|
||||||
case INVOKE_INTERFACE_RANGE:
|
case INVOKE_INTERFACE_RANGE:
|
||||||
|
// TODO: normalize interfaces
|
||||||
return true;
|
return true;
|
||||||
case NEG_INT:
|
case NEG_INT:
|
||||||
case NOT_INT:
|
case NOT_INT:
|
||||||
@ -1545,12 +1559,12 @@ public class MethodAnalyzer {
|
|||||||
|
|
||||||
ClassDef thisClass = classPath.getClassDef(method.getDefiningClass());
|
ClassDef thisClass = classPath.getClassDef(method.getDefiningClass());
|
||||||
|
|
||||||
if (!canAccessClass(thisClass, classPath.getClassDef(resolvedField.getDefiningClass()))) {
|
if (!TypeUtils.canAccessClass(thisClass.getType(), classPath.getClassDef(resolvedField.getDefiningClass()))) {
|
||||||
|
|
||||||
// the class is not accessible. So we start looking at objectRegisterTypeProto (which may be different
|
// the class is not accessible. So we start looking at objectRegisterTypeProto (which may be different
|
||||||
// than resolvedField.getDefiningClass()), and walk up the class hierarchy.
|
// than resolvedField.getDefiningClass()), and walk up the class hierarchy.
|
||||||
ClassDef fieldClass = classPath.getClassDef(objectRegisterTypeProto.getType());
|
ClassDef fieldClass = classPath.getClassDef(objectRegisterTypeProto.getType());
|
||||||
while (!canAccessClass(thisClass, fieldClass)) {
|
while (!TypeUtils.canAccessClass(thisClass.getType(), fieldClass)) {
|
||||||
String superclass = fieldClass.getSuperclass();
|
String superclass = fieldClass.getSuperclass();
|
||||||
if (superclass == null) {
|
if (superclass == null) {
|
||||||
throw new ExceptionWithContext("Couldn't find accessible class while resolving field %s",
|
throw new ExceptionWithContext("Couldn't find accessible class while resolving field %s",
|
||||||
@ -1585,6 +1599,75 @@ public class MethodAnalyzer {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean analyzeInvokeVirtual(@Nonnull AnalyzedInstruction analyzedInstruction, boolean isRange) {
|
||||||
|
MethodReference targetMethod;
|
||||||
|
|
||||||
|
if (!normalizeVirtualMethods) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isRange) {
|
||||||
|
Instruction3rc instruction = (Instruction3rc)analyzedInstruction.instruction;
|
||||||
|
targetMethod = (MethodReference)instruction.getReference();
|
||||||
|
} else {
|
||||||
|
Instruction35c instruction = (Instruction35c)analyzedInstruction.instruction;
|
||||||
|
targetMethod = (MethodReference)instruction.getReference();
|
||||||
|
}
|
||||||
|
|
||||||
|
TypeProto typeProto = classPath.getClass(targetMethod.getDefiningClass());
|
||||||
|
int methodIndex;
|
||||||
|
try {
|
||||||
|
methodIndex = typeProto.findMethodIndexInVtable(targetMethod);
|
||||||
|
} catch (UnresolvedClassException ex) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (methodIndex < 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Method replacementMethod = typeProto.getMethodByVtableIndex(methodIndex);
|
||||||
|
assert replacementMethod != null;
|
||||||
|
while (true) {
|
||||||
|
String superType = typeProto.getSuperclass();
|
||||||
|
if (superType == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
typeProto = classPath.getClass(superType);
|
||||||
|
Method resolvedMethod = typeProto.getMethodByVtableIndex(methodIndex);
|
||||||
|
if (resolvedMethod == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!resolvedMethod.equals(replacementMethod)) {
|
||||||
|
if (!AnalyzedMethodUtil.canAccess(typeProto, replacementMethod, true, true, true)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
replacementMethod = resolvedMethod;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (replacementMethod.equals(method)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Instruction deodexedInstruction;
|
||||||
|
if (isRange) {
|
||||||
|
Instruction3rc instruction = (Instruction3rc)analyzedInstruction.instruction;
|
||||||
|
deodexedInstruction = new ImmutableInstruction3rc(instruction.getOpcode(), instruction.getStartRegister(),
|
||||||
|
instruction.getRegisterCount(), replacementMethod);
|
||||||
|
} else {
|
||||||
|
Instruction35c instruction = (Instruction35c)analyzedInstruction.instruction;
|
||||||
|
deodexedInstruction = new ImmutableInstruction35c(instruction.getOpcode(), instruction.getRegisterCount(),
|
||||||
|
instruction.getRegisterC(), instruction.getRegisterD(), instruction.getRegisterE(),
|
||||||
|
instruction.getRegisterF(), instruction.getRegisterG(), replacementMethod);
|
||||||
|
}
|
||||||
|
|
||||||
|
analyzedInstruction.setDeodexedInstruction(deodexedInstruction);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private boolean analyzeInvokeVirtualQuick(@Nonnull AnalyzedInstruction analyzedInstruction, boolean isSuper,
|
private boolean analyzeInvokeVirtualQuick(@Nonnull AnalyzedInstruction analyzedInstruction, boolean isSuper,
|
||||||
boolean isRange) {
|
boolean isRange) {
|
||||||
int methodIndex;
|
int methodIndex;
|
||||||
@ -1637,12 +1720,13 @@ public class MethodAnalyzer {
|
|||||||
// no need to check class access for invoke-super. A class can obviously access its superclass.
|
// no need to check class access for invoke-super. A class can obviously access its superclass.
|
||||||
ClassDef thisClass = classPath.getClassDef(method.getDefiningClass());
|
ClassDef thisClass = classPath.getClassDef(method.getDefiningClass());
|
||||||
|
|
||||||
if (!isSuper && !canAccessClass(thisClass, classPath.getClassDef(resolvedMethod.getDefiningClass()))) {
|
if (!isSuper && !TypeUtils.canAccessClass(
|
||||||
|
thisClass.getType(), classPath.getClassDef(resolvedMethod.getDefiningClass()))) {
|
||||||
|
|
||||||
// the class is not accessible. So we start looking at objectRegisterTypeProto (which may be different
|
// the class is not accessible. So we start looking at objectRegisterTypeProto (which may be different
|
||||||
// than resolvedMethod.getDefiningClass()), and walk up the class hierarchy.
|
// than resolvedMethod.getDefiningClass()), and walk up the class hierarchy.
|
||||||
ClassDef methodClass = classPath.getClassDef(objectRegisterTypeProto.getType());
|
ClassDef methodClass = classPath.getClassDef(objectRegisterTypeProto.getType());
|
||||||
while (!canAccessClass(thisClass, methodClass)) {
|
while (!TypeUtils.canAccessClass(thisClass.getType(), methodClass)) {
|
||||||
String superclass = methodClass.getSuperclass();
|
String superclass = methodClass.getSuperclass();
|
||||||
if (superclass == null) {
|
if (superclass == null) {
|
||||||
throw new ExceptionWithContext("Couldn't find accessible class while resolving method %s",
|
throw new ExceptionWithContext("Couldn't find accessible class while resolving method %s",
|
||||||
@ -1698,24 +1782,6 @@ public class MethodAnalyzer {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean canAccessClass(@Nonnull ClassDef accessorClassDef, @Nonnull ClassDef accesseeClassDef) {
|
|
||||||
if (AccessFlags.PUBLIC.isSet(accesseeClassDef.getAccessFlags())) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Classes can only be public or package private. Any private or protected inner classes are actually
|
|
||||||
// package private.
|
|
||||||
return getPackage(accesseeClassDef.getType()).equals(getPackage(accessorClassDef.getType()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String getPackage(String className) {
|
|
||||||
int lastSlash = className.lastIndexOf('/');
|
|
||||||
if (lastSlash < 0) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
return className.substring(1, lastSlash);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean analyzePutGetVolatile(@Nonnull AnalyzedInstruction analyzedInstruction) {
|
private boolean analyzePutGetVolatile(@Nonnull AnalyzedInstruction analyzedInstruction) {
|
||||||
return analyzePutGetVolatile(analyzedInstruction, true);
|
return analyzePutGetVolatile(analyzedInstruction, true);
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,7 @@
|
|||||||
|
|
||||||
package org.jf.dexlib2.analysis;
|
package org.jf.dexlib2.analysis;
|
||||||
|
|
||||||
|
import org.jf.dexlib2.iface.Method;
|
||||||
import org.jf.dexlib2.iface.reference.FieldReference;
|
import org.jf.dexlib2.iface.reference.FieldReference;
|
||||||
import org.jf.dexlib2.iface.reference.MethodReference;
|
import org.jf.dexlib2.iface.reference.MethodReference;
|
||||||
import org.jf.util.ExceptionWithContext;
|
import org.jf.util.ExceptionWithContext;
|
||||||
@ -65,7 +66,11 @@ public class PrimitiveProto implements TypeProto {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
public MethodReference getMethodByVtableIndex(int vtableIndex) {
|
public Method getMethodByVtableIndex(int vtableIndex) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override public int findMethodIndexInVtable(@Nonnull MethodReference method) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,7 @@
|
|||||||
|
|
||||||
package org.jf.dexlib2.analysis;
|
package org.jf.dexlib2.analysis;
|
||||||
|
|
||||||
|
import org.jf.dexlib2.iface.Method;
|
||||||
import org.jf.dexlib2.iface.reference.FieldReference;
|
import org.jf.dexlib2.iface.reference.FieldReference;
|
||||||
import org.jf.dexlib2.iface.reference.MethodReference;
|
import org.jf.dexlib2.iface.reference.MethodReference;
|
||||||
|
|
||||||
@ -45,5 +46,6 @@ public interface TypeProto {
|
|||||||
@Nullable String getSuperclass();
|
@Nullable String getSuperclass();
|
||||||
@Nonnull TypeProto getCommonSuperclass(@Nonnull TypeProto other);
|
@Nonnull TypeProto getCommonSuperclass(@Nonnull TypeProto other);
|
||||||
@Nullable FieldReference getFieldByOffset(int fieldOffset);
|
@Nullable FieldReference getFieldByOffset(int fieldOffset);
|
||||||
@Nullable MethodReference getMethodByVtableIndex(int vtableIndex);
|
@Nullable Method getMethodByVtableIndex(int vtableIndex);
|
||||||
|
int findMethodIndexInVtable(@Nonnull MethodReference method);
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,7 @@
|
|||||||
|
|
||||||
package org.jf.dexlib2.analysis;
|
package org.jf.dexlib2.analysis;
|
||||||
|
|
||||||
|
import org.jf.dexlib2.iface.Method;
|
||||||
import org.jf.dexlib2.iface.reference.FieldReference;
|
import org.jf.dexlib2.iface.reference.FieldReference;
|
||||||
import org.jf.dexlib2.iface.reference.MethodReference;
|
import org.jf.dexlib2.iface.reference.MethodReference;
|
||||||
|
|
||||||
@ -75,7 +76,11 @@ public class UnknownClassProto implements TypeProto {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
public MethodReference getMethodByVtableIndex(int vtableIndex) {
|
public Method getMethodByVtableIndex(int vtableIndex) {
|
||||||
return classPath.getClass("Ljava/lang/Object;").getMethodByVtableIndex(vtableIndex);
|
return classPath.getClass("Ljava/lang/Object;").getMethodByVtableIndex(vtableIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override public int findMethodIndexInVtable(@Nonnull MethodReference method) {
|
||||||
|
return classPath.getClass("Ljava/lang/Object;").findMethodIndexInVtable(method);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -94,4 +94,16 @@ public class TypeProtoUtils {
|
|||||||
return type.getClassPath().getUnknownClass();
|
return type.getClassPath().getUnknownClass();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean extendsFrom(@Nonnull TypeProto candidate, @Nonnull String possibleSuper) {
|
||||||
|
if (candidate.getType().equals(possibleSuper)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
for (TypeProto superProto: getSuperclassChain(candidate)) {
|
||||||
|
if (superProto.getType().equals(possibleSuper)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,7 @@ import com.google.common.base.Predicate;
|
|||||||
import org.jf.dexlib2.AccessFlags;
|
import org.jf.dexlib2.AccessFlags;
|
||||||
import org.jf.dexlib2.iface.Method;
|
import org.jf.dexlib2.iface.Method;
|
||||||
import org.jf.dexlib2.iface.reference.MethodReference;
|
import org.jf.dexlib2.iface.reference.MethodReference;
|
||||||
|
import org.jf.util.CharSequenceUtils;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
@ -68,6 +69,12 @@ public final class MethodUtil {
|
|||||||
return methodReference.getName().equals("<init>");
|
return methodReference.getName().equals("<init>");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isPackagePrivate(@Nonnull Method method) {
|
||||||
|
return (method.getAccessFlags() & (AccessFlags.PRIVATE.getValue() |
|
||||||
|
AccessFlags.PROTECTED.getValue() |
|
||||||
|
AccessFlags.PUBLIC.getValue())) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
public static int getParameterRegisterCount(@Nonnull Method method) {
|
public static int getParameterRegisterCount(@Nonnull Method method) {
|
||||||
return getParameterRegisterCount(method, MethodUtil.isStatic(method));
|
return getParameterRegisterCount(method, MethodUtil.isStatic(method));
|
||||||
}
|
}
|
||||||
@ -109,5 +116,11 @@ public final class MethodUtil {
|
|||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean methodSignaturesMatch(@Nonnull MethodReference a, @Nonnull MethodReference b) {
|
||||||
|
return (a.getName().equals(b.getName()) &&
|
||||||
|
a.getReturnType().equals(b.getReturnType()) &&
|
||||||
|
CharSequenceUtils.listEquals(a.getParameterTypes(), b.getParameterTypes()));
|
||||||
|
}
|
||||||
|
|
||||||
private MethodUtil() {}
|
private MethodUtil() {}
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,8 @@
|
|||||||
|
|
||||||
package org.jf.dexlib2.util;
|
package org.jf.dexlib2.util;
|
||||||
|
|
||||||
|
import org.jf.dexlib2.AccessFlags;
|
||||||
|
import org.jf.dexlib2.iface.ClassDef;
|
||||||
import org.jf.dexlib2.iface.reference.TypeReference;
|
import org.jf.dexlib2.iface.reference.TypeReference;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
@ -49,5 +51,24 @@ public final class TypeUtils {
|
|||||||
return type.length() == 1;
|
return type.length() == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
public static String getPackage(@Nonnull String type) {
|
||||||
|
int lastSlash = type.lastIndexOf('/');
|
||||||
|
if (lastSlash < 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return type.substring(1, lastSlash);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean canAccessClass(@Nonnull String accessorType, @Nonnull ClassDef accesseeClassDef) {
|
||||||
|
if (AccessFlags.PUBLIC.isSet(accesseeClassDef.getAccessFlags())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Classes can only be public or package private. Any private or protected inner classes are actually
|
||||||
|
// package private.
|
||||||
|
return getPackage(accesseeClassDef.getType()).equals(getPackage(accessorType));
|
||||||
|
}
|
||||||
|
|
||||||
private TypeUtils() {}
|
private TypeUtils() {}
|
||||||
}
|
}
|
||||||
|
@ -71,7 +71,7 @@ public class CustomMethodInlineTableTest {
|
|||||||
ClassPath classPath = ClassPath.fromClassPath(ImmutableList.<String>of(), ImmutableList.<String>of(), dexFile,
|
ClassPath classPath = ClassPath.fromClassPath(ImmutableList.<String>of(), ImmutableList.<String>of(), dexFile,
|
||||||
15, false);
|
15, false);
|
||||||
InlineMethodResolver inlineMethodResolver = new CustomInlineMethodResolver(classPath, "Lblah;->blah()V");
|
InlineMethodResolver inlineMethodResolver = new CustomInlineMethodResolver(classPath, "Lblah;->blah()V");
|
||||||
MethodAnalyzer methodAnalyzer = new MethodAnalyzer(classPath, method, inlineMethodResolver);
|
MethodAnalyzer methodAnalyzer = new MethodAnalyzer(classPath, method, inlineMethodResolver, false);
|
||||||
|
|
||||||
Instruction deodexedInstruction = methodAnalyzer.getInstructions().get(0);
|
Instruction deodexedInstruction = methodAnalyzer.getInstructions().get(0);
|
||||||
Assert.assertEquals(Opcode.INVOKE_VIRTUAL, deodexedInstruction.getOpcode());
|
Assert.assertEquals(Opcode.INVOKE_VIRTUAL, deodexedInstruction.getOpcode());
|
||||||
@ -98,7 +98,7 @@ public class CustomMethodInlineTableTest {
|
|||||||
ClassPath classPath = ClassPath.fromClassPath(ImmutableList.<String>of(), ImmutableList.<String>of(), dexFile,
|
ClassPath classPath = ClassPath.fromClassPath(ImmutableList.<String>of(), ImmutableList.<String>of(), dexFile,
|
||||||
15, false);
|
15, false);
|
||||||
InlineMethodResolver inlineMethodResolver = new CustomInlineMethodResolver(classPath, "Lblah;->blah()V");
|
InlineMethodResolver inlineMethodResolver = new CustomInlineMethodResolver(classPath, "Lblah;->blah()V");
|
||||||
MethodAnalyzer methodAnalyzer = new MethodAnalyzer(classPath, method, inlineMethodResolver);
|
MethodAnalyzer methodAnalyzer = new MethodAnalyzer(classPath, method, inlineMethodResolver, false);
|
||||||
|
|
||||||
Instruction deodexedInstruction = methodAnalyzer.getInstructions().get(0);
|
Instruction deodexedInstruction = methodAnalyzer.getInstructions().get(0);
|
||||||
Assert.assertEquals(Opcode.INVOKE_STATIC, deodexedInstruction.getOpcode());
|
Assert.assertEquals(Opcode.INVOKE_STATIC, deodexedInstruction.getOpcode());
|
||||||
@ -125,7 +125,7 @@ public class CustomMethodInlineTableTest {
|
|||||||
ClassPath classPath = ClassPath.fromClassPath(ImmutableList.<String>of(), ImmutableList.<String>of(), dexFile,
|
ClassPath classPath = ClassPath.fromClassPath(ImmutableList.<String>of(), ImmutableList.<String>of(), dexFile,
|
||||||
15, false);
|
15, false);
|
||||||
InlineMethodResolver inlineMethodResolver = new CustomInlineMethodResolver(classPath, "Lblah;->blah()V");
|
InlineMethodResolver inlineMethodResolver = new CustomInlineMethodResolver(classPath, "Lblah;->blah()V");
|
||||||
MethodAnalyzer methodAnalyzer = new MethodAnalyzer(classPath, method, inlineMethodResolver);
|
MethodAnalyzer methodAnalyzer = new MethodAnalyzer(classPath, method, inlineMethodResolver, false);
|
||||||
|
|
||||||
Instruction deodexedInstruction = methodAnalyzer.getInstructions().get(0);
|
Instruction deodexedInstruction = methodAnalyzer.getInstructions().get(0);
|
||||||
Assert.assertEquals(Opcode.INVOKE_DIRECT, deodexedInstruction.getOpcode());
|
Assert.assertEquals(Opcode.INVOKE_DIRECT, deodexedInstruction.getOpcode());
|
||||||
|
Loading…
x
Reference in New Issue
Block a user