mirror of
https://github.com/revanced/smali.git
synced 2025-05-08 10:24:31 +02:00
Make baksmali thread safe, and add -j option
This commit is contained in:
parent
4b171afedb
commit
7e25c35df7
@ -30,6 +30,7 @@ package org.jf.baksmali;
|
|||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
import org.jf.baksmali.Adaptors.ClassDefinition;
|
import org.jf.baksmali.Adaptors.ClassDefinition;
|
||||||
import org.jf.dexlib2.analysis.ClassPath;
|
import org.jf.dexlib2.analysis.ClassPath;
|
||||||
import org.jf.dexlib2.iface.ClassDef;
|
import org.jf.dexlib2.iface.ClassDef;
|
||||||
@ -40,10 +41,11 @@ import org.jf.util.IndentingWriter;
|
|||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
|
||||||
public class baksmali {
|
public class baksmali {
|
||||||
|
|
||||||
public static void disassembleDexFile(DexFile dexFile, baksmaliOptions options) {
|
public static void disassembleDexFile(DexFile dexFile, final baksmaliOptions options) {
|
||||||
if (options.registerInfo != 0 || options.deodex) {
|
if (options.registerInfo != 0 || options.deodex) {
|
||||||
try {
|
try {
|
||||||
Iterable<String> extraClassPathEntries;
|
Iterable<String> extraClassPathEntries;
|
||||||
@ -87,11 +89,34 @@ public class baksmali {
|
|||||||
options.syntheticAccessorResolver = new SyntheticAccessorResolver(classDefs);
|
options.syntheticAccessorResolver = new SyntheticAccessorResolver(classDefs);
|
||||||
}
|
}
|
||||||
|
|
||||||
ClassFileNameHandler fileNameHandler = new ClassFileNameHandler(outputDirectoryFile, ".smali");
|
final ClassFileNameHandler fileNameHandler = new ClassFileNameHandler(outputDirectoryFile, ".smali");
|
||||||
|
|
||||||
for (ClassDef classDef: classDefs) {
|
ExecutorService executor = Executors.newFixedThreadPool(options.jobs);
|
||||||
disassembleClass(classDef, fileNameHandler, options);
|
List<Future<Void>> tasks = Lists.newArrayList();
|
||||||
|
|
||||||
|
for (final ClassDef classDef: classDefs) {
|
||||||
|
tasks.add(executor.submit(new Callable<Void>() {
|
||||||
|
@Override public Void call() throws Exception {
|
||||||
|
disassembleClass(classDef, fileNameHandler, options);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (Future<Void> task: tasks) {
|
||||||
|
while(true) {
|
||||||
|
try {
|
||||||
|
task.get();
|
||||||
|
} catch (InterruptedException ex) {
|
||||||
|
continue;
|
||||||
|
} catch (ExecutionException ex) {
|
||||||
|
throw new RuntimeException(ex);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
executor.shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void disassembleClass(ClassDef classDef, ClassFileNameHandler fileNameHandler,
|
private static void disassembleClass(ClassDef classDef, ClassFileNameHandler fileNameHandler,
|
||||||
@ -124,8 +149,11 @@ public class baksmali {
|
|||||||
File smaliParent = smaliFile.getParentFile();
|
File smaliParent = smaliFile.getParentFile();
|
||||||
if (!smaliParent.exists()) {
|
if (!smaliParent.exists()) {
|
||||||
if (!smaliParent.mkdirs()) {
|
if (!smaliParent.mkdirs()) {
|
||||||
System.err.println("Unable to create directory " + smaliParent.toString() + " - skipping class");
|
// check again, it's likely it was created in a different thread
|
||||||
return;
|
if (!smaliParent.exists()) {
|
||||||
|
System.err.println("Unable to create directory " + smaliParent.toString() + " - skipping class");
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,6 +68,7 @@ public class baksmaliOptions {
|
|||||||
public InlineMethodResolver inlineResolver = null;
|
public InlineMethodResolver inlineResolver = null;
|
||||||
public int registerInfo = 0;
|
public int registerInfo = 0;
|
||||||
public ClassPath classPath = null;
|
public ClassPath classPath = null;
|
||||||
|
public int jobs = -1;
|
||||||
|
|
||||||
public SyntheticAccessorResolver syntheticAccessorResolver = null;
|
public SyntheticAccessorResolver syntheticAccessorResolver = null;
|
||||||
|
|
||||||
|
@ -193,6 +193,9 @@ public class main {
|
|||||||
case 'a':
|
case 'a':
|
||||||
options.apiLevel = Integer.parseInt(commandLine.getOptionValue("a"));
|
options.apiLevel = Integer.parseInt(commandLine.getOptionValue("a"));
|
||||||
break;
|
break;
|
||||||
|
case 'j':
|
||||||
|
options.jobs = Integer.parseInt(commandLine.getOptionValue("j"));
|
||||||
|
break;
|
||||||
case 'N':
|
case 'N':
|
||||||
disassemble = false;
|
disassemble = false;
|
||||||
break;
|
break;
|
||||||
@ -219,6 +222,13 @@ public class main {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options.jobs <= 0) {
|
||||||
|
options.jobs = Runtime.getRuntime().availableProcessors();
|
||||||
|
if (options.jobs > 6) {
|
||||||
|
options.jobs = 6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
String inputDexFileName = remainingArgs[0];
|
String inputDexFileName = remainingArgs[0];
|
||||||
|
|
||||||
File dexFileFile = new File(inputDexFileName);
|
File dexFileFile = new File(inputDexFileName);
|
||||||
@ -375,6 +385,13 @@ public class main {
|
|||||||
.withArgName("API_LEVEL")
|
.withArgName("API_LEVEL")
|
||||||
.create("a");
|
.create("a");
|
||||||
|
|
||||||
|
Option jobsOption = OptionBuilder.withLongOpt("jobs")
|
||||||
|
.withDescription("The number of threads to use. Defaults to the number of cores available, up to a " +
|
||||||
|
"maximum of 6")
|
||||||
|
.hasArg()
|
||||||
|
.withArgName("NUM_THREADS")
|
||||||
|
.create("j");
|
||||||
|
|
||||||
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")
|
||||||
@ -418,6 +435,7 @@ public class main {
|
|||||||
basicOptions.addOption(codeOffsetOption);
|
basicOptions.addOption(codeOffsetOption);
|
||||||
basicOptions.addOption(noAccessorCommentsOption);
|
basicOptions.addOption(noAccessorCommentsOption);
|
||||||
basicOptions.addOption(apiLevelOption);
|
basicOptions.addOption(apiLevelOption);
|
||||||
|
basicOptions.addOption(jobsOption);
|
||||||
|
|
||||||
debugOptions.addOption(dumpOption);
|
debugOptions.addOption(dumpOption);
|
||||||
debugOptions.addOption(ignoreErrorsOption);
|
debugOptions.addOption(ignoreErrorsOption);
|
||||||
|
@ -31,6 +31,9 @@
|
|||||||
|
|
||||||
package org.jf.dexlib2.analysis;
|
package org.jf.dexlib2.analysis;
|
||||||
|
|
||||||
|
import com.google.common.cache.CacheBuilder;
|
||||||
|
import com.google.common.cache.CacheLoader;
|
||||||
|
import com.google.common.cache.LoadingCache;
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
@ -53,10 +56,8 @@ import java.util.regex.Pattern;
|
|||||||
|
|
||||||
public class ClassPath {
|
public class ClassPath {
|
||||||
@Nonnull private final TypeProto unknownClass;
|
@Nonnull private final TypeProto unknownClass;
|
||||||
@Nonnull private DexFile[] dexFiles;
|
|
||||||
@Nonnull private HashMap<String, TypeProto> loadedClasses = Maps.newHashMap();
|
|
||||||
@Nonnull private HashMap<String, ClassDef> availableClasses = Maps.newHashMap();
|
@Nonnull private HashMap<String, ClassDef> availableClasses = Maps.newHashMap();
|
||||||
@Nonnull private int api;
|
private int api;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new ClassPath instance that can load classes from the given dex files
|
* Creates a new ClassPath instance that can load classes from the given dex files
|
||||||
@ -78,6 +79,7 @@ public class ClassPath {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private ClassPath(@Nonnull DexFile[] classPath, boolean copyArray, int api) {
|
private ClassPath(@Nonnull DexFile[] classPath, boolean copyArray, int api) {
|
||||||
|
DexFile[] dexFiles;
|
||||||
if (copyArray) {
|
if (copyArray) {
|
||||||
dexFiles = new DexFile[classPath.length+1];
|
dexFiles = new DexFile[classPath.length+1];
|
||||||
System.arraycopy(classPath, 0, dexFiles, 0, classPath.length);
|
System.arraycopy(classPath, 0, dexFiles, 0, classPath.length);
|
||||||
@ -128,23 +130,21 @@ public class ClassPath {
|
|||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
public TypeProto getClass(CharSequence type) {
|
public TypeProto getClass(CharSequence type) {
|
||||||
String typeString = type.toString();
|
return loadedClasses.getUnchecked(type.toString());
|
||||||
TypeProto typeProto = loadedClasses.get(typeString);
|
|
||||||
if (typeProto != null) {
|
|
||||||
return typeProto;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type.charAt(0) == '[') {
|
|
||||||
typeProto = new ArrayProto(this, typeString);
|
|
||||||
} else {
|
|
||||||
typeProto = new ClassProto(this, typeString);
|
|
||||||
}
|
|
||||||
// All primitive types are preloaded into loadedClasses, so we don't need to check for that here
|
|
||||||
|
|
||||||
loadedClasses.put(typeString, typeProto);
|
|
||||||
return typeProto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final CacheLoader<String, TypeProto> classLoader = new CacheLoader<String, TypeProto>() {
|
||||||
|
@Override public TypeProto load(String type) throws Exception {
|
||||||
|
if (type.charAt(0) == '[') {
|
||||||
|
return new ArrayProto(ClassPath.this, type);
|
||||||
|
} else {
|
||||||
|
return new ClassProto(ClassPath.this, type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Nonnull private LoadingCache<String, TypeProto> loadedClasses = CacheBuilder.newBuilder().build(classLoader);
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
public ClassDef getClassDef(String type) {
|
public ClassDef getClassDef(String type) {
|
||||||
ClassDef ret = availableClasses.get(type);
|
ClassDef ret = availableClasses.get(type);
|
||||||
|
@ -32,7 +32,12 @@
|
|||||||
package org.jf.dexlib2.analysis;
|
package org.jf.dexlib2.analysis;
|
||||||
|
|
||||||
import com.google.common.base.Predicates;
|
import com.google.common.base.Predicates;
|
||||||
import com.google.common.collect.*;
|
import com.google.common.base.Supplier;
|
||||||
|
import com.google.common.base.Suppliers;
|
||||||
|
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 org.jf.dexlib2.AccessFlags;
|
import org.jf.dexlib2.AccessFlags;
|
||||||
import org.jf.dexlib2.analysis.util.TypeProtoUtils;
|
import org.jf.dexlib2.analysis.util.TypeProtoUtils;
|
||||||
import org.jf.dexlib2.iface.ClassDef;
|
import org.jf.dexlib2.iface.ClassDef;
|
||||||
@ -57,10 +62,7 @@ import java.util.List;
|
|||||||
public class ClassProto implements TypeProto {
|
public class ClassProto implements TypeProto {
|
||||||
@Nonnull protected final ClassPath classPath;
|
@Nonnull protected final ClassPath classPath;
|
||||||
@Nonnull protected final String type;
|
@Nonnull protected final String type;
|
||||||
@Nullable protected ClassDef classDef;
|
|
||||||
@Nullable protected LinkedHashMap<String, ClassDef> interfaces;
|
|
||||||
@Nullable protected List<Method> vtable;
|
|
||||||
@Nullable protected SparseArray<FieldReference> instanceFields;
|
|
||||||
protected boolean vtableFullyResolved = true;
|
protected boolean vtableFullyResolved = true;
|
||||||
protected boolean interfacesFullyResolved = true;
|
protected boolean interfacesFullyResolved = true;
|
||||||
|
|
||||||
@ -78,27 +80,15 @@ public class ClassProto implements TypeProto {
|
|||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
public ClassDef getClassDef() {
|
public ClassDef getClassDef() {
|
||||||
if (classDef == null) {
|
return classDefSupplier.get();
|
||||||
classDef = classPath.getClassDef(type);
|
|
||||||
}
|
|
||||||
return classDef;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
|
||||||
List<Method> getVtable() {
|
|
||||||
if (vtable == null) {
|
|
||||||
loadVtable();
|
|
||||||
}
|
|
||||||
return vtable;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull private final Supplier<ClassDef> classDefSupplier = Suppliers.memoize(new Supplier<ClassDef>() {
|
||||||
SparseArray<FieldReference> getInstanceFields() {
|
@Override public ClassDef get() {
|
||||||
if (instanceFields == null) {
|
return classPath.getClassDef(type);
|
||||||
instanceFields = loadFields();
|
|
||||||
}
|
}
|
||||||
return instanceFields;
|
});
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if this class is an interface.
|
* Returns true if this class is an interface.
|
||||||
@ -128,81 +118,88 @@ public class ClassProto implements TypeProto {
|
|||||||
*/
|
*/
|
||||||
@Nonnull
|
@Nonnull
|
||||||
protected LinkedHashMap<String, ClassDef> getInterfaces() {
|
protected LinkedHashMap<String, ClassDef> getInterfaces() {
|
||||||
if (interfaces != null) {
|
return interfacesSupplier.get();
|
||||||
return interfaces;
|
|
||||||
}
|
|
||||||
|
|
||||||
interfaces = Maps.newLinkedHashMap();
|
|
||||||
|
|
||||||
try {
|
|
||||||
for (String interfaceType: getClassDef().getInterfaces()) {
|
|
||||||
if (!interfaces.containsKey(interfaceType)) {
|
|
||||||
ClassDef interfaceDef;
|
|
||||||
try {
|
|
||||||
interfaceDef = classPath.getClassDef(interfaceType);
|
|
||||||
interfaces.put(interfaceType, interfaceDef);
|
|
||||||
} catch (UnresolvedClassException ex) {
|
|
||||||
interfaces.put(interfaceType, null);
|
|
||||||
interfacesFullyResolved = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ClassProto interfaceProto = (ClassProto) classPath.getClass(interfaceType);
|
|
||||||
for (String superInterface: interfaceProto.getInterfaces().keySet()) {
|
|
||||||
if (!interfaces.containsKey(superInterface)) {
|
|
||||||
interfaces.put(superInterface, interfaceProto.getInterfaces().get(superInterface));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!interfaceProto.interfacesFullyResolved) {
|
|
||||||
interfacesFullyResolved = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (UnresolvedClassException ex) {
|
|
||||||
interfacesFullyResolved = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// now add self and super class interfaces, required for common super class lookup
|
|
||||||
// we don't really need ClassDef's for that, so let's just use null
|
|
||||||
|
|
||||||
if (isInterface() && !interfaces.containsKey(getType())) {
|
|
||||||
interfaces.put(getType(), null);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
String superclass = getSuperclass();
|
|
||||||
if (superclass != null) {
|
|
||||||
ClassProto superclassProto = (ClassProto) classPath.getClass(superclass);
|
|
||||||
for (String superclassInterface: superclassProto.getInterfaces().keySet()) {
|
|
||||||
if (!interfaces.containsKey(superclassInterface)) {
|
|
||||||
interfaces.put(superclassInterface, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!superclassProto.interfacesFullyResolved) {
|
|
||||||
interfacesFullyResolved = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (UnresolvedClassException ex) {
|
|
||||||
interfacesFullyResolved = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return interfaces;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
private final Supplier<LinkedHashMap<String, ClassDef>> interfacesSupplier =
|
||||||
|
Suppliers.memoize(new Supplier<LinkedHashMap<String, ClassDef>>() {
|
||||||
|
@Override public LinkedHashMap<String, ClassDef> get() {
|
||||||
|
LinkedHashMap<String, ClassDef> interfaces = Maps.newLinkedHashMap();
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (String interfaceType: getClassDef().getInterfaces()) {
|
||||||
|
if (!interfaces.containsKey(interfaceType)) {
|
||||||
|
ClassDef interfaceDef;
|
||||||
|
try {
|
||||||
|
interfaceDef = classPath.getClassDef(interfaceType);
|
||||||
|
interfaces.put(interfaceType, interfaceDef);
|
||||||
|
} catch (UnresolvedClassException ex) {
|
||||||
|
interfaces.put(interfaceType, null);
|
||||||
|
interfacesFullyResolved = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ClassProto interfaceProto = (ClassProto) classPath.getClass(interfaceType);
|
||||||
|
for (String superInterface: interfaceProto.getInterfaces().keySet()) {
|
||||||
|
if (!interfaces.containsKey(superInterface)) {
|
||||||
|
interfaces.put(superInterface, interfaceProto.getInterfaces().get(superInterface));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!interfaceProto.interfacesFullyResolved) {
|
||||||
|
interfacesFullyResolved = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (UnresolvedClassException ex) {
|
||||||
|
interfacesFullyResolved = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// now add self and super class interfaces, required for common super class lookup
|
||||||
|
// we don't really need ClassDef's for that, so let's just use null
|
||||||
|
|
||||||
|
if (isInterface() && !interfaces.containsKey(getType())) {
|
||||||
|
interfaces.put(getType(), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
String superclass = getSuperclass();
|
||||||
|
if (superclass != null) {
|
||||||
|
ClassProto superclassProto = (ClassProto) classPath.getClass(superclass);
|
||||||
|
for (String superclassInterface: superclassProto.getInterfaces().keySet()) {
|
||||||
|
if (!interfaces.containsKey(superclassInterface)) {
|
||||||
|
interfaces.put(superclassInterface, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!superclassProto.interfacesFullyResolved) {
|
||||||
|
interfacesFullyResolved = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (UnresolvedClassException ex) {
|
||||||
|
interfacesFullyResolved = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return interfaces;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the interfaces directly implemented by this class, or the interfaces they transitively implement.
|
* Gets the interfaces directly implemented by this class, or the interfaces they transitively implement.
|
||||||
*
|
*
|
||||||
* This does not include any interfaces that are only implemented by a superclass
|
* This does not include any interfaces that are only implemented by a superclass
|
||||||
*
|
*
|
||||||
* @return An iterables of ClassDefs representing the directly or transitively implemented interfaces
|
* @return An iterables of ClassDefs representing the directly or transitively implemented interfaces
|
||||||
* @throws UnresolvedClassException if any interface could not be resolved
|
* @throws UnresolvedClassException if interfaces could not be fully resolved
|
||||||
*/
|
*/
|
||||||
@Nonnull
|
@Nonnull
|
||||||
protected Iterable<ClassDef> getDirectInterfaces() {
|
protected Iterable<ClassDef> getDirectInterfaces() {
|
||||||
|
Iterable<ClassDef> directInterfaces =
|
||||||
|
FluentIterable.from(getInterfaces().values()).filter(Predicates.notNull());
|
||||||
|
|
||||||
if (!interfacesFullyResolved) {
|
if (!interfacesFullyResolved) {
|
||||||
throw new UnresolvedClassException("Interfaces for class %s not fully resolved", getType());
|
throw new UnresolvedClassException("Interfaces for class %s not fully resolved", getType());
|
||||||
}
|
}
|
||||||
|
|
||||||
return FluentIterable.from(getInterfaces().values()).filter(Predicates.notNull());
|
return directInterfaces;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -213,6 +210,8 @@ public class ClassProto implements TypeProto {
|
|||||||
*
|
*
|
||||||
* @param iface The interface to check for
|
* @param iface The interface to check for
|
||||||
* @return true if this class implements the given interface, otherwise false
|
* @return true if this class implements the given interface, otherwise false
|
||||||
|
* @throws UnresolvedClassException if the interfaces for this class could not be fully resolved, and the interface
|
||||||
|
* is not one of the interfaces that were successfully resolved
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean implementsInterface(@Nonnull String iface) {
|
public boolean implementsInterface(@Nonnull String iface) {
|
||||||
@ -354,184 +353,193 @@ public class ClassProto implements TypeProto {
|
|||||||
return vtable.get(vtableIndex);
|
return vtable.get(vtableIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull SparseArray<FieldReference> getInstanceFields() {
|
||||||
private SparseArray<FieldReference> loadFields() {
|
return instanceFieldsSupplier.get();
|
||||||
//This is a bit of an "involved" operation. We need to follow the same algorithm that dalvik uses to
|
|
||||||
//arrange fields, so that we end up with the same field offsets (which is needed for deodexing).
|
|
||||||
//See mydroid/dalvik/vm/oo/Class.c - computeFieldOffsets()
|
|
||||||
|
|
||||||
final byte REFERENCE = 0;
|
|
||||||
final byte WIDE = 1;
|
|
||||||
final byte OTHER = 2;
|
|
||||||
|
|
||||||
ArrayList<Field> fields = getSortedInstanceFields(getClassDef());
|
|
||||||
final int fieldCount = fields.size();
|
|
||||||
//the "type" for each field in fields. 0=reference,1=wide,2=other
|
|
||||||
byte[] fieldTypes = new byte[fields.size()];
|
|
||||||
for (int i=0; i<fieldCount; i++) {
|
|
||||||
fieldTypes[i] = getFieldType(fields.get(i));
|
|
||||||
}
|
|
||||||
|
|
||||||
//The first operation is to move all of the reference fields to the front. To do this, find the first
|
|
||||||
//non-reference field, then find the last reference field, swap them and repeat
|
|
||||||
int back = fields.size() - 1;
|
|
||||||
int front;
|
|
||||||
for (front = 0; front<fieldCount; front++) {
|
|
||||||
if (fieldTypes[front] != REFERENCE) {
|
|
||||||
while (back > front) {
|
|
||||||
if (fieldTypes[back] == REFERENCE) {
|
|
||||||
swap(fieldTypes, fields, front, back--);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
back--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fieldTypes[front] != REFERENCE) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int startFieldOffset = 8;
|
|
||||||
String superclassType = getSuperclass();
|
|
||||||
ClassProto superclass = null;
|
|
||||||
if (superclassType != null) {
|
|
||||||
superclass = (ClassProto) classPath.getClass(superclassType);
|
|
||||||
if (superclass != null) {
|
|
||||||
startFieldOffset = superclass.getNextFieldOffset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int fieldIndexMod;
|
|
||||||
if ((startFieldOffset % 8) == 0) {
|
|
||||||
fieldIndexMod = 0;
|
|
||||||
} else {
|
|
||||||
fieldIndexMod = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
//next, we need to group all the wide fields after the reference fields. But the wide fields have to be
|
|
||||||
//8-byte aligned. If we're on an odd field index, we need to insert a 32-bit field. If the next field
|
|
||||||
//is already a 32-bit field, use that. Otherwise, find the first 32-bit field from the end and swap it in.
|
|
||||||
//If there are no 32-bit fields, do nothing for now. We'll add padding when calculating the field offsets
|
|
||||||
if (front < fieldCount && (front % 2) != fieldIndexMod) {
|
|
||||||
if (fieldTypes[front] == WIDE) {
|
|
||||||
//we need to swap in a 32-bit field, so the wide fields will be correctly aligned
|
|
||||||
back = fieldCount - 1;
|
|
||||||
while (back > front) {
|
|
||||||
if (fieldTypes[back] == OTHER) {
|
|
||||||
swap(fieldTypes, fields, front++, back);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
back--;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
//there's already a 32-bit field here that we can use
|
|
||||||
front++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//do the swap thing for wide fields
|
|
||||||
back = fieldCount - 1;
|
|
||||||
for (; front<fieldCount; front++) {
|
|
||||||
if (fieldTypes[front] != WIDE) {
|
|
||||||
while (back > front) {
|
|
||||||
if (fieldTypes[back] == WIDE) {
|
|
||||||
swap(fieldTypes, fields, front, back--);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
back--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fieldTypes[front] != WIDE) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int superFieldCount = 0;
|
|
||||||
if (superclass != null) {
|
|
||||||
superFieldCount = superclass.instanceFields.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
//now the fields are in the correct order. Add them to the SparseArray and lookup, and calculate the offsets
|
|
||||||
int totalFieldCount = superFieldCount + fieldCount;
|
|
||||||
SparseArray<FieldReference> instanceFields = new SparseArray<FieldReference>(totalFieldCount);
|
|
||||||
|
|
||||||
int fieldOffset;
|
|
||||||
|
|
||||||
if (superclass != null && superFieldCount > 0) {
|
|
||||||
for (int i=0; i<superFieldCount; i++) {
|
|
||||||
instanceFields.append(superclass.instanceFields.keyAt(i), superclass.instanceFields.valueAt(i));
|
|
||||||
}
|
|
||||||
|
|
||||||
fieldOffset = instanceFields.keyAt(superFieldCount-1);
|
|
||||||
|
|
||||||
FieldReference lastSuperField = superclass.instanceFields.valueAt(superFieldCount-1);
|
|
||||||
char fieldType = lastSuperField.getType().charAt(0);
|
|
||||||
if (fieldType == 'J' || fieldType == 'D') {
|
|
||||||
fieldOffset += 8;
|
|
||||||
} else {
|
|
||||||
fieldOffset += 4;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
//the field values start at 8 bytes into the DataObject dalvik structure
|
|
||||||
fieldOffset = 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean gotDouble = false;
|
|
||||||
for (int i=0; i<fieldCount; i++) {
|
|
||||||
FieldReference field = fields.get(i);
|
|
||||||
|
|
||||||
//add padding to align the wide fields, if needed
|
|
||||||
if (fieldTypes[i] == WIDE && !gotDouble) {
|
|
||||||
if (!gotDouble) {
|
|
||||||
if (fieldOffset % 8 != 0) {
|
|
||||||
assert fieldOffset % 8 == 4;
|
|
||||||
fieldOffset += 4;
|
|
||||||
}
|
|
||||||
gotDouble = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
instanceFields.append(fieldOffset, field);
|
|
||||||
if (fieldTypes[i] == WIDE) {
|
|
||||||
fieldOffset += 8;
|
|
||||||
} else {
|
|
||||||
fieldOffset += 4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return instanceFields;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull private final Supplier<SparseArray<FieldReference>> instanceFieldsSupplier =
|
||||||
private static ArrayList<Field> getSortedInstanceFields(@Nonnull ClassDef classDef) {
|
Suppliers.memoize(new Supplier<SparseArray<FieldReference>>() {
|
||||||
ArrayList<Field> fields = Lists.newArrayList(classDef.getInstanceFields());
|
@Override public SparseArray<FieldReference> get() {
|
||||||
Collections.sort(fields);
|
//This is a bit of an "involved" operation. We need to follow the same algorithm that dalvik uses to
|
||||||
return fields;
|
//arrange fields, so that we end up with the same field offsets (which is needed for deodexing).
|
||||||
}
|
//See mydroid/dalvik/vm/oo/Class.c - computeFieldOffsets()
|
||||||
|
|
||||||
private byte getFieldType(@Nonnull FieldReference field) {
|
final byte REFERENCE = 0;
|
||||||
switch (field.getType().charAt(0)) {
|
final byte WIDE = 1;
|
||||||
case '[':
|
final byte OTHER = 2;
|
||||||
case 'L':
|
|
||||||
return 0; //REFERENCE
|
|
||||||
case 'J':
|
|
||||||
case 'D':
|
|
||||||
return 1; //WIDE
|
|
||||||
default:
|
|
||||||
return 2; //OTHER
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void swap(byte[] fieldTypes, List<Field> fields, int position1, int position2) {
|
ArrayList<Field> fields = getSortedInstanceFields(getClassDef());
|
||||||
byte tempType = fieldTypes[position1];
|
final int fieldCount = fields.size();
|
||||||
fieldTypes[position1] = fieldTypes[position2];
|
//the "type" for each field in fields. 0=reference,1=wide,2=other
|
||||||
fieldTypes[position2] = tempType;
|
byte[] fieldTypes = new byte[fields.size()];
|
||||||
|
for (int i=0; i<fieldCount; i++) {
|
||||||
|
fieldTypes[i] = getFieldType(fields.get(i));
|
||||||
|
}
|
||||||
|
|
||||||
Field tempField = fields.set(position1, fields.get(position2));
|
//The first operation is to move all of the reference fields to the front. To do this, find the first
|
||||||
fields.set(position2, tempField);
|
//non-reference field, then find the last reference field, swap them and repeat
|
||||||
}
|
int back = fields.size() - 1;
|
||||||
|
int front;
|
||||||
|
for (front = 0; front<fieldCount; front++) {
|
||||||
|
if (fieldTypes[front] != REFERENCE) {
|
||||||
|
while (back > front) {
|
||||||
|
if (fieldTypes[back] == REFERENCE) {
|
||||||
|
swap(fieldTypes, fields, front, back--);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
back--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fieldTypes[front] != REFERENCE) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int startFieldOffset = 8;
|
||||||
|
String superclassType = getSuperclass();
|
||||||
|
ClassProto superclass = null;
|
||||||
|
if (superclassType != null) {
|
||||||
|
superclass = (ClassProto) classPath.getClass(superclassType);
|
||||||
|
if (superclass != null) {
|
||||||
|
startFieldOffset = superclass.getNextFieldOffset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int fieldIndexMod;
|
||||||
|
if ((startFieldOffset % 8) == 0) {
|
||||||
|
fieldIndexMod = 0;
|
||||||
|
} else {
|
||||||
|
fieldIndexMod = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
//next, we need to group all the wide fields after the reference fields. But the wide fields have to be
|
||||||
|
//8-byte aligned. If we're on an odd field index, we need to insert a 32-bit field. If the next field
|
||||||
|
//is already a 32-bit field, use that. Otherwise, find the first 32-bit field from the end and swap it in.
|
||||||
|
//If there are no 32-bit fields, do nothing for now. We'll add padding when calculating the field offsets
|
||||||
|
if (front < fieldCount && (front % 2) != fieldIndexMod) {
|
||||||
|
if (fieldTypes[front] == WIDE) {
|
||||||
|
//we need to swap in a 32-bit field, so the wide fields will be correctly aligned
|
||||||
|
back = fieldCount - 1;
|
||||||
|
while (back > front) {
|
||||||
|
if (fieldTypes[back] == OTHER) {
|
||||||
|
swap(fieldTypes, fields, front++, back);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
back--;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//there's already a 32-bit field here that we can use
|
||||||
|
front++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//do the swap thing for wide fields
|
||||||
|
back = fieldCount - 1;
|
||||||
|
for (; front<fieldCount; front++) {
|
||||||
|
if (fieldTypes[front] != WIDE) {
|
||||||
|
while (back > front) {
|
||||||
|
if (fieldTypes[back] == WIDE) {
|
||||||
|
swap(fieldTypes, fields, front, back--);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
back--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fieldTypes[front] != WIDE) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SparseArray<FieldReference> superFields;
|
||||||
|
if (superclass != null) {
|
||||||
|
superFields = superclass.getInstanceFields();
|
||||||
|
} else {
|
||||||
|
superFields = new SparseArray<FieldReference>();
|
||||||
|
}
|
||||||
|
int superFieldCount = superFields.size();
|
||||||
|
|
||||||
|
//now the fields are in the correct order. Add them to the SparseArray and lookup, and calculate the offsets
|
||||||
|
int totalFieldCount = superFieldCount + fieldCount;
|
||||||
|
SparseArray<FieldReference> instanceFields = new SparseArray<FieldReference>(totalFieldCount);
|
||||||
|
|
||||||
|
int fieldOffset;
|
||||||
|
|
||||||
|
if (superclass != null && superFieldCount > 0) {
|
||||||
|
for (int i=0; i<superFieldCount; i++) {
|
||||||
|
instanceFields.append(superFields.keyAt(i), superFields.valueAt(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldOffset = instanceFields.keyAt(superFieldCount-1);
|
||||||
|
|
||||||
|
FieldReference lastSuperField = superFields.valueAt(superFieldCount-1);
|
||||||
|
char fieldType = lastSuperField.getType().charAt(0);
|
||||||
|
if (fieldType == 'J' || fieldType == 'D') {
|
||||||
|
fieldOffset += 8;
|
||||||
|
} else {
|
||||||
|
fieldOffset += 4;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//the field values start at 8 bytes into the DataObject dalvik structure
|
||||||
|
fieldOffset = 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean gotDouble = false;
|
||||||
|
for (int i=0; i<fieldCount; i++) {
|
||||||
|
FieldReference field = fields.get(i);
|
||||||
|
|
||||||
|
//add padding to align the wide fields, if needed
|
||||||
|
if (fieldTypes[i] == WIDE && !gotDouble) {
|
||||||
|
if (!gotDouble) {
|
||||||
|
if (fieldOffset % 8 != 0) {
|
||||||
|
assert fieldOffset % 8 == 4;
|
||||||
|
fieldOffset += 4;
|
||||||
|
}
|
||||||
|
gotDouble = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
instanceFields.append(fieldOffset, field);
|
||||||
|
if (fieldTypes[i] == WIDE) {
|
||||||
|
fieldOffset += 8;
|
||||||
|
} else {
|
||||||
|
fieldOffset += 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return instanceFields;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
private ArrayList<Field> getSortedInstanceFields(@Nonnull ClassDef classDef) {
|
||||||
|
ArrayList<Field> fields = Lists.newArrayList(classDef.getInstanceFields());
|
||||||
|
Collections.sort(fields);
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte getFieldType(@Nonnull FieldReference field) {
|
||||||
|
switch (field.getType().charAt(0)) {
|
||||||
|
case '[':
|
||||||
|
case 'L':
|
||||||
|
return 0; //REFERENCE
|
||||||
|
case 'J':
|
||||||
|
case 'D':
|
||||||
|
return 1; //WIDE
|
||||||
|
default:
|
||||||
|
return 2; //OTHER
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void swap(byte[] fieldTypes, List<Field> fields, int position1, int position2) {
|
||||||
|
byte tempType = fieldTypes[position1];
|
||||||
|
fieldTypes[position1] = fieldTypes[position2];
|
||||||
|
fieldTypes[position2] = tempType;
|
||||||
|
|
||||||
|
Field tempField = fields.set(position1, fields.get(position2));
|
||||||
|
fields.set(position2, tempField);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
private int getNextFieldOffset() {
|
private int getNextFieldOffset() {
|
||||||
SparseArray<FieldReference> instanceFields = getInstanceFields();
|
SparseArray<FieldReference> instanceFields = getInstanceFields();
|
||||||
@ -552,93 +560,100 @@ public class ClassProto implements TypeProto {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: check the case when we have a package private method that overrides an interface method
|
@Nonnull List<Method> getVtable() {
|
||||||
private void loadVtable() {
|
return vtableSupplier.get();
|
||||||
vtable = Lists.newArrayList();
|
|
||||||
|
|
||||||
//copy the virtual methods from the superclass
|
|
||||||
String superclassType;
|
|
||||||
try {
|
|
||||||
superclassType = getSuperclass();
|
|
||||||
} catch (UnresolvedClassException ex) {
|
|
||||||
vtable.addAll(((ClassProto)classPath.getClass("Ljava/lang/Object;")).getVtable());
|
|
||||||
vtableFullyResolved = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (superclassType != null) {
|
|
||||||
ClassProto superclass = (ClassProto) classPath.getClass(superclassType);
|
|
||||||
vtable.addAll(superclass.getVtable());
|
|
||||||
|
|
||||||
// if the superclass's vtable wasn't fully resolved, then we can't know where the new methods added by this
|
|
||||||
// class should start, so we just propagate what we can from the parent and hope for the best.
|
|
||||||
if (!superclass.interfacesFullyResolved) {
|
|
||||||
vtableFullyResolved = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//iterate over the virtual methods in the current class, and only add them when we don't already have the
|
|
||||||
//method (i.e. if it was implemented by the superclass)
|
|
||||||
if (!isInterface()) {
|
|
||||||
addToVtable(getClassDef().getVirtualMethods(), vtable, true);
|
|
||||||
|
|
||||||
for (ClassDef interfaceDef: getDirectInterfaces()) {
|
|
||||||
addToVtable(interfaceDef.getVirtualMethods(), vtable, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addToVtable(@Nonnull Iterable<? extends Method> localMethods,
|
//TODO: check the case when we have a package private method that overrides an interface method
|
||||||
@Nonnull List<Method> vtable, boolean replaceExisting) {
|
@Nonnull private final Supplier<List<Method>> vtableSupplier = Suppliers.memoize(new Supplier<List<Method>>() {
|
||||||
List<? extends Method> methods = Lists.newArrayList(localMethods);
|
@Override public List<Method> get() {
|
||||||
Collections.sort(methods);
|
List<Method> vtable = Lists.newArrayList();
|
||||||
|
|
||||||
outer: for (Method virtualMethod: methods) {
|
//copy the virtual methods from the superclass
|
||||||
for (int i=0; i<vtable.size(); i++) {
|
String superclassType;
|
||||||
Method superMethod = vtable.get(i);
|
try {
|
||||||
if (methodSignaturesMatch(superMethod, virtualMethod)) {
|
superclassType = getSuperclass();
|
||||||
if (classPath.getApi() < 17 || canAccess(superMethod)) {
|
} catch (UnresolvedClassException ex) {
|
||||||
if (replaceExisting) {
|
vtable.addAll(((ClassProto)classPath.getClass("Ljava/lang/Object;")).getVtable());
|
||||||
vtable.set(i, virtualMethod);
|
vtableFullyResolved = false;
|
||||||
}
|
return vtable;
|
||||||
continue outer;
|
}
|
||||||
}
|
|
||||||
|
if (superclassType != null) {
|
||||||
|
ClassProto superclass = (ClassProto) classPath.getClass(superclassType);
|
||||||
|
vtable.addAll(superclass.getVtable());
|
||||||
|
|
||||||
|
// if the superclass's vtable wasn't fully resolved, then we can't know where the new methods added by this
|
||||||
|
// class should start, so we just propagate what we can from the parent and hope for the best.
|
||||||
|
if (!superclass.vtableFullyResolved) {
|
||||||
|
vtableFullyResolved = false;
|
||||||
|
return vtable;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// we didn't find an equivalent method, so add it as a new entry
|
|
||||||
vtable.add(virtualMethod);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean methodSignaturesMatch(@Nonnull Method a, @Nonnull Method b) {
|
//iterate over the virtual methods in the current class, and only add them when we don't already have the
|
||||||
return (a.getName().equals(b.getName()) &&
|
//method (i.e. if it was implemented by the superclass)
|
||||||
a.getReturnType().equals(b.getReturnType()) &&
|
if (!isInterface()) {
|
||||||
a.getParameters().equals(b.getParameters()));
|
addToVtable(getClassDef().getVirtualMethods(), vtable, true);
|
||||||
}
|
|
||||||
|
|
||||||
private boolean canAccess(@Nonnull Method virtualMethod) {
|
for (ClassDef interfaceDef: getDirectInterfaces()) {
|
||||||
if (!methodIsPackagePrivate(virtualMethod.getAccessFlags())) {
|
addToVtable(interfaceDef.getVirtualMethods(), vtable, false);
|
||||||
return true;
|
}
|
||||||
|
}
|
||||||
|
return vtable;
|
||||||
}
|
}
|
||||||
|
|
||||||
String otherPackage = getPackage(virtualMethod.getDefiningClass());
|
private void addToVtable(@Nonnull Iterable<? extends Method> localMethods,
|
||||||
String ourPackage = getPackage(getClassDef().getType());
|
@Nonnull List<Method> vtable, boolean replaceExisting) {
|
||||||
return otherPackage.equals(ourPackage);
|
List<? extends Method> methods = Lists.newArrayList(localMethods);
|
||||||
}
|
Collections.sort(methods);
|
||||||
|
|
||||||
@Nonnull
|
outer: for (Method virtualMethod: methods) {
|
||||||
private static String getPackage(@Nonnull String classType) {
|
for (int i=0; i<vtable.size(); i++) {
|
||||||
int lastSlash = classType.lastIndexOf('/');
|
Method superMethod = vtable.get(i);
|
||||||
if (lastSlash < 0) {
|
if (methodSignaturesMatch(superMethod, virtualMethod)) {
|
||||||
return "";
|
if (classPath.getApi() < 17 || canAccess(superMethod)) {
|
||||||
|
if (replaceExisting) {
|
||||||
|
vtable.set(i, virtualMethod);
|
||||||
|
}
|
||||||
|
continue outer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// we didn't find an equivalent method, so add it as a new entry
|
||||||
|
vtable.add(virtualMethod);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return classType.substring(1, lastSlash);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean methodIsPackagePrivate(int accessFlags) {
|
private boolean methodSignaturesMatch(@Nonnull Method a, @Nonnull Method b) {
|
||||||
return (accessFlags & (AccessFlags.PRIVATE.getValue() |
|
return (a.getName().equals(b.getName()) &&
|
||||||
AccessFlags.PROTECTED.getValue() |
|
a.getReturnType().equals(b.getReturnType()) &&
|
||||||
AccessFlags.PUBLIC.getValue())) == 0;
|
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;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
@ -159,7 +159,7 @@ public class ClassFileNameHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public File addUniqueChild(String[] pathElements, int pathElementsIndex) {
|
public synchronized File addUniqueChild(String[] pathElements, int pathElementsIndex) {
|
||||||
String elementName;
|
String elementName;
|
||||||
String elementNameLower;
|
String elementNameLower;
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user