refactor: use own dex parser instead deprecated dx lib

This commit is contained in:
Skylot 2020-04-29 17:24:44 +01:00
parent 09e267f8bc
commit 0d69e0ac97
181 changed files with 6779 additions and 3072 deletions

1
.gitignore vendored
View File

@ -27,7 +27,6 @@ jadx-output/
*-tmp/
**/tmp/
*.dex
*.class
*.dump
*.log

View File

@ -34,6 +34,7 @@ allprojects {
dependencies {
compile 'org.slf4j:slf4j-api:1.7.30'
compileOnly 'org.jetbrains:annotations:19.0.0'
testCompile 'ch.qos.logback:logback-classic:1.2.3'
testCompile 'org.hamcrest:hamcrest-library:2.2'
@ -44,6 +45,7 @@ allprojects {
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.6.2'
testCompile 'org.eclipse.jdt.core.compiler:ecj:4.6.1'
testCompileOnly 'org.jetbrains:annotations:19.0.0'
}
test {
@ -86,6 +88,7 @@ spotless {
include 'jadx-cli/src/**/java/**/*.java'
include 'jadx-core/src/**/java/**/*.java'
include 'jadx-gui/src/**/java/**/*.java'
include 'jadx-plugins/**/java/**/*.java'
}
importOrderFile 'config/code-formatter/eclipse.importorder'

View File

@ -4,6 +4,10 @@ plugins {
dependencies {
compile(project(':jadx-core'))
runtime(project(':jadx-plugins:jadx-dex-input'))
runtime(project(':jadx-plugins:jadx-java-convert'))
compile 'com.beust:jcommander:1.78'
compile 'ch.qos.logback:logback-classic:1.2.3'
}

View File

@ -17,8 +17,11 @@ public class JadxCLI {
try {
JadxCLIArgs jadxArgs = new JadxCLIArgs();
if (jadxArgs.processArgs(args)) {
result = processAndSave(jadxArgs);
result = processAndSave(jadxArgs.toJadxArgs());
}
} catch (JadxArgsValidateException e) {
LOG.error("Incorrect arguments: {}", e.getMessage());
result = 1;
} catch (Exception e) {
LOG.error("jadx error: {}", e.getMessage(), e);
result = 1;
@ -28,23 +31,18 @@ public class JadxCLI {
}
}
static int processAndSave(JadxCLIArgs inputArgs) {
JadxArgs args = inputArgs.toJadxArgs();
args.setCodeCache(new NoOpCodeCache());
JadxDecompiler jadx = new JadxDecompiler(args);
try {
static int processAndSave(JadxArgs jadxArgs) {
jadxArgs.setCodeCache(new NoOpCodeCache());
try (JadxDecompiler jadx = new JadxDecompiler(jadxArgs)) {
jadx.load();
} catch (JadxArgsValidateException e) {
LOG.error("Incorrect arguments: {}", e.getMessage());
return 1;
}
jadx.save();
int errorsCount = jadx.getErrorsCount();
if (errorsCount != 0) {
jadx.printErrorsReport();
LOG.error("finished with errors, count: {}", errorsCount);
} else {
LOG.info("done");
jadx.save();
int errorsCount = jadx.getErrorsCount();
if (errorsCount != 0) {
jadx.printErrorsReport();
LOG.error("finished with errors, count: {}", errorsCount);
} else {
LOG.info("done");
}
}
return 0;
}

View File

@ -1,11 +1,13 @@
plugins {
id 'java-library'
}
dependencies {
runtime files('clsp-data/android-29-clst.jar')
runtime files('clsp-data/android-29-res.jar')
compile files('lib/dx-1.16.jar') // TODO: dx don't support java version > 9 (53)
api(project(':jadx-plugins:jadx-plugins-api'))
compile 'org.ow2.asm:asm:8.0.1'
compile 'org.jetbrains:annotations:19.0.0'
compile 'com.google.code.gson:gson:2.8.6'
compile 'org.smali:baksmali:2.4.0'
@ -15,6 +17,9 @@ dependencies {
compile 'com.google.guava:guava:29.0-jre'
testCompile 'org.apache.commons:commons-lang3:3.9'
testRuntime(project(':jadx-plugins:jadx-dex-input'))
testRuntime(project(':jadx-plugins:jadx-java-convert'))
}
test {

View File

@ -1,6 +1,8 @@
package jadx.api;
import java.io.Closeable;
import java.io.File;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
@ -17,6 +19,10 @@ import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.plugins.JadxPlugin;
import jadx.api.plugins.JadxPluginManager;
import jadx.api.plugins.input.JadxInputPlugin;
import jadx.api.plugins.input.data.ILoadResult;
import jadx.core.Jadx;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.nodes.LineAttrNode;
@ -26,8 +32,8 @@ import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.visitors.SaveCode;
import jadx.core.export.ExportGradleProject;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.InputFile;
import jadx.core.xmlgen.BinaryXMLParser;
import jadx.core.xmlgen.ResourcesSaver;
@ -39,10 +45,10 @@ import jadx.core.xmlgen.ResourcesSaver;
* JadxArgs args = new JadxArgs();
* args.getInputFiles().add(new File("test.apk"));
* args.setOutDir(new File("jadx-test-output"));
*
* JadxDecompiler jadx = new JadxDecompiler(args);
* jadx.load();
* jadx.save();
* try (JadxDecompiler jadx = new JadxDecompiler(args)) {
* jadx.load();
* jadx.save();
* }
* </code>
* </pre>
* <p>
@ -56,11 +62,12 @@ import jadx.core.xmlgen.ResourcesSaver;
* </code>
* </pre>
*/
public final class JadxDecompiler {
public final class JadxDecompiler implements Closeable {
private static final Logger LOG = LoggerFactory.getLogger(JadxDecompiler.class);
private JadxArgs args;
private List<InputFile> inputFiles;
private JadxPluginManager pluginManager = new JadxPluginManager();
private List<ILoadResult> loadedInputs = new ArrayList<>();
private RootNode root;
private List<JavaClass> classes;
@ -68,9 +75,9 @@ public final class JadxDecompiler {
private BinaryXMLParser xmlParser;
private Map<ClassNode, JavaClass> classesMap = new ConcurrentHashMap<>();
private Map<MethodNode, JavaMethod> methodsMap = new ConcurrentHashMap<>();
private Map<FieldNode, JavaField> fieldsMap = new ConcurrentHashMap<>();
private final Map<ClassNode, JavaClass> classesMap = new ConcurrentHashMap<>();
private final Map<MethodNode, JavaMethod> methodsMap = new ConcurrentHashMap<>();
private final Map<FieldNode, JavaField> fieldsMap = new ConcurrentHashMap<>();
public JadxDecompiler() {
this(new JadxArgs());
@ -84,16 +91,23 @@ public final class JadxDecompiler {
reset();
JadxArgsValidator.validate(args);
LOG.info("loading ...");
inputFiles = loadFiles(args.getInputFiles());
loadInputFiles();
root = new RootNode(args);
root.load(inputFiles);
root.loadClasses(loadedInputs);
root.initClassPath();
root.loadResources(getResources());
root.initPasses();
}
private void loadInputFiles() {
loadedInputs.clear();
List<Path> inputPaths = Utils.collectionMap(args.getInputFiles(), File::toPath);
for (JadxInputPlugin inputPlugin : pluginManager.getInputPlugins()) {
loadedInputs.add(inputPlugin.loadFiles(inputPaths));
}
}
private void reset() {
root = null;
classes = null;
@ -103,27 +117,34 @@ public final class JadxDecompiler {
classesMap.clear();
methodsMap.clear();
fieldsMap.clear();
closeInputs();
}
private void closeInputs() {
loadedInputs.forEach(load -> {
try {
load.close();
} catch (Exception e) {
LOG.error("Failed to close input", e);
}
});
loadedInputs.clear();
}
@Override
public void close() {
reset();
}
public void registerPlugin(JadxPlugin plugin) {
pluginManager.register(plugin);
}
public static String getVersion() {
return Jadx.getVersion();
}
private List<InputFile> loadFiles(List<File> files) {
if (files.isEmpty()) {
throw new JadxRuntimeException("Empty file list");
}
List<InputFile> filesList = new ArrayList<>();
for (File file : files) {
try {
InputFile.addFilesFrom(file, filesList, args.isSkipSources());
} catch (Exception e) {
throw new JadxRuntimeException("Error load file: " + file, e);
}
}
return filesList;
}
public void save() {
save(!args.isSkipSources(), !args.isSkipResources());
}
@ -232,7 +253,7 @@ public final class JadxDecompiler {
if (root == null) {
return Collections.emptyList();
}
resources = new ResourcesLoader(this).load(inputFiles);
resources = new ResourcesLoader(this).load();
}
return resources;
}
@ -432,4 +453,5 @@ public final class JadxDecompiler {
public String toString() {
return "jadx decompiler " + getVersion();
}
}

View File

@ -39,7 +39,7 @@ public final class JavaField implements JavaNode {
}
public ArgType getType() {
return ArgType.tryToResolveClassAlias(field.dex(), field.getType());
return ArgType.tryToResolveClassAlias(field.root(), field.getType());
}
@Override

View File

@ -48,12 +48,12 @@ public final class JavaMethod implements JavaNode {
}
List<ArgType> arguments = mth.getArgTypes();
return Utils.collectionMap(arguments,
type -> ArgType.tryToResolveClassAlias(mth.dex(), type));
type -> ArgType.tryToResolveClassAlias(mth.root(), type));
}
public ArgType getReturnType() {
ArgType retType = mth.getReturnType();
return ArgType.tryToResolveClassAlias(mth.dex(), retType);
return ArgType.tryToResolveClassAlias(mth.root(), retType);
}
public boolean isConstructor() {

View File

@ -21,7 +21,6 @@ import jadx.core.codegen.CodeWriter;
import jadx.core.utils.Utils;
import jadx.core.utils.android.Res9patchStreamDecoder;
import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.files.InputFile;
import jadx.core.utils.files.ZipSecurity;
import jadx.core.xmlgen.ResContainer;
import jadx.core.xmlgen.ResTableParser;
@ -39,10 +38,11 @@ public final class ResourcesLoader {
this.jadxRef = jadxRef;
}
List<ResourceFile> load(List<InputFile> inputFiles) {
List<ResourceFile> load() {
List<File> inputFiles = jadxRef.getArgs().getInputFiles();
List<ResourceFile> list = new ArrayList<>(inputFiles.size());
for (InputFile file : inputFiles) {
loadFile(list, file.getFile());
for (File file : inputFiles) {
loadFile(list, file);
}
return list;
}

View File

@ -11,11 +11,11 @@ public class Consts {
public static final String CLASS_STRING_BUILDER = "java.lang.StringBuilder";
public static final String DALVIK_ANNOTATION_PKG = "dalvik.annotation.";
public static final String DALVIK_SIGNATURE = "dalvik.annotation.Signature";
public static final String DALVIK_INNER_CLASS = "dalvik.annotation.InnerClass";
public static final String DALVIK_THROWS = "dalvik.annotation.Throws";
public static final String DALVIK_ANNOTATION_DEFAULT = "dalvik.annotation.AnnotationDefault";
public static final String DALVIK_ANNOTATION_PKG = "Ldalvik/annotation/";
public static final String DALVIK_SIGNATURE = "Ldalvik/annotation/Signature;";
public static final String DALVIK_INNER_CLASS = "Ldalvik/annotation/InnerClass;";
public static final String DALVIK_THROWS = "Ldalvik/annotation/Throws;";
public static final String DALVIK_ANNOTATION_DEFAULT = "Ldalvik/annotation/AnnotationDefault;";
public static final String DEFAULT_PACKAGE_NAME = "defpackage";
public static final String ANONYMOUS_CLASS_PREFIX = "AnonymousClass";

View File

@ -11,13 +11,40 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.JadxArgs;
import jadx.core.dex.visitors.*;
import jadx.core.dex.visitors.AttachMethodDetails;
import jadx.core.dex.visitors.AttachTryCatchVisitor;
import jadx.core.dex.visitors.ClassModifier;
import jadx.core.dex.visitors.ConstInlineVisitor;
import jadx.core.dex.visitors.ConstructorVisitor;
import jadx.core.dex.visitors.DeboxingVisitor;
import jadx.core.dex.visitors.DependencyCollector;
import jadx.core.dex.visitors.DotGraphVisitor;
import jadx.core.dex.visitors.EnumVisitor;
import jadx.core.dex.visitors.ExtractFieldInit;
import jadx.core.dex.visitors.FallbackModeVisitor;
import jadx.core.dex.visitors.FixAccessModifiers;
import jadx.core.dex.visitors.GenericTypesVisitor;
import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.dex.visitors.InitCodeVariables;
import jadx.core.dex.visitors.MarkFinallyVisitor;
import jadx.core.dex.visitors.MethodInlineVisitor;
import jadx.core.dex.visitors.MethodInvokeVisitor;
import jadx.core.dex.visitors.ModVisitor;
import jadx.core.dex.visitors.MoveInlineVisitor;
import jadx.core.dex.visitors.OverrideMethodVisitor;
import jadx.core.dex.visitors.PrepareForCodeGen;
import jadx.core.dex.visitors.ProcessAnonymous;
import jadx.core.dex.visitors.ProcessInstructionsVisitor;
import jadx.core.dex.visitors.ReSugarCode;
import jadx.core.dex.visitors.RenameVisitor;
import jadx.core.dex.visitors.ShadowFieldVisitor;
import jadx.core.dex.visitors.SimplifyVisitor;
import jadx.core.dex.visitors.blocksmaker.BlockExceptionHandler;
import jadx.core.dex.visitors.blocksmaker.BlockFinish;
import jadx.core.dex.visitors.blocksmaker.BlockProcessor;
import jadx.core.dex.visitors.blocksmaker.BlockSplitter;
import jadx.core.dex.visitors.debuginfo.DebugInfoApplyVisitor;
import jadx.core.dex.visitors.debuginfo.DebugInfoParseVisitor;
import jadx.core.dex.visitors.debuginfo.DebugInfoAttachVisitor;
import jadx.core.dex.visitors.regions.CheckRegions;
import jadx.core.dex.visitors.regions.CleanRegions;
import jadx.core.dex.visitors.regions.IfRegionVisitor;
@ -41,73 +68,88 @@ public class Jadx {
}
}
public static List<IDexTreeVisitor> getFallbackPassesList() {
List<IDexTreeVisitor> passes = new ArrayList<>(3);
passes.add(new AttachTryCatchVisitor());
passes.add(new ProcessInstructionsVisitor());
passes.add(new FallbackModeVisitor());
return passes;
}
public static List<IDexTreeVisitor> getPassesList(JadxArgs args) {
List<IDexTreeVisitor> passes = new ArrayList<>();
if (args.isFallbackMode()) {
passes.add(new FallbackModeVisitor());
} else {
if (args.isDebugInfo()) {
passes.add(new DebugInfoParseVisitor());
}
passes.add(new BlockSplitter());
if (args.isRawCFGOutput()) {
passes.add(DotGraphVisitor.dumpRaw());
}
passes.add(new BlockProcessor());
passes.add(new BlockExceptionHandler());
passes.add(new BlockFinish());
passes.add(new AttachMethodDetails());
passes.add(new OverrideMethodVisitor());
passes.add(new SSATransform());
passes.add(new MoveInlineVisitor());
passes.add(new ConstructorVisitor());
passes.add(new InitCodeVariables());
passes.add(new MarkFinallyVisitor());
passes.add(new ConstInlineVisitor());
passes.add(new TypeInferenceVisitor());
if (args.isDebugInfo()) {
passes.add(new DebugInfoApplyVisitor());
}
passes.add(new GenericTypesVisitor());
passes.add(new ShadowFieldVisitor());
passes.add(new DeboxingVisitor());
passes.add(new ModVisitor());
passes.add(new CodeShrinkVisitor());
passes.add(new ReSugarCode());
if (args.isCfgOutput()) {
passes.add(DotGraphVisitor.dump());
}
passes.add(new RegionMakerVisitor());
passes.add(new IfRegionVisitor());
passes.add(new ReturnVisitor());
passes.add(new CleanRegions());
passes.add(new CodeShrinkVisitor());
passes.add(new MethodInvokeVisitor());
passes.add(new SimplifyVisitor());
passes.add(new CheckRegions());
passes.add(new EnumVisitor());
passes.add(new ExtractFieldInit());
passes.add(new FixAccessModifiers());
passes.add(new ProcessAnonymous());
passes.add(new ClassModifier());
passes.add(new MethodInlineVisitor());
passes.add(new LoopRegionVisitor());
passes.add(new ProcessVariables());
passes.add(new PrepareForCodeGen());
if (args.isCfgOutput()) {
passes.add(DotGraphVisitor.dumpRegions());
}
passes.add(new DependencyCollector());
passes.add(new RenameVisitor());
return getFallbackPassesList();
}
List<IDexTreeVisitor> passes = new ArrayList<>();
if (args.isDebugInfo()) {
passes.add(new DebugInfoAttachVisitor());
}
passes.add(new AttachTryCatchVisitor());
passes.add(new ProcessInstructionsVisitor());
passes.add(new BlockSplitter());
if (args.isRawCFGOutput()) {
passes.add(DotGraphVisitor.dumpRaw());
}
passes.add(new BlockProcessor());
passes.add(new BlockExceptionHandler());
passes.add(new BlockFinish());
passes.add(new AttachMethodDetails());
passes.add(new OverrideMethodVisitor());
passes.add(new SSATransform());
passes.add(new MoveInlineVisitor());
passes.add(new ConstructorVisitor());
passes.add(new InitCodeVariables());
passes.add(new MarkFinallyVisitor());
passes.add(new ConstInlineVisitor());
passes.add(new TypeInferenceVisitor());
if (args.isRawCFGOutput()) {
passes.add(DotGraphVisitor.dumpRaw());
}
if (args.isDebugInfo()) {
passes.add(new DebugInfoApplyVisitor());
}
passes.add(new GenericTypesVisitor());
passes.add(new ShadowFieldVisitor());
passes.add(new DeboxingVisitor());
passes.add(new ModVisitor());
passes.add(new CodeShrinkVisitor());
passes.add(new ReSugarCode());
if (args.isCfgOutput()) {
passes.add(DotGraphVisitor.dump());
}
passes.add(new RegionMakerVisitor());
passes.add(new IfRegionVisitor());
passes.add(new ReturnVisitor());
passes.add(new CleanRegions());
passes.add(new CodeShrinkVisitor());
passes.add(new MethodInvokeVisitor());
passes.add(new SimplifyVisitor());
passes.add(new CheckRegions());
passes.add(new EnumVisitor());
passes.add(new ExtractFieldInit());
passes.add(new FixAccessModifiers());
passes.add(new ProcessAnonymous());
passes.add(new ClassModifier());
passes.add(new MethodInlineVisitor());
passes.add(new LoopRegionVisitor());
passes.add(new ProcessVariables());
passes.add(new PrepareForCodeGen());
if (args.isCfgOutput()) {
passes.add(DotGraphVisitor.dumpRegions());
}
passes.add(new DependencyCollector());
passes.add(new RenameVisitor());
return passes;
}

View File

@ -1,20 +1,22 @@
package jadx.core.clsp;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.JadxArgs;
import jadx.api.plugins.JadxPluginManager;
import jadx.api.plugins.input.JadxInputPlugin;
import jadx.api.plugins.input.data.ILoadResult;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.files.InputFile;
/**
* Utility class for convert dex or jar to jadx classes set (.jcst)
@ -26,30 +28,24 @@ public class ConvertToClsSet {
LOG.info("<output .jcst or .jar file> <several input dex or jar files> ");
}
public static void main(String[] args) throws IOException, DecodeException {
public static void main(String[] args) throws IOException {
if (args.length < 2) {
usage();
System.exit(1);
}
Path output = Paths.get(args[0]);
List<Path> inputPaths = Stream.of(args).map(s -> Paths.get(s)).collect(Collectors.toList());
Path output = inputPaths.remove(0);
List<InputFile> inputFiles = new ArrayList<>(args.length - 1);
for (int i = 1; i < args.length; i++) {
File f = new File(args[i]);
if (f.isDirectory()) {
addFilesFromDirectory(f, inputFiles);
} else {
InputFile.addFilesFrom(f, inputFiles, false);
}
}
for (InputFile inputFile : inputFiles) {
LOG.info("Loaded: {}", inputFile.getFile());
JadxPluginManager pluginManager = new JadxPluginManager();
List<ILoadResult> loadedInputs = new ArrayList<>();
for (JadxInputPlugin inputPlugin : pluginManager.getInputPlugins()) {
loadedInputs.add(inputPlugin.loadFiles(inputPaths));
}
JadxArgs jadxArgs = new JadxArgs();
jadxArgs.setRenameFlags(EnumSet.noneOf(JadxArgs.RenameEnum.class));
RootNode root = new RootNode(jadxArgs);
root.load(inputFiles);
root.loadClasses(loadedInputs);
ClsSet set = new ClsSet(root);
set.loadFrom(root);
@ -57,22 +53,4 @@ public class ConvertToClsSet {
LOG.info("Output: {}, file size: {}B", output, output.toFile().length());
LOG.info("done");
}
private static void addFilesFromDirectory(File dir, List<InputFile> inputFiles) {
File[] files = dir.listFiles();
if (files == null) {
return;
}
for (File file : files) {
if (file.isDirectory()) {
addFilesFromDirectory(file, inputFiles);
} else {
try {
InputFile.addFilesFrom(file, inputFiles, false);
} catch (Exception e) {
LOG.warn("Skip file: {}, load error: {}", file, e.getMessage());
}
}
}
}
}

View File

@ -7,10 +7,12 @@ import java.util.Map.Entry;
import org.jetbrains.annotations.Nullable;
import jadx.api.plugins.input.data.IFieldData;
import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.api.plugins.input.data.annotations.IAnnotation;
import jadx.core.Consts;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttributeNode;
import jadx.core.dex.attributes.annotations.Annotation;
import jadx.core.dex.attributes.annotations.AnnotationsList;
import jadx.core.dex.attributes.annotations.MethodParameters;
import jadx.core.dex.info.FieldInfo;
@ -18,6 +20,7 @@ import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.StringUtils;
import jadx.core.utils.exceptions.JadxRuntimeException;
@ -52,7 +55,7 @@ public class AnnotationGen {
if (aList == null || aList.isEmpty()) {
return;
}
for (Annotation a : aList.getAll()) {
for (IAnnotation a : aList.getAll()) {
formatAnnotation(code, a);
code.add(' ');
}
@ -63,7 +66,7 @@ public class AnnotationGen {
if (aList == null || aList.isEmpty()) {
return;
}
for (Annotation a : aList.getAll()) {
for (IAnnotation a : aList.getAll()) {
String aCls = a.getAnnotationClass();
if (!aCls.startsWith(Consts.DALVIK_ANNOTATION_PKG)) {
code.startLine();
@ -72,20 +75,20 @@ public class AnnotationGen {
}
}
private void formatAnnotation(CodeWriter code, Annotation a) {
private void formatAnnotation(CodeWriter code, IAnnotation a) {
code.add('@');
ClassNode annCls = cls.dex().resolveClass(a.getType());
ClassNode annCls = cls.root().resolveClass(a.getAnnotationClass());
if (annCls != null) {
classGen.useClass(code, annCls);
} else {
classGen.useType(code, a.getType());
classGen.useClass(code, a.getAnnotationClass());
}
Map<String, Object> vl = a.getValues();
Map<String, EncodedValue> vl = a.getValues();
if (!vl.isEmpty()) {
code.add('(');
for (Iterator<Entry<String, Object>> it = vl.entrySet().iterator(); it.hasNext();) {
Entry<String, Object> e = it.next();
for (Iterator<Entry<String, EncodedValue>> it = vl.entrySet().iterator(); it.hasNext();) {
Entry<String, EncodedValue> e = it.next();
String paramName = getParamName(annCls, e.getKey());
if (paramName.equals("value") && vl.size() == 1) {
// don't add "value = " if no other parameters
@ -93,7 +96,7 @@ public class AnnotationGen {
code.add(paramName);
code.add(" = ");
}
encodeValue(code, e.getValue());
encodeValue(cls.root(), code, e.getValue());
if (it.hasNext()) {
code.add(", ");
}
@ -127,66 +130,94 @@ public class AnnotationGen {
}
}
public Object getAnnotationDefaultValue(String name) {
Annotation an = cls.getAnnotation(Consts.DALVIK_ANNOTATION_DEFAULT);
public EncodedValue getAnnotationDefaultValue(String name) {
IAnnotation an = cls.getAnnotation(Consts.DALVIK_ANNOTATION_DEFAULT);
if (an != null) {
Annotation defAnnotation = (Annotation) an.getDefaultValue();
IAnnotation defAnnotation = (IAnnotation) an.getDefaultValue().getValue();
return defAnnotation.getValues().get(name);
}
return null;
}
// TODO: refactor this boilerplate code
public void encodeValue(CodeWriter code, Object val) {
if (val == null) {
public void encodeValue(RootNode root, CodeWriter code, EncodedValue encodedValue) {
if (encodedValue == null) {
code.add("null");
return;
}
if (val instanceof String) {
code.add(getStringUtils().unescapeString((String) val));
} else if (val instanceof Integer) {
code.add(TypeGen.formatInteger((Integer) val, false));
} else if (val instanceof Character) {
code.add(getStringUtils().unescapeChar((Character) val));
} else if (val instanceof Boolean) {
code.add(Boolean.TRUE.equals(val) ? "true" : "false");
} else if (val instanceof Float) {
code.add(TypeGen.formatFloat((Float) val));
} else if (val instanceof Double) {
code.add(TypeGen.formatDouble((Double) val));
} else if (val instanceof Long) {
code.add(TypeGen.formatLong((Long) val, false));
} else if (val instanceof Short) {
code.add(TypeGen.formatShort((Short) val, false));
} else if (val instanceof Byte) {
code.add(TypeGen.formatByte((Byte) val, false));
} else if (val instanceof ArgType) {
classGen.useType(code, (ArgType) val);
code.add(".class");
} else if (val instanceof FieldInfo) {
// must be a static field
FieldInfo field = (FieldInfo) val;
InsnGen.makeStaticFieldAccess(code, field, classGen);
} else if (val instanceof Iterable) {
code.add('{');
Iterator<?> it = ((Iterable<?>) val).iterator();
while (it.hasNext()) {
Object obj = it.next();
encodeValue(code, obj);
if (it.hasNext()) {
code.add(", ");
Object value = encodedValue.getValue();
switch (encodedValue.getType()) {
case ENCODED_NULL:
code.add("null");
break;
case ENCODED_BOOLEAN:
code.add(Boolean.TRUE.equals(value) ? "true" : "false");
break;
case ENCODED_BYTE:
code.add(TypeGen.formatByte((Byte) value, false));
break;
case ENCODED_SHORT:
code.add(TypeGen.formatShort((Short) value, false));
break;
case ENCODED_CHAR:
code.add(getStringUtils().unescapeChar((Character) value));
break;
case ENCODED_INT:
code.add(TypeGen.formatInteger((Integer) value, false));
break;
case ENCODED_LONG:
code.add(TypeGen.formatLong((Long) value, false));
break;
case ENCODED_FLOAT:
code.add(TypeGen.formatFloat((Float) value));
break;
case ENCODED_DOUBLE:
code.add(TypeGen.formatDouble((Double) value));
break;
case ENCODED_STRING:
code.add(getStringUtils().unescapeString((String) value));
break;
case ENCODED_TYPE:
classGen.useType(code, ArgType.parse((String) value));
code.add(".class");
break;
case ENCODED_ENUM:
case ENCODED_FIELD:
// must be a static field
if (value instanceof IFieldData) {
FieldInfo field = FieldInfo.fromData(root, (IFieldData) value);
InsnGen.makeStaticFieldAccess(code, field, classGen);
} else if (value instanceof FieldInfo) {
InsnGen.makeStaticFieldAccess(code, (FieldInfo) value, classGen);
} else {
throw new JadxRuntimeException("Unexpected field type class: " + value.getClass());
}
}
code.add('}');
} else if (val instanceof Annotation) {
formatAnnotation(code, (Annotation) val);
} else {
// TODO: also can be method values
throw new JadxRuntimeException("Can't decode value: " + val + " (" + val.getClass() + ')');
break;
case ENCODED_METHOD:
// TODO
break;
case ENCODED_ARRAY:
code.add('{');
Iterator<?> it = ((Iterable<?>) value).iterator();
while (it.hasNext()) {
EncodedValue v = (EncodedValue) it.next();
encodeValue(cls.root(), code, v);
if (it.hasNext()) {
code.add(", ");
}
}
code.add('}');
break;
case ENCODED_ANNOTATION:
formatAnnotation(code, (IAnnotation) value);
break;
default:
throw new JadxRuntimeException("Can't decode value: " + encodedValue.getType() + " (" + encodedValue + ')');
}
}
private StringUtils getStringUtils() {
return cls.dex().root().getStringUtils();
return cls.root().getStringUtils();
}
}

View File

@ -13,13 +13,16 @@ import java.util.stream.Stream;
import org.jetbrains.annotations.Nullable;
import com.android.dx.rop.code.AccessFlags;
import jadx.api.ICodeInfo;
import jadx.api.JadxArgs;
import jadx.api.plugins.input.data.AccessFlags;
import jadx.api.plugins.input.data.annotations.EncodedType;
import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.AttrNode;
import jadx.core.dex.attributes.FieldInitAttr;
import jadx.core.dex.attributes.FieldInitAttr.InitType;
import jadx.core.dex.attributes.nodes.EnumClassAttr;
import jadx.core.dex.attributes.nodes.EnumClassAttr.EnumField;
import jadx.core.dex.attributes.nodes.JadxError;
@ -31,13 +34,11 @@ import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.PrimitiveType;
import jadx.core.dex.instructions.mods.ConstructorInsn;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.GenericTypeParameter;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.parser.FieldInitAttr;
import jadx.core.dex.nodes.parser.FieldInitAttr.InitType;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.CodeGenUtils;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.Utils;
@ -122,17 +123,17 @@ public class ClassGen {
public void addClassDeclaration(CodeWriter clsCode) {
AccessInfo af = cls.getAccessFlags();
if (af.isInterface()) {
af = af.remove(AccessFlags.ACC_ABSTRACT)
.remove(AccessFlags.ACC_STATIC);
af = af.remove(AccessFlags.ABSTRACT)
.remove(AccessFlags.STATIC);
} else if (af.isEnum()) {
af = af.remove(AccessFlags.ACC_FINAL)
.remove(AccessFlags.ACC_ABSTRACT)
.remove(AccessFlags.ACC_STATIC);
af = af.remove(AccessFlags.FINAL)
.remove(AccessFlags.ABSTRACT)
.remove(AccessFlags.STATIC);
}
// 'static' and 'private' modifier not allowed for top classes (not inner)
if (!cls.getClassInfo().isInner()) {
af = af.remove(AccessFlags.ACC_STATIC).remove(AccessFlags.ACC_PRIVATE);
af = af.remove(AccessFlags.STATIC).remove(AccessFlags.PRIVATE);
}
annotationGen.addForClass(clsCode);
@ -392,15 +393,16 @@ public class ClassGen {
FieldInitAttr fv = f.get(AType.FIELD_INIT);
if (fv != null) {
code.add(" = ");
if (fv.getValue() == null) {
code.add(TypeGen.literalToString(0, f.getType(), cls, fallback));
} else {
if (fv.getValueType() == InitType.CONST) {
annotationGen.encodeValue(code, fv.getValue());
} else if (fv.getValueType() == InitType.INSN) {
InsnGen insnGen = makeInsnGen(fv.getInsnMth());
addInsnBody(insnGen, code, fv.getInsn());
if (fv.getValueType() == InitType.CONST) {
EncodedValue encodedValue = fv.getEncodedValue();
if (encodedValue.getType() == EncodedType.ENCODED_NULL) {
code.add(TypeGen.literalToString(0, f.getType(), cls, fallback));
} else {
annotationGen.encodeValue(cls.root(), code, encodedValue);
}
} else if (fv.getValueType() == InitType.INSN) {
InsnGen insnGen = makeInsnGen(fv.getInsnMth());
addInsnBody(insnGen, code, fv.getInsn());
}
}
code.add(';');
@ -425,7 +427,7 @@ public class ClassGen {
EnumField f = it.next();
code.startLine(f.getField().getAlias());
ConstructorInsn constrInsn = f.getConstrInsn();
MethodNode callMth = cls.dex().resolveMethod(constrInsn.getCallMth());
MethodNode callMth = cls.root().resolveMethod(constrInsn.getCallMth());
int skipCount = getEnumCtrSkipArgsCount(callMth);
if (constrInsn.getArgsCount() > skipCount) {
if (igen == null) {
@ -493,6 +495,10 @@ public class ClassGen {
}
}
public void useClass(CodeWriter code, String rawCls) {
useClass(code, ArgType.object(rawCls));
}
public void useClass(CodeWriter code, ArgType type) {
ArgType outerType = type.getOuterType();
if (outerType != null) {
@ -528,7 +534,7 @@ public class ClassGen {
}
public void useClass(CodeWriter code, ClassInfo classInfo) {
ClassNode classNode = cls.dex().resolveClass(classInfo);
ClassNode classNode = cls.root().resolveClass(classInfo);
if (classNode != null) {
useClass(code, classNode);
} else {
@ -569,11 +575,11 @@ public class ClassGen {
return shortName;
}
// don't add import if class not public (must be accessed using inheritance)
ClassNode classNode = cls.dex().resolveClass(extClsInfo);
ClassNode classNode = cls.root().resolveClass(extClsInfo);
if (classNode != null && !classNode.getAccessFlags().isPublic()) {
return shortName;
}
if (searchCollision(cls.dex(), useCls, extClsInfo)) {
if (searchCollision(cls.root(), useCls, extClsInfo)) {
return fullName;
}
// ignore classes from default package
@ -652,7 +658,7 @@ public class ClassGen {
return false;
}
private static boolean searchCollision(DexNode dex, ClassInfo useCls, ClassInfo searchCls) {
private static boolean searchCollision(RootNode root, ClassInfo useCls, ClassInfo searchCls) {
if (useCls == null) {
return false;
}
@ -660,7 +666,7 @@ public class ClassGen {
if (useCls.getAliasShortName().equals(shortName)) {
return true;
}
ClassNode classNode = dex.resolveClass(useCls);
ClassNode classNode = root.resolveClass(useCls);
if (classNode != null) {
for (ClassNode inner : classNode.getInnerClasses()) {
if (inner.getShortName().equals(shortName)
@ -669,7 +675,7 @@ public class ClassGen {
}
}
}
return searchCollision(dex, useCls.getParentClass(), searchCls);
return searchCollision(root, useCls.getParentClass(), searchCls);
}
private void insertRenameInfo(CodeWriter code, ClassNode cls) {

View File

@ -27,7 +27,7 @@ import jadx.core.dex.instructions.ArithOp;
import jadx.core.dex.instructions.BaseInvokeNode;
import jadx.core.dex.instructions.ConstClassNode;
import jadx.core.dex.instructions.ConstStringNode;
import jadx.core.dex.instructions.FillArrayNode;
import jadx.core.dex.instructions.FillArrayInsn;
import jadx.core.dex.instructions.FilledNewArrayNode;
import jadx.core.dex.instructions.GotoNode;
import jadx.core.dex.instructions.IfNode;
@ -36,7 +36,7 @@ import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.InvokeNode;
import jadx.core.dex.instructions.InvokeType;
import jadx.core.dex.instructions.NewArrayNode;
import jadx.core.dex.instructions.SwitchNode;
import jadx.core.dex.instructions.SwitchInsn;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.CodeVar;
import jadx.core.dex.instructions.args.InsnArg;
@ -152,7 +152,7 @@ public class InsnGen {
private void instanceField(CodeWriter code, FieldInfo field, InsnArg arg) throws CodegenException {
ClassNode pCls = mth.getParentClass();
FieldNode fieldNode = pCls.dex().root().deepResolveField(field);
FieldNode fieldNode = pCls.root().deepResolveField(field);
if (fieldNode != null) {
FieldReplaceAttr replace = fieldNode.get(AType.FIELD_REPLACE);
if (replace != null) {
@ -190,7 +190,7 @@ public class InsnGen {
}
code.add('.');
}
FieldNode fieldNode = clsGen.getClassNode().dex().root().deepResolveField(field);
FieldNode fieldNode = clsGen.getClassNode().root().deepResolveField(field);
if (fieldNode != null) {
code.attachAnnotation(fieldNode);
}
@ -264,7 +264,7 @@ public class InsnGen {
switch (insn.getType()) {
case CONST_STR:
String str = ((ConstStringNode) insn).getString();
code.add(mth.dex().root().getStringUtils().unescapeString(str));
code.add(mth.root().getStringUtils().unescapeString(str));
break;
case CONST_CLASS:
@ -395,7 +395,7 @@ public class InsnGen {
break;
case FILL_ARRAY:
FillArrayNode arrayNode = (FillArrayNode) insn;
FillArrayInsn arrayNode = (FillArrayInsn) insn;
if (fallback) {
String arrStr = arrayNode.dataToString();
addArg(code, insn.getArg(0));
@ -509,15 +509,16 @@ public class InsnGen {
case SWITCH:
fallbackOnlyInsn(insn);
SwitchNode sw = (SwitchNode) insn;
SwitchInsn sw = (SwitchInsn) insn;
code.add("switch(");
addArg(code, insn.getArg(0));
code.add(") {");
code.incIndent();
for (int i = 0; i < sw.getCasesCount(); i++) {
String key = sw.getKeys()[i].toString();
code.startLine("case ").add(key).add(": goto ");
code.add(MethodGen.getLabelName(sw.getTargets()[i])).add(';');
int[] keys = sw.getKeys();
int[] targets = sw.getTargets();
for (int i = 0; i < keys.length; i++) {
code.startLine("case ").add(Integer.toString(keys[i])).add(": goto ");
code.add(MethodGen.getLabelName(targets[i])).add(';');
}
code.startLine("default: goto ");
code.add(MethodGen.getLabelName(sw.getDefaultCaseOffset())).add(';');
@ -541,6 +542,21 @@ public class InsnGen {
code.add(')');
break;
case MOVE_RESULT:
fallbackOnlyInsn(insn);
code.add("move-result");
break;
case FILL_ARRAY_DATA:
fallbackOnlyInsn(insn);
code.add("fill-array " + insn.toString());
break;
case SWITCH_DATA:
fallbackOnlyInsn(insn);
code.add(insn.toString());
break;
default:
throw new CodegenException(mth, "Unknown instruction: " + insn.getType());
}
@ -550,7 +566,7 @@ public class InsnGen {
* In most cases must be combined with new array instructions.
* Use one by one array fill (can be replaced with System.arrayCopy)
*/
private void fillArray(CodeWriter code, FillArrayNode arrayNode) throws CodegenException {
private void fillArray(CodeWriter code, FillArrayInsn arrayNode) throws CodegenException {
code.add("// fill-array-data instruction");
code.startLine();
List<LiteralArg> args = arrayNode.getLiteralArgs(arrayNode.getElementType());
@ -605,7 +621,7 @@ public class InsnGen {
}
private void makeConstructor(ConstructorInsn insn, CodeWriter code) throws CodegenException {
ClassNode cls = mth.dex().resolveClass(insn.getClassType());
ClassNode cls = mth.root().resolveClass(insn.getClassType());
if (cls != null && cls.isAnonymous() && !fallback) {
cls.ensureProcessed();
inlineAnonymousConstructor(code, cls, insn);
@ -639,7 +655,7 @@ public class InsnGen {
code.add('>');
}
}
MethodNode callMth = mth.dex().resolveMethod(insn.getCallMth());
MethodNode callMth = mth.root().resolveMethod(insn.getCallMth());
generateMethodArguments(code, insn, 0, callMth);
}
@ -673,7 +689,7 @@ public class InsnGen {
} else {
useClass(code, parent);
}
MethodNode callMth = mth.dex().resolveMethod(insn.getCallMth());
MethodNode callMth = mth.root().resolveMethod(insn.getCallMth());
generateMethodArguments(code, insn, 0, callMth);
code.add(' ');
new ClassGen(cls, mgen.getClassGen().getParentGen()).addClassBody(code, true);

View File

@ -7,9 +7,10 @@ import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.android.dx.rop.code.AccessFlags;
import jadx.api.plugins.input.data.AccessFlags;
import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.core.Consts;
import jadx.core.Jadx;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.annotations.MethodParameters;
@ -28,7 +29,7 @@ import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.trycatch.CatchAttr;
import jadx.core.dex.visitors.DepthTraversal;
import jadx.core.dex.visitors.FallbackModeVisitor;
import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.utils.CodeGenUtils;
import jadx.core.utils.InsnUtils;
import jadx.core.utils.Utils;
@ -86,12 +87,12 @@ public class MethodGen {
AccessInfo ai = mth.getAccessFlags();
// don't add 'abstract' and 'public' to methods in interface
if (clsAccFlags.isInterface()) {
ai = ai.remove(AccessFlags.ACC_ABSTRACT);
ai = ai.remove(AccessFlags.ACC_PUBLIC);
ai = ai.remove(AccessFlags.ABSTRACT);
ai = ai.remove(AccessFlags.PUBLIC);
}
// don't add 'public' for annotations
if (clsAccFlags.isAnnotation()) {
ai = ai.remove(AccessFlags.ACC_PUBLIC);
ai = ai.remove(AccessFlags.PUBLIC);
}
if (mth.getMethodInfo().hasAlias() && !ai.isConstructor()) {
@ -145,10 +146,10 @@ public class MethodGen {
// add default value if in annotation class
if (mth.getParentClass().getAccessFlags().isAnnotation()) {
Object def = annotationGen.getAnnotationDefaultValue(mth.getName());
EncodedValue def = annotationGen.getAnnotationDefaultValue(mth.getName());
if (def != null) {
code.add(" default ");
annotationGen.encodeValue(code, def);
annotationGen.encodeValue(mth.root(), code, def);
}
}
return true;
@ -269,17 +270,17 @@ public class MethodGen {
}
public void addFallbackMethodCode(CodeWriter code, FallbackOption fallbackOption) {
if (mth.getInstructions() == null) {
// load original instructions
try {
mth.unload();
mth.load();
DepthTraversal.visit(new FallbackModeVisitor(), mth);
} catch (DecodeException e) {
LOG.error("Error reload instructions in fallback mode:", e);
code.startLine("// Can't load method instructions: " + e.getMessage());
return;
// load original instructions
try {
mth.unload();
mth.load();
for (IDexTreeVisitor visitor : Jadx.getFallbackPassesList()) {
DepthTraversal.visit(visitor, mth);
}
} catch (DecodeException e) {
LOG.error("Error reload instructions in fallback mode:", e);
code.startLine("// Can't load method instructions: " + e.getMessage());
return;
}
InsnNode[] insnArr = mth.getInstructions();
if (insnArr == null) {

View File

@ -10,11 +10,12 @@ import org.slf4j.LoggerFactory;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.FieldInitAttr;
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
import jadx.core.dex.attributes.nodes.ForceReturnAttr;
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.instructions.SwitchNode;
import jadx.core.dex.instructions.SwitchInsn;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.CodeVar;
import jadx.core.dex.instructions.args.InsnArg;
@ -26,7 +27,6 @@ import jadx.core.dex.nodes.IBlock;
import jadx.core.dex.nodes.IContainer;
import jadx.core.dex.nodes.IRegion;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.parser.FieldInitAttr;
import jadx.core.dex.regions.Region;
import jadx.core.dex.regions.SwitchRegion;
import jadx.core.dex.regions.SwitchRegion.CaseInfo;
@ -251,7 +251,7 @@ public class RegionGen extends InsnGen {
}
private CodeWriter makeSwitch(SwitchRegion sw, CodeWriter code) throws CodegenException {
SwitchNode insn = (SwitchNode) BlockUtils.getLastInsn(sw.getHeader());
SwitchInsn insn = (SwitchInsn) BlockUtils.getLastInsn(sw.getHeader());
Objects.requireNonNull(insn, "Switch insn not found in header");
InsnArg arg = insn.getArg(0);
code.startLine("switch (");
@ -288,7 +288,7 @@ public class RegionGen extends InsnGen {
// print original value, sometimes replaced with incorrect field
FieldInitAttr valueAttr = fn.get(AType.FIELD_INIT);
if (valueAttr != null) {
Object value = valueAttr.getValue();
Object value = valueAttr.getEncodedValue();
if (value != null && valueAttr.getValueType() == FieldInitAttr.InitType.CONST) {
code.add(" /*").add(value.toString()).add("*/");
}

View File

@ -68,7 +68,7 @@ public class JsonCodeGen {
JsonClass jsonCls = new JsonClass();
jsonCls.setPkg(classInfo.getAliasPkg());
jsonCls.setDex(cls.dex().getDexFile().getName());
jsonCls.setDex(cls.getInputPath().toString());
jsonCls.setName(classInfo.getFullName());
if (classInfo.hasAlias()) {
jsonCls.setAlias(classInfo.getAliasFullName());

View File

@ -12,7 +12,6 @@ import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -26,9 +25,9 @@ import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
public class Deobfuscator {
private static final Logger LOG = LoggerFactory.getLogger(Deobfuscator.class);
@ -42,8 +41,7 @@ public class Deobfuscator {
public static final String KOTLIN_METADATA_CLASSNAME_REGEX = "(L.*;)";
private final JadxArgs args;
@NotNull
private final List<DexNode> dexNodes;
private final RootNode root;
private final DeobfPresets deobfPresets;
private final Map<ClassInfo, DeobfClsInfo> clsMap = new LinkedHashMap<>();
@ -67,9 +65,9 @@ public class Deobfuscator {
private int fldIndex = 0;
private int mthIndex = 0;
public Deobfuscator(JadxArgs args, @NotNull List<DexNode> dexNodes, Path deobfMapFile) {
public Deobfuscator(JadxArgs args, RootNode root, Path deobfMapFile) {
this.args = args;
this.dexNodes = dexNodes;
this.root = root;
this.minLength = args.getDeobfuscationMinLength();
this.maxLength = args.getDeobfuscationMaxLength();
@ -109,15 +107,11 @@ public class Deobfuscator {
}
private void preProcess() {
for (DexNode dexNode : dexNodes) {
for (ClassNode cls : dexNode.getClasses()) {
Collections.addAll(reservedClsNames, cls.getPackage().split("\\."));
}
for (ClassNode cls : root.getClasses()) {
Collections.addAll(reservedClsNames, cls.getPackage().split("\\."));
}
for (DexNode dexNode : dexNodes) {
for (ClassNode cls : dexNode.getClasses()) {
preProcessClass(cls);
}
for (ClassNode cls : root.getClasses()) {
preProcessClass(cls);
}
}
@ -126,10 +120,8 @@ public class Deobfuscator {
if (DEBUG) {
dumpAlias();
}
for (DexNode dexNode : dexNodes) {
for (ClassNode cls : dexNode.getClasses()) {
processClass(cls);
}
for (ClassNode cls : root.getClasses()) {
processClass(cls);
}
postProcess();
}
@ -212,14 +204,14 @@ public class Deobfuscator {
if (added) {
ArgType superClass = cls.getSuperClass();
if (superClass != null) {
ClassNode superNode = cls.dex().resolveClass(superClass);
ClassNode superNode = cls.root().resolveClass(superClass);
if (superNode != null) {
collectClassHierarchy(superNode, collected);
}
}
for (ArgType argType : cls.getInterfaces()) {
ClassNode interfaceNode = cls.dex().resolveClass(argType);
ClassNode interfaceNode = cls.root().resolveClass(argType);
if (interfaceNode != null) {
collectClassHierarchy(interfaceNode, collected);
}
@ -473,7 +465,7 @@ public class Deobfuscator {
return null;
}
}
ClassNode otherCls = cls.root().searchClassByName(cls.getPackage() + '.' + name);
ClassNode otherCls = cls.root().resolveClass(cls.getPackage() + '.' + name);
if (otherCls != null) {
return null;
}
@ -584,10 +576,8 @@ public class Deobfuscator {
}
private void dumpAlias() {
for (DexNode dexNode : dexNodes) {
for (ClassNode cls : dexNode.getClasses()) {
dumpClassAlias(cls);
}
for (ClassNode cls : root.getClasses()) {
dumpClassAlias(cls);
}
}

View File

@ -27,7 +27,6 @@ import jadx.core.dex.attributes.nodes.RenameReasonAttr;
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
import jadx.core.dex.attributes.nodes.SourceFileAttr;
import jadx.core.dex.nodes.IMethodDetails;
import jadx.core.dex.nodes.parser.FieldInitAttr;
import jadx.core.dex.trycatch.CatchAttr;
import jadx.core.dex.trycatch.ExcHandlerAttr;
import jadx.core.dex.trycatch.SplitterBlockAttr;

View File

@ -2,7 +2,7 @@ package jadx.core.dex.attributes;
import java.util.List;
import jadx.core.dex.attributes.annotations.Annotation;
import jadx.api.plugins.input.data.annotations.IAnnotation;
public abstract class AttrNode implements IAttributeNode {
@ -64,7 +64,7 @@ public abstract class AttrNode implements IAttributeNode {
}
@Override
public Annotation getAnnotation(String cls) {
public IAnnotation getAnnotation(String cls) {
return storage.getAnnotation(cls);
}

View File

@ -8,7 +8,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import jadx.core.dex.attributes.annotations.Annotation;
import jadx.api.plugins.input.data.annotations.IAnnotation;
import jadx.core.dex.attributes.annotations.AnnotationsList;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException;
@ -70,7 +70,7 @@ public class AttributeStorage {
return (T) attributes.get(type);
}
public Annotation getAnnotation(String cls) {
public IAnnotation getAnnotation(String cls) {
AnnotationsList aList = get(AType.ANNOTATION_LIST);
return aList == null ? null : aList.get(cls);
}

View File

@ -3,7 +3,7 @@ package jadx.core.dex.attributes;
import java.util.Collections;
import java.util.List;
import jadx.core.dex.attributes.annotations.Annotation;
import jadx.api.plugins.input.data.annotations.IAnnotation;
public final class EmptyAttrStorage extends AttributeStorage {
@ -23,7 +23,7 @@ public final class EmptyAttrStorage extends AttributeStorage {
}
@Override
public Annotation getAnnotation(String cls) {
public IAnnotation getAnnotation(String cls) {
return null;
}

View File

@ -1,18 +1,16 @@
package jadx.core.dex.nodes.parser;
package jadx.core.dex.attributes;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
public class FieldInitAttr implements IAttribute {
public static final FieldInitAttr NULL_VALUE = constValue(null);
public static final FieldInitAttr NULL_VALUE = constValue(EncodedValue.NULL);
public enum InitType {
CONST,
INSN
}
private final Object value;
@ -25,7 +23,7 @@ public class FieldInitAttr implements IAttribute {
this.insnMth = insnMth;
}
public static FieldInitAttr constValue(Object value) {
public static FieldInitAttr constValue(EncodedValue value) {
return new FieldInitAttr(InitType.CONST, value, null);
}
@ -33,8 +31,8 @@ public class FieldInitAttr implements IAttribute {
return new FieldInitAttr(InitType.INSN, insn, mth);
}
public Object getValue() {
return value;
public EncodedValue getEncodedValue() {
return (EncodedValue) value;
}
public InsnNode getInsn() {

View File

@ -2,7 +2,7 @@ package jadx.core.dex.attributes;
import java.util.List;
import jadx.core.dex.attributes.annotations.Annotation;
import jadx.api.plugins.input.data.annotations.IAnnotation;
public interface IAttributeNode {
@ -20,7 +20,7 @@ public interface IAttributeNode {
<T extends IAttribute> T get(AType<T> type);
Annotation getAnnotation(String cls);
IAnnotation getAnnotation(String cls);
<T> List<T> getAll(AType<AttrList<T>> type);

View File

@ -1,47 +0,0 @@
package jadx.core.dex.attributes.annotations;
import java.util.Map;
import jadx.core.dex.instructions.args.ArgType;
public class Annotation {
public enum Visibility {
BUILD, RUNTIME, SYSTEM
}
private final Visibility visibility;
private final ArgType atype;
private final Map<String, Object> values;
public Annotation(Visibility visibility, ArgType type, Map<String, Object> values) {
this.visibility = visibility;
this.atype = type;
this.values = values;
}
public Visibility getVisibility() {
return visibility;
}
public ArgType getType() {
return atype;
}
public String getAnnotationClass() {
return atype.getObject();
}
public Map<String, Object> getValues() {
return values;
}
public Object getDefaultValue() {
return values.get("value");
}
@Override
public String toString() {
return "Annotation[" + visibility + ", " + atype + ", " + values + ']';
}
}

View File

@ -1,33 +1,50 @@
package jadx.core.dex.attributes.annotations;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jetbrains.annotations.Nullable;
import jadx.api.plugins.input.data.annotations.IAnnotation;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.nodes.ICodeNode;
import jadx.core.utils.Utils;
public class AnnotationsList implements IAttribute {
public static final AnnotationsList EMPTY = new AnnotationsList(Collections.emptyList());
private final Map<String, Annotation> map;
public AnnotationsList(List<Annotation> anList) {
map = new HashMap<>(anList.size());
for (Annotation a : anList) {
map.put(a.getAnnotationClass(), a);
public static void attach(ICodeNode node, List<IAnnotation> annotationList) {
AnnotationsList attrList = pack(annotationList);
if (attrList != null) {
node.addAttr(attrList);
}
}
public Annotation get(String className) {
@Nullable
public static AnnotationsList pack(List<IAnnotation> annotationList) {
if (annotationList.isEmpty()) {
return null;
}
Map<String, IAnnotation> annMap = new HashMap<>(annotationList.size());
for (IAnnotation ann : annotationList) {
annMap.put(ann.getAnnotationClass(), ann);
}
return new AnnotationsList(annMap);
}
private final Map<String, IAnnotation> map;
public AnnotationsList(Map<String, IAnnotation> map) {
this.map = map;
}
public IAnnotation get(String className) {
return map.get(className);
}
public Collection<Annotation> getAll() {
public Collection<IAnnotation> getAll() {
return map.values();
}

View File

@ -3,16 +3,29 @@ package jadx.core.dex.attributes.annotations;
import java.util.ArrayList;
import java.util.List;
import jadx.api.plugins.input.data.annotations.IAnnotation;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.nodes.ICodeNode;
import jadx.core.utils.Utils;
public class MethodParameters implements IAttribute {
public static void attach(ICodeNode node, List<List<IAnnotation>> annotationRefList) {
if (annotationRefList.isEmpty()) {
return;
}
List<AnnotationsList> list = new ArrayList<>(annotationRefList.size());
for (List<IAnnotation> annList : annotationRefList) {
list.add(AnnotationsList.pack(annList));
}
node.addAttr(new MethodParameters(list));
}
private final List<AnnotationsList> paramList;
public MethodParameters(int paramCount) {
paramList = new ArrayList<>(paramCount);
public MethodParameters(List<AnnotationsList> paramsList) {
this.paramList = paramsList;
}
public List<AnnotationsList> getParamList() {

View File

@ -2,21 +2,21 @@ package jadx.core.dex.attributes.nodes;
import java.util.List;
import jadx.api.plugins.input.data.ILocalVar;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.visitors.debuginfo.LocalVar;
import jadx.core.utils.Utils;
import static jadx.core.codegen.CodeWriter.NL;
public class LocalVarsDebugInfoAttr implements IAttribute {
private final List<LocalVar> localVars;
private final List<ILocalVar> localVars;
public LocalVarsDebugInfoAttr(List<LocalVar> localVars) {
public LocalVarsDebugInfoAttr(List<ILocalVar> localVars) {
this.localVars = localVars;
}
public List<LocalVar> getLocalVars() {
public List<ILocalVar> getLocalVars() {
return localVars;
}

View File

@ -5,17 +5,12 @@ import java.util.Objects;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.visitors.debuginfo.LocalVar;
public class RegDebugInfoAttr implements IAttribute {
private final ArgType type;
private final String name;
public RegDebugInfoAttr(LocalVar var) {
this(var.getType(), var.getName());
}
public RegDebugInfoAttr(ArgType type, String name) {
this.type = type;
this.name = name;

View File

@ -1,13 +1,12 @@
package jadx.core.dex.info;
import com.android.dx.rop.code.AccessFlags;
import jadx.api.plugins.input.data.AccessFlags;
import jadx.core.Consts;
import jadx.core.utils.exceptions.JadxRuntimeException;
public class AccessInfo {
public static final int VISIBILITY_FLAGS = AccessFlags.ACC_PUBLIC | AccessFlags.ACC_PROTECTED | AccessFlags.ACC_PRIVATE;
public static final int VISIBILITY_FLAGS = AccessFlags.PUBLIC | AccessFlags.PROTECTED | AccessFlags.PRIVATE;
private final int accFlags;
public enum AFType {
@ -53,15 +52,15 @@ public class AccessInfo {
}
public boolean isPublic() {
return (accFlags & AccessFlags.ACC_PUBLIC) != 0;
return (accFlags & AccessFlags.PUBLIC) != 0;
}
public boolean isProtected() {
return (accFlags & AccessFlags.ACC_PROTECTED) != 0;
return (accFlags & AccessFlags.PROTECTED) != 0;
}
public boolean isPrivate() {
return (accFlags & AccessFlags.ACC_PRIVATE) != 0;
return (accFlags & AccessFlags.PRIVATE) != 0;
}
public boolean isPackagePrivate() {
@ -69,59 +68,59 @@ public class AccessInfo {
}
public boolean isAbstract() {
return (accFlags & AccessFlags.ACC_ABSTRACT) != 0;
return (accFlags & AccessFlags.ABSTRACT) != 0;
}
public boolean isInterface() {
return (accFlags & AccessFlags.ACC_INTERFACE) != 0;
return (accFlags & AccessFlags.INTERFACE) != 0;
}
public boolean isAnnotation() {
return (accFlags & AccessFlags.ACC_ANNOTATION) != 0;
return (accFlags & AccessFlags.ANNOTATION) != 0;
}
public boolean isNative() {
return (accFlags & AccessFlags.ACC_NATIVE) != 0;
return (accFlags & AccessFlags.NATIVE) != 0;
}
public boolean isStatic() {
return (accFlags & AccessFlags.ACC_STATIC) != 0;
return (accFlags & AccessFlags.STATIC) != 0;
}
public boolean isFinal() {
return (accFlags & AccessFlags.ACC_FINAL) != 0;
return (accFlags & AccessFlags.FINAL) != 0;
}
public boolean isConstructor() {
return (accFlags & AccessFlags.ACC_CONSTRUCTOR) != 0;
return (accFlags & AccessFlags.CONSTRUCTOR) != 0;
}
public boolean isEnum() {
return (accFlags & AccessFlags.ACC_ENUM) != 0;
return (accFlags & AccessFlags.ENUM) != 0;
}
public boolean isSynthetic() {
return (accFlags & AccessFlags.ACC_SYNTHETIC) != 0;
return (accFlags & AccessFlags.SYNTHETIC) != 0;
}
public boolean isBridge() {
return (accFlags & AccessFlags.ACC_BRIDGE) != 0;
return (accFlags & AccessFlags.BRIDGE) != 0;
}
public boolean isVarArgs() {
return (accFlags & AccessFlags.ACC_VARARGS) != 0;
return (accFlags & AccessFlags.VARARGS) != 0;
}
public boolean isSynchronized() {
return (accFlags & (AccessFlags.ACC_SYNCHRONIZED | AccessFlags.ACC_DECLARED_SYNCHRONIZED)) != 0;
return (accFlags & (AccessFlags.SYNCHRONIZED | AccessFlags.DECLARED_SYNCHRONIZED)) != 0;
}
public boolean isTransient() {
return (accFlags & AccessFlags.ACC_TRANSIENT) != 0;
return (accFlags & AccessFlags.TRANSIENT) != 0;
}
public boolean isVolatile() {
return (accFlags & AccessFlags.ACC_VOLATILE) != 0;
return (accFlags & AccessFlags.VOLATILE) != 0;
}
public AFType getType() {
@ -174,14 +173,14 @@ public class AccessInfo {
break;
case CLASS:
if ((accFlags & AccessFlags.ACC_STRICT) != 0) {
if ((accFlags & AccessFlags.STRICT) != 0) {
code.append("strict ");
}
if (Consts.DEBUG) {
if ((accFlags & AccessFlags.ACC_SUPER) != 0) {
if ((accFlags & AccessFlags.SUPER) != 0) {
code.append("/* super */ ");
}
if ((accFlags & AccessFlags.ACC_ENUM) != 0) {
if ((accFlags & AccessFlags.ENUM) != 0) {
code.append("/* enum */ ");
}
}
@ -209,25 +208,12 @@ public class AccessInfo {
throw new JadxRuntimeException("Unknown visibility flags: " + getVisibility());
}
public String rawString() {
switch (type) {
case CLASS:
return AccessFlags.classString(accFlags);
case FIELD:
return AccessFlags.fieldString(accFlags);
case METHOD:
return AccessFlags.methodString(accFlags);
default:
return "?";
}
}
public int rawValue() {
return accFlags;
}
@Override
public String toString() {
return "AccessInfo: " + type + " 0x" + Integer.toHexString(accFlags) + " (" + rawString() + ')';
return "AccessInfo: " + type + " 0x" + Integer.toHexString(accFlags) + " (" + makeString() + ')';
}
}

View File

@ -7,7 +7,6 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.exceptions.JadxRuntimeException;
@ -37,13 +36,6 @@ public final class ClassInfo implements Comparable<ClassInfo> {
return root.getInfoStorage().putCls(newClsInfo);
}
public static ClassInfo fromDex(DexNode dex, int clsIndex) {
if (clsIndex == DexNode.NO_INDEX) {
throw new JadxRuntimeException("NO_INDEX for class");
}
return fromType(dex.root(), dex.getType(clsIndex));
}
public static ClassInfo fromName(RootNode root, String clsName) {
return fromType(root, ArgType.object(clsName));
}

View File

@ -10,12 +10,12 @@ import org.jetbrains.annotations.Nullable;
import jadx.api.JadxArgs;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.FieldInitAttr;
import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.instructions.args.PrimitiveType;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.parser.FieldInitAttr;
import jadx.core.dex.nodes.RootNode;
public class ConstStorage {
@ -72,10 +72,10 @@ public class ConstStorage {
if (accFlags.isStatic() && accFlags.isFinal()) {
FieldInitAttr fv = f.get(AType.FIELD_INIT);
if (fv != null
&& fv.getValue() != null
&& fv.getEncodedValue() != null
&& fv.getValueType() == FieldInitAttr.InitType.CONST
&& fv != FieldInitAttr.NULL_VALUE) {
addConstField(cls, f, fv.getValue(), accFlags.isPublic());
addConstField(cls, f, fv.getEncodedValue().getValue(), accFlags.isPublic());
}
}
}
@ -98,9 +98,9 @@ public class ConstStorage {
if (!replaceEnabled) {
return null;
}
DexNode dex = cls.dex();
RootNode root = cls.root();
if (value instanceof Integer) {
FieldNode rField = getResourceField((Integer) value, dex);
FieldNode rField = getResourceField((Integer) value, root);
if (rField != null) {
return rField;
}
@ -125,7 +125,7 @@ public class ConstStorage {
if (parentClass == null) {
break;
}
current = dex.resolveClass(parentClass);
current = root.resolveClass(parentClass);
}
if (searchGlobal) {
return globalValues.get(value);
@ -134,12 +134,12 @@ public class ConstStorage {
}
@Nullable
private FieldNode getResourceField(Integer value, DexNode dex) {
private FieldNode getResourceField(Integer value, RootNode root) {
String str = resourcesNames.get(value);
if (str == null) {
return null;
}
ClassNode appResClass = dex.root().getAppResClass();
ClassNode appResClass = root.getAppResClass();
if (appResClass == null) {
return null;
}

View File

@ -2,11 +2,10 @@ package jadx.core.dex.info;
import java.util.Objects;
import com.android.dex.FieldId;
import jadx.api.plugins.input.data.IFieldData;
import jadx.core.codegen.TypeGen;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.RootNode;
public final class FieldInfo {
@ -22,17 +21,15 @@ public final class FieldInfo {
this.alias = name;
}
public static FieldInfo from(DexNode dex, ClassInfo declClass, String name, ArgType type) {
public static FieldInfo from(RootNode root, ClassInfo declClass, String name, ArgType type) {
FieldInfo field = new FieldInfo(declClass, name, type);
return dex.root().getInfoStorage().getField(field);
return root.getInfoStorage().getField(field);
}
public static FieldInfo fromDex(DexNode dex, int index) {
FieldId field = dex.getFieldId(index);
return from(dex,
ClassInfo.fromDex(dex, field.getDeclaringClassIndex()),
dex.getString(field.getNameIndex()),
dex.getType(field.getTypeIndex()));
public static FieldInfo fromData(RootNode root, IFieldData fieldData) {
ClassInfo declClass = ClassInfo.fromName(root, fieldData.getParentClassType());
FieldInfo field = new FieldInfo(declClass, fieldData.getName(), ArgType.parse(fieldData.getType()));
return root.getInfoStorage().getField(field);
}
public String getName() {

View File

@ -4,14 +4,11 @@ import java.util.HashMap;
import java.util.Map;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.DexNode;
import jadx.core.utils.exceptions.JadxRuntimeException;
public class InfoStorage {
private final Map<ArgType, ClassInfo> classes = new HashMap<>();
private final Map<FieldInfo, FieldInfo> fields = new HashMap<>();
private final Map<Integer, MethodInfo> methods = new HashMap<>();
// use only one MethodInfo instance
private final Map<MethodInfo, MethodInfo> uniqueMethods = new HashMap<>();
@ -26,27 +23,6 @@ public class InfoStorage {
}
}
private static int generateMethodLookupId(DexNode dex, int mthId) {
return dex.getDexId() << 16 | mthId;
}
public MethodInfo getMethod(DexNode dex, int mtdId) {
synchronized (methods) {
return methods.get(generateMethodLookupId(dex, mtdId));
}
}
public MethodInfo putMethod(DexNode dex, int mthId, MethodInfo methodInfo) {
synchronized (methods) {
MethodInfo uniqueMethodInfo = putMethod(methodInfo);
MethodInfo prev = methods.put(generateMethodLookupId(dex, mthId), uniqueMethodInfo);
if (prev != null && prev != uniqueMethodInfo) {
throw new JadxRuntimeException("Method lookup id collision: " + methodInfo + ", " + prev + ", " + uniqueMethodInfo);
}
return uniqueMethodInfo;
}
}
public MethodInfo putMethod(MethodInfo newMth) {
synchronized (uniqueMethods) {
MethodInfo prev = uniqueMethods.get(newMth);

View File

@ -5,12 +5,9 @@ import java.util.Objects;
import org.jetbrains.annotations.Nullable;
import com.android.dex.MethodId;
import com.android.dex.ProtoId;
import jadx.api.plugins.input.data.IMethodData;
import jadx.core.codegen.TypeGen;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.Utils;
@ -34,20 +31,13 @@ public final class MethodInfo implements Comparable<MethodInfo> {
this.shortId = makeShortId(name, argTypes, retType);
}
public static MethodInfo fromDex(DexNode dex, int mthIndex) {
MethodInfo storageMth = dex.root().getInfoStorage().getMethod(dex, mthIndex);
if (storageMth != null) {
return storageMth;
}
MethodId mthId = dex.getMethodId(mthIndex);
String mthName = dex.getString(mthId.getNameIndex());
ClassInfo parentClass = ClassInfo.fromDex(dex, mthId.getDeclaringClassIndex());
ProtoId proto = dex.getProtoId(mthId.getProtoIndex());
ArgType returnType = dex.getType(proto.getReturnTypeIndex());
List<ArgType> args = dex.readParamList(proto.getParametersOffset());
MethodInfo newMth = new MethodInfo(parentClass, mthName, args, returnType);
return dex.root().getInfoStorage().putMethod(dex, mthIndex, newMth);
public static MethodInfo fromData(RootNode root, IMethodData methodData) {
ArgType parentClsType = ArgType.parse(methodData.getParentClassType());
ClassInfo parentClass = ClassInfo.fromType(root, parentClsType);
ArgType returnType = ArgType.parse(methodData.getReturnType());
List<ArgType> args = Utils.collectionMap(methodData.getArgTypes(), ArgType::parse);
MethodInfo newMth = new MethodInfo(parentClass, methodData.getName(), args, returnType);
return root.getInfoStorage().putMethod(newMth);
}
public static MethodInfo fromDetails(RootNode rootNode, ClassInfo declClass, String name, List<ArgType> args, ArgType retType) {

View File

@ -1,7 +1,6 @@
package jadx.core.dex.instructions;
import com.android.dx.io.instructions.DecodedInstruction;
import jadx.api.plugins.input.insns.InsnData;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
@ -14,12 +13,12 @@ public class ArithNode extends InsnNode {
private final ArithOp op;
public ArithNode(DecodedInstruction insn, ArithOp op, ArgType type, boolean literal) {
public ArithNode(InsnData insn, ArithOp op, ArgType type, boolean literal) {
super(InsnType.ARITH, 2);
this.op = op;
setResult(InsnArg.reg(insn, 0, type));
int rc = insn.getRegisterCount();
int rc = insn.getRegsCount();
if (literal) {
if (rc == 1) {
// self

View File

@ -4,8 +4,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import com.android.dx.io.instructions.FillArrayDataPayloadDecodedInstruction;
import jadx.api.plugins.input.insns.custom.IArrayPayload;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.LiteralArg;
@ -13,7 +12,7 @@ import jadx.core.dex.instructions.args.PrimitiveType;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.exceptions.JadxRuntimeException;
public final class FillArrayNode extends InsnNode {
public final class FillArrayData extends InsnNode {
private static final ArgType ONE_BYTE_TYPE = ArgType.unknown(PrimitiveType.BOOLEAN, PrimitiveType.BYTE);
private static final ArgType TWO_BYTES_TYPE = ArgType.unknown(PrimitiveType.SHORT, PrimitiveType.CHAR);
@ -22,21 +21,22 @@ public final class FillArrayNode extends InsnNode {
private final Object data;
private final int size;
private final int elemSize;
private ArgType elemType;
public FillArrayNode(int resReg, FillArrayDataPayloadDecodedInstruction payload) {
this(payload.getData(), payload.getSize(), getElementType(payload.getElementWidthUnit()));
addArg(InsnArg.reg(resReg, ArgType.array(elemType)));
public FillArrayData(IArrayPayload payload) {
this(payload.getData(), payload.getSize(), payload.getElementSize());
}
private FillArrayNode(Object data, int size, ArgType elemType) {
super(InsnType.FILL_ARRAY, 1);
private FillArrayData(Object data, int size, int elemSize) {
super(InsnType.FILL_ARRAY_DATA, 0);
this.data = data;
this.size = size;
this.elemType = elemType;
this.elemSize = elemSize;
this.elemType = getElementType(elemSize);
}
private static ArgType getElementType(short elementWidthUnit) {
private static ArgType getElementType(int elementWidthUnit) {
switch (elementWidthUnit) {
case 1:
return ONE_BYTE_TYPE;
@ -66,24 +66,29 @@ public final class FillArrayNode extends InsnNode {
public List<LiteralArg> getLiteralArgs(ArgType type) {
List<LiteralArg> list = new ArrayList<>(size);
Object array = data;
if (array instanceof int[]) {
for (int b : (int[]) array) {
list.add(InsnArg.lit(b, type));
}
} else if (array instanceof byte[]) {
for (byte b : (byte[]) array) {
list.add(InsnArg.lit(b, type));
}
} else if (array instanceof short[]) {
for (short b : (short[]) array) {
list.add(InsnArg.lit(b, type));
}
} else if (array instanceof long[]) {
for (long b : (long[]) array) {
list.add(InsnArg.lit(b, type));
}
} else {
throw new JadxRuntimeException("Unknown type: " + data.getClass() + ", expected: " + type);
switch (elemSize) {
case 1:
for (byte b : (byte[]) array) {
list.add(InsnArg.lit(b, type));
}
break;
case 2:
for (short b : (short[]) array) {
list.add(InsnArg.lit(b, type));
}
break;
case 4:
for (int b : (int[]) array) {
list.add(InsnArg.lit(b, type));
}
break;
case 8:
for (long b : (long[]) array) {
list.add(InsnArg.lit(b, type));
}
break;
default:
throw new JadxRuntimeException("Unknown type: " + data.getClass() + ", expected: " + type);
}
return list;
}
@ -93,32 +98,33 @@ public final class FillArrayNode extends InsnNode {
if (this == obj) {
return true;
}
if (!(obj instanceof FillArrayNode) || !super.isSame(obj)) {
if (!(obj instanceof FillArrayData) || !super.isSame(obj)) {
return false;
}
FillArrayNode other = (FillArrayNode) obj;
FillArrayData other = (FillArrayData) obj;
return elemType.equals(other.elemType) && data == other.data;
}
@Override
public InsnNode copy() {
return copyCommonParams(new FillArrayNode(data, size, elemType));
FillArrayData copy = new FillArrayData(data, size, elemSize);
copy.elemType = this.elemType;
return copyCommonParams(copy);
}
public String dataToString() {
if (data instanceof int[]) {
return Arrays.toString((int[]) data);
switch (elemSize) {
case 1:
return Arrays.toString((byte[]) data);
case 2:
return Arrays.toString((short[]) data);
case 4:
return Arrays.toString((int[]) data);
case 8:
return Arrays.toString((long[]) data);
default:
return "?";
}
if (data instanceof short[]) {
return Arrays.toString((short[]) data);
}
if (data instanceof byte[]) {
return Arrays.toString((byte[]) data);
}
if (data instanceof long[]) {
return Arrays.toString((long[]) data);
}
return "?";
}
@Override

View File

@ -0,0 +1,67 @@
package jadx.core.dex.instructions;
import java.util.List;
import java.util.Objects;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.nodes.InsnNode;
public final class FillArrayInsn extends InsnNode {
private final int target;
private FillArrayData arrayData;
public FillArrayInsn(InsnArg arg, int target) {
super(InsnType.FILL_ARRAY, 1);
this.target = target;
addArg(arg);
}
public int getTarget() {
return target;
}
public void setArrayData(FillArrayData arrayData) {
this.arrayData = arrayData;
}
@Override
public boolean isSame(InsnNode obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof FillArrayInsn) || !super.isSame(obj)) {
return false;
}
FillArrayInsn other = (FillArrayInsn) obj;
return Objects.equals(arrayData, other.arrayData);
}
@Override
public InsnNode copy() {
FillArrayInsn copy = new FillArrayInsn(getArg(0), target);
return copyCommonParams(copy);
}
@Override
public String toString() {
return super.toString() + ", data: " + arrayData;
}
public int getSize() {
return arrayData.getSize();
}
public ArgType getElementType() {
return arrayData.getElementType();
}
public List<LiteralArg> getLiteralArgs(ArgType elType) {
return arrayData.getLiteralArgs(elType);
}
public String dataToString() {
return Objects.toString(arrayData);
}
}

View File

@ -2,8 +2,7 @@ package jadx.core.dex.instructions;
import java.util.List;
import com.android.dx.io.instructions.DecodedInstruction;
import jadx.api.plugins.input.insns.InsnData;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.PrimitiveType;
@ -21,12 +20,12 @@ public class IfNode extends GotoNode {
private BlockNode thenBlock;
private BlockNode elseBlock;
public IfNode(DecodedInstruction insn, IfOp op) {
public IfNode(InsnData insn, IfOp op) {
super(InsnType.IF, insn.getTarget(), 2);
this.op = op;
ArgType argType = narrowTypeByOp(op);
addArg(InsnArg.reg(insn, 0, argType));
if (insn.getRegisterCount() == 1) {
if (insn.getRegsCount() == 1) {
addArg(InsnArg.lit(0, argType));
} else {
addArg(InsnArg.reg(insn, 1, argType));

View File

@ -1,21 +1,13 @@
package jadx.core.dex.instructions;
import java.io.EOFException;
import java.util.function.Predicate;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.android.dex.Code;
import com.android.dx.io.OpcodeInfo;
import com.android.dx.io.Opcodes;
import com.android.dx.io.instructions.DecodedInstruction;
import com.android.dx.io.instructions.FillArrayDataPayloadDecodedInstruction;
import com.android.dx.io.instructions.PackedSwitchPayloadDecodedInstruction;
import com.android.dx.io.instructions.ShortArrayCodeInput;
import com.android.dx.io.instructions.SparseSwitchPayloadDecodedInstruction;
import jadx.api.plugins.input.data.ICodeReader;
import jadx.api.plugins.input.insns.InsnData;
import jadx.api.plugins.input.insns.custom.IArrayPayload;
import jadx.api.plugins.input.insns.custom.ISwitchPayload;
import jadx.core.Consts;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo;
@ -23,642 +15,497 @@ import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.InsnUtils;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.exceptions.DecodeException;
public class InsnDecoder {
private static final Logger LOG = LoggerFactory.getLogger(InsnDecoder.class);
private final MethodNode method;
private final DexNode dex;
private DecodedInstruction[] insnArr;
private final RootNode root;
public InsnDecoder(MethodNode mthNode) {
this.method = mthNode;
this.dex = method.dex();
this.root = method.root();
}
public void decodeInsns(Code mthCode) throws DecodeException {
short[] encodedInstructions = mthCode.getInstructions();
int size = encodedInstructions.length;
DecodedInstruction[] decoded = new DecodedInstruction[size];
ShortArrayCodeInput in = new ShortArrayCodeInput(encodedInstructions);
try {
while (in.hasMore()) {
decoded[in.cursor()] = decodeRawInsn(in);
public InsnNode[] process(ICodeReader codeReader) {
InsnNode[] instructions = new InsnNode[codeReader.getInsnsCount()];
codeReader.visitInstructions(rawInsn -> {
int offset = rawInsn.getOffset();
InsnNode insn;
try {
rawInsn.decode();
insn = decode(rawInsn, offset);
insn.setOffset(offset);
} catch (Exception e) {
LOG.error("Failed to decode insn: " + rawInsn + ", method: " + method, e);
insn = new InsnNode(InsnType.NOP, 0);
}
} catch (Exception e) {
throw new DecodeException(method, e.getMessage(), e);
}
insnArr = decoded;
}
private DecodedInstruction decodeRawInsn(ShortArrayCodeInput in) throws EOFException {
int opcodeUnit = in.read();
int opcode = Opcodes.extractOpcodeFromUnit(opcodeUnit);
OpcodeInfo.Info opcodeInfo;
try {
opcodeInfo = OpcodeInfo.get(opcode);
} catch (IllegalArgumentException e) {
LOG.warn("Ignore decode error: '{}', replace with NOP instruction", e.getMessage());
opcodeInfo = OpcodeInfo.NOP;
}
return opcodeInfo.getFormat().decode(opcodeUnit, in);
}
public InsnNode[] process() throws DecodeException {
InsnNode[] instructions = new InsnNode[insnArr.length];
for (int i = 0; i < insnArr.length; i++) {
DecodedInstruction rawInsn = insnArr[i];
if (rawInsn != null) {
InsnNode insn = decode(rawInsn, i);
insn.setOffset(i);
instructions[i] = insn;
} else {
instructions[i] = null;
}
}
insnArr = null;
instructions[offset] = insn;
});
return instructions;
}
@NotNull
private InsnNode decode(DecodedInstruction insn, int offset) throws DecodeException {
private InsnNode decode(InsnData insn, int offset) throws DecodeException {
switch (insn.getOpcode()) {
case Opcodes.NOP:
case Opcodes.PACKED_SWITCH_PAYLOAD:
case Opcodes.SPARSE_SWITCH_PAYLOAD:
case Opcodes.FILL_ARRAY_DATA_PAYLOAD:
case NOP:
return new InsnNode(InsnType.NOP, 0);
// move-result will be process in invoke and filled-new-array instructions
case Opcodes.MOVE_RESULT:
case Opcodes.MOVE_RESULT_WIDE:
case Opcodes.MOVE_RESULT_OBJECT:
return new InsnNode(InsnType.NOP, 0);
case MOVE_RESULT:
return insn(InsnType.MOVE_RESULT, InsnArg.reg(insn, 0, ArgType.UNKNOWN));
case Opcodes.CONST:
case Opcodes.CONST_4:
case Opcodes.CONST_16:
case Opcodes.CONST_HIGH16:
case CONST:
LiteralArg narrowLitArg = InsnArg.lit(insn, ArgType.NARROW);
return insn(InsnType.CONST, InsnArg.reg(insn, 0, narrowLitArg.getType()), narrowLitArg);
case Opcodes.CONST_WIDE:
case Opcodes.CONST_WIDE_16:
case Opcodes.CONST_WIDE_32:
case Opcodes.CONST_WIDE_HIGH16:
case CONST_WIDE:
LiteralArg wideLitArg = InsnArg.lit(insn, ArgType.WIDE);
return insn(InsnType.CONST, InsnArg.reg(insn, 0, wideLitArg.getType()), wideLitArg);
case Opcodes.CONST_STRING:
case Opcodes.CONST_STRING_JUMBO:
InsnNode constStrInsn = new ConstStringNode(dex.getString(insn.getIndex()));
case CONST_STRING:
InsnNode constStrInsn = new ConstStringNode(insn.getIndexAsString());
constStrInsn.setResult(InsnArg.reg(insn, 0, ArgType.STRING));
return constStrInsn;
case Opcodes.CONST_CLASS: {
ArgType clsType = dex.getType(insn.getIndex());
case CONST_CLASS: {
ArgType clsType = ArgType.parse(insn.getIndexAsType());
InsnNode constClsInsn = new ConstClassNode(clsType);
constClsInsn.setResult(
InsnArg.reg(insn, 0, ArgType.generic(Consts.CLASS_CLASS, clsType)));
constClsInsn.setResult(InsnArg.reg(insn, 0, ArgType.generic(Consts.CLASS_CLASS, clsType)));
return constClsInsn;
}
case Opcodes.MOVE:
case Opcodes.MOVE_16:
case Opcodes.MOVE_FROM16:
case MOVE:
return insn(InsnType.MOVE,
InsnArg.reg(insn, 0, ArgType.NARROW),
InsnArg.reg(insn, 1, ArgType.NARROW));
case Opcodes.MOVE_WIDE:
case Opcodes.MOVE_WIDE_16:
case Opcodes.MOVE_WIDE_FROM16:
case MOVE_WIDE:
return insn(InsnType.MOVE,
InsnArg.reg(insn, 0, ArgType.WIDE),
InsnArg.reg(insn, 1, ArgType.WIDE));
case Opcodes.MOVE_OBJECT:
case Opcodes.MOVE_OBJECT_16:
case Opcodes.MOVE_OBJECT_FROM16:
case MOVE_OBJECT:
return insn(InsnType.MOVE,
InsnArg.reg(insn, 0, ArgType.UNKNOWN_OBJECT),
InsnArg.reg(insn, 1, ArgType.UNKNOWN_OBJECT));
case Opcodes.ADD_INT:
case Opcodes.ADD_INT_2ADDR:
case ADD_INT:
return arith(insn, ArithOp.ADD, ArgType.INT);
case Opcodes.ADD_DOUBLE:
case Opcodes.ADD_DOUBLE_2ADDR:
case ADD_DOUBLE:
return arith(insn, ArithOp.ADD, ArgType.DOUBLE);
case Opcodes.ADD_FLOAT:
case Opcodes.ADD_FLOAT_2ADDR:
case ADD_FLOAT:
return arith(insn, ArithOp.ADD, ArgType.FLOAT);
case Opcodes.ADD_LONG:
case Opcodes.ADD_LONG_2ADDR:
case ADD_LONG:
return arith(insn, ArithOp.ADD, ArgType.LONG);
case Opcodes.ADD_INT_LIT8:
case Opcodes.ADD_INT_LIT16:
case ADD_INT_LIT:
return arithLit(insn, ArithOp.ADD, ArgType.INT);
case Opcodes.SUB_INT:
case Opcodes.SUB_INT_2ADDR:
case SUB_INT:
return arith(insn, ArithOp.SUB, ArgType.INT);
case Opcodes.RSUB_INT_LIT8:
case Opcodes.RSUB_INT: // LIT16
case RSUB_INT:
return new ArithNode(ArithOp.SUB,
InsnArg.reg(insn, 0, ArgType.INT),
InsnArg.lit(insn, ArgType.INT),
InsnArg.reg(insn, 1, ArgType.INT));
case Opcodes.SUB_LONG:
case Opcodes.SUB_LONG_2ADDR:
case SUB_LONG:
return arith(insn, ArithOp.SUB, ArgType.LONG);
case Opcodes.SUB_FLOAT:
case Opcodes.SUB_FLOAT_2ADDR:
case SUB_FLOAT:
return arith(insn, ArithOp.SUB, ArgType.FLOAT);
case Opcodes.SUB_DOUBLE:
case Opcodes.SUB_DOUBLE_2ADDR:
case SUB_DOUBLE:
return arith(insn, ArithOp.SUB, ArgType.DOUBLE);
case Opcodes.MUL_INT:
case Opcodes.MUL_INT_2ADDR:
case MUL_INT:
return arith(insn, ArithOp.MUL, ArgType.INT);
case Opcodes.MUL_DOUBLE:
case Opcodes.MUL_DOUBLE_2ADDR:
case MUL_DOUBLE:
return arith(insn, ArithOp.MUL, ArgType.DOUBLE);
case Opcodes.MUL_FLOAT:
case Opcodes.MUL_FLOAT_2ADDR:
case MUL_FLOAT:
return arith(insn, ArithOp.MUL, ArgType.FLOAT);
case Opcodes.MUL_LONG:
case Opcodes.MUL_LONG_2ADDR:
case MUL_LONG:
return arith(insn, ArithOp.MUL, ArgType.LONG);
case Opcodes.MUL_INT_LIT8:
case Opcodes.MUL_INT_LIT16:
case MUL_INT_LIT:
return arithLit(insn, ArithOp.MUL, ArgType.INT);
case Opcodes.DIV_INT:
case Opcodes.DIV_INT_2ADDR:
case DIV_INT:
return arith(insn, ArithOp.DIV, ArgType.INT);
case Opcodes.REM_INT:
case Opcodes.REM_INT_2ADDR:
case REM_INT:
return arith(insn, ArithOp.REM, ArgType.INT);
case Opcodes.REM_LONG:
case Opcodes.REM_LONG_2ADDR:
case REM_LONG:
return arith(insn, ArithOp.REM, ArgType.LONG);
case Opcodes.REM_FLOAT:
case Opcodes.REM_FLOAT_2ADDR:
case REM_FLOAT:
return arith(insn, ArithOp.REM, ArgType.FLOAT);
case Opcodes.REM_DOUBLE:
case Opcodes.REM_DOUBLE_2ADDR:
case REM_DOUBLE:
return arith(insn, ArithOp.REM, ArgType.DOUBLE);
case Opcodes.DIV_DOUBLE:
case Opcodes.DIV_DOUBLE_2ADDR:
case DIV_DOUBLE:
return arith(insn, ArithOp.DIV, ArgType.DOUBLE);
case Opcodes.DIV_FLOAT:
case Opcodes.DIV_FLOAT_2ADDR:
case DIV_FLOAT:
return arith(insn, ArithOp.DIV, ArgType.FLOAT);
case Opcodes.DIV_LONG:
case Opcodes.DIV_LONG_2ADDR:
case DIV_LONG:
return arith(insn, ArithOp.DIV, ArgType.LONG);
case Opcodes.DIV_INT_LIT8:
case Opcodes.DIV_INT_LIT16:
case DIV_INT_LIT:
return arithLit(insn, ArithOp.DIV, ArgType.INT);
case Opcodes.REM_INT_LIT8:
case Opcodes.REM_INT_LIT16:
case REM_INT_LIT:
return arithLit(insn, ArithOp.REM, ArgType.INT);
case Opcodes.AND_INT:
case Opcodes.AND_INT_2ADDR:
case AND_INT:
return arith(insn, ArithOp.AND, ArgType.INT);
case Opcodes.AND_INT_LIT8:
case Opcodes.AND_INT_LIT16:
case AND_INT_LIT:
return arithLit(insn, ArithOp.AND, ArgType.INT);
case Opcodes.XOR_INT_LIT8:
case Opcodes.XOR_INT_LIT16:
case XOR_INT_LIT:
return arithLit(insn, ArithOp.XOR, ArgType.INT);
case Opcodes.AND_LONG:
case Opcodes.AND_LONG_2ADDR:
case AND_LONG:
return arith(insn, ArithOp.AND, ArgType.LONG);
case Opcodes.OR_INT:
case Opcodes.OR_INT_2ADDR:
case OR_INT:
return arith(insn, ArithOp.OR, ArgType.INT);
case Opcodes.OR_INT_LIT8:
case Opcodes.OR_INT_LIT16:
case OR_INT_LIT:
return arithLit(insn, ArithOp.OR, ArgType.INT);
case Opcodes.XOR_INT:
case Opcodes.XOR_INT_2ADDR:
case XOR_INT:
return arith(insn, ArithOp.XOR, ArgType.INT);
case Opcodes.OR_LONG:
case Opcodes.OR_LONG_2ADDR:
case OR_LONG:
return arith(insn, ArithOp.OR, ArgType.LONG);
case Opcodes.XOR_LONG:
case Opcodes.XOR_LONG_2ADDR:
case XOR_LONG:
return arith(insn, ArithOp.XOR, ArgType.LONG);
case Opcodes.USHR_INT:
case Opcodes.USHR_INT_2ADDR:
case USHR_INT:
return arith(insn, ArithOp.USHR, ArgType.INT);
case Opcodes.USHR_LONG:
case Opcodes.USHR_LONG_2ADDR:
case USHR_LONG:
return arith(insn, ArithOp.USHR, ArgType.LONG);
case Opcodes.SHL_INT:
case Opcodes.SHL_INT_2ADDR:
case SHL_INT:
return arith(insn, ArithOp.SHL, ArgType.INT);
case Opcodes.SHL_LONG:
case Opcodes.SHL_LONG_2ADDR:
case SHL_LONG:
return arith(insn, ArithOp.SHL, ArgType.LONG);
case Opcodes.SHR_INT:
case Opcodes.SHR_INT_2ADDR:
case SHR_INT:
return arith(insn, ArithOp.SHR, ArgType.INT);
case Opcodes.SHR_LONG:
case Opcodes.SHR_LONG_2ADDR:
case SHR_LONG:
return arith(insn, ArithOp.SHR, ArgType.LONG);
case Opcodes.SHL_INT_LIT8:
case SHL_INT_LIT:
return arithLit(insn, ArithOp.SHL, ArgType.INT);
case Opcodes.SHR_INT_LIT8:
case SHR_INT_LIT:
return arithLit(insn, ArithOp.SHR, ArgType.INT);
case Opcodes.USHR_INT_LIT8:
case USHR_INT_LIT:
return arithLit(insn, ArithOp.USHR, ArgType.INT);
case Opcodes.NEG_INT:
case NEG_INT:
return neg(insn, ArgType.INT);
case Opcodes.NEG_LONG:
case NEG_LONG:
return neg(insn, ArgType.LONG);
case Opcodes.NEG_FLOAT:
case NEG_FLOAT:
return neg(insn, ArgType.FLOAT);
case Opcodes.NEG_DOUBLE:
case NEG_DOUBLE:
return neg(insn, ArgType.DOUBLE);
case Opcodes.NOT_INT:
case NOT_INT:
return not(insn, ArgType.INT);
case Opcodes.NOT_LONG:
case NOT_LONG:
return not(insn, ArgType.LONG);
case Opcodes.INT_TO_BYTE:
case INT_TO_BYTE:
return cast(insn, ArgType.INT, ArgType.BYTE);
case Opcodes.INT_TO_CHAR:
case INT_TO_CHAR:
return cast(insn, ArgType.INT, ArgType.CHAR);
case Opcodes.INT_TO_SHORT:
case INT_TO_SHORT:
return cast(insn, ArgType.INT, ArgType.SHORT);
case Opcodes.INT_TO_FLOAT:
case INT_TO_FLOAT:
return cast(insn, ArgType.INT, ArgType.FLOAT);
case Opcodes.INT_TO_DOUBLE:
case INT_TO_DOUBLE:
return cast(insn, ArgType.INT, ArgType.DOUBLE);
case Opcodes.INT_TO_LONG:
case INT_TO_LONG:
return cast(insn, ArgType.INT, ArgType.LONG);
case Opcodes.FLOAT_TO_INT:
case FLOAT_TO_INT:
return cast(insn, ArgType.FLOAT, ArgType.INT);
case Opcodes.FLOAT_TO_DOUBLE:
case FLOAT_TO_DOUBLE:
return cast(insn, ArgType.FLOAT, ArgType.DOUBLE);
case Opcodes.FLOAT_TO_LONG:
case FLOAT_TO_LONG:
return cast(insn, ArgType.FLOAT, ArgType.LONG);
case Opcodes.DOUBLE_TO_INT:
case DOUBLE_TO_INT:
return cast(insn, ArgType.DOUBLE, ArgType.INT);
case Opcodes.DOUBLE_TO_FLOAT:
case DOUBLE_TO_FLOAT:
return cast(insn, ArgType.DOUBLE, ArgType.FLOAT);
case Opcodes.DOUBLE_TO_LONG:
case DOUBLE_TO_LONG:
return cast(insn, ArgType.DOUBLE, ArgType.LONG);
case Opcodes.LONG_TO_INT:
case LONG_TO_INT:
return cast(insn, ArgType.LONG, ArgType.INT);
case Opcodes.LONG_TO_FLOAT:
case LONG_TO_FLOAT:
return cast(insn, ArgType.LONG, ArgType.FLOAT);
case Opcodes.LONG_TO_DOUBLE:
case LONG_TO_DOUBLE:
return cast(insn, ArgType.LONG, ArgType.DOUBLE);
case Opcodes.IF_EQ:
case Opcodes.IF_EQZ:
case IF_EQ:
case IF_EQZ:
return new IfNode(insn, IfOp.EQ);
case Opcodes.IF_NE:
case Opcodes.IF_NEZ:
case IF_NE:
case IF_NEZ:
return new IfNode(insn, IfOp.NE);
case Opcodes.IF_GT:
case Opcodes.IF_GTZ:
case IF_GT:
case IF_GTZ:
return new IfNode(insn, IfOp.GT);
case Opcodes.IF_GE:
case Opcodes.IF_GEZ:
case IF_GE:
case IF_GEZ:
return new IfNode(insn, IfOp.GE);
case Opcodes.IF_LT:
case Opcodes.IF_LTZ:
case IF_LT:
case IF_LTZ:
return new IfNode(insn, IfOp.LT);
case Opcodes.IF_LE:
case Opcodes.IF_LEZ:
case IF_LE:
case IF_LEZ:
return new IfNode(insn, IfOp.LE);
case Opcodes.CMP_LONG:
case CMP_LONG:
return cmp(insn, InsnType.CMP_L, ArgType.LONG);
case Opcodes.CMPL_FLOAT:
case CMPL_FLOAT:
return cmp(insn, InsnType.CMP_L, ArgType.FLOAT);
case Opcodes.CMPL_DOUBLE:
case CMPL_DOUBLE:
return cmp(insn, InsnType.CMP_L, ArgType.DOUBLE);
case Opcodes.CMPG_FLOAT:
case CMPG_FLOAT:
return cmp(insn, InsnType.CMP_G, ArgType.FLOAT);
case Opcodes.CMPG_DOUBLE:
case CMPG_DOUBLE:
return cmp(insn, InsnType.CMP_G, ArgType.DOUBLE);
case Opcodes.GOTO:
case Opcodes.GOTO_16:
case Opcodes.GOTO_32:
case GOTO:
return new GotoNode(insn.getTarget());
case Opcodes.THROW:
case THROW:
return insn(InsnType.THROW, null, InsnArg.reg(insn, 0, ArgType.THROWABLE));
case Opcodes.MOVE_EXCEPTION:
case MOVE_EXCEPTION:
return insn(InsnType.MOVE_EXCEPTION, InsnArg.reg(insn, 0, ArgType.UNKNOWN_OBJECT_NO_ARRAY));
case Opcodes.RETURN_VOID:
case RETURN_VOID:
return new InsnNode(InsnType.RETURN, 0);
case Opcodes.RETURN:
case Opcodes.RETURN_WIDE:
case Opcodes.RETURN_OBJECT:
case RETURN:
return insn(InsnType.RETURN,
null,
InsnArg.reg(insn, 0, method.getReturnType()));
case Opcodes.INSTANCE_OF:
InsnNode instInsn = new IndexInsnNode(InsnType.INSTANCE_OF, dex.getType(insn.getIndex()), 1);
case INSTANCE_OF:
InsnNode instInsn = new IndexInsnNode(InsnType.INSTANCE_OF, ArgType.parse(insn.getIndexAsType()), 1);
instInsn.setResult(InsnArg.reg(insn, 0, ArgType.BOOLEAN));
instInsn.addArg(InsnArg.reg(insn, 1, ArgType.UNKNOWN_OBJECT));
return instInsn;
case Opcodes.CHECK_CAST:
ArgType castType = dex.getType(insn.getIndex());
case CHECK_CAST:
ArgType castType = ArgType.parse(insn.getIndexAsType());
InsnNode checkCastInsn = new IndexInsnNode(InsnType.CHECK_CAST, castType, 1);
checkCastInsn.setResult(InsnArg.reg(insn, 0, castType));
checkCastInsn.addArg(InsnArg.reg(insn, 0, ArgType.UNKNOWN_OBJECT));
return checkCastInsn;
case Opcodes.IGET:
case Opcodes.IGET_BOOLEAN:
case Opcodes.IGET_BYTE:
case Opcodes.IGET_CHAR:
case Opcodes.IGET_SHORT:
case Opcodes.IGET_WIDE:
case Opcodes.IGET_OBJECT:
FieldInfo igetFld = FieldInfo.fromDex(dex, insn.getIndex());
case IGET:
FieldInfo igetFld = FieldInfo.fromData(root, insn.getIndexAsField());
InsnNode igetInsn = new IndexInsnNode(InsnType.IGET, igetFld, 1);
igetInsn.setResult(InsnArg.reg(insn, 0, tryResolveFieldType(igetFld)));
igetInsn.addArg(InsnArg.reg(insn, 1, igetFld.getDeclClass().getType()));
return igetInsn;
case Opcodes.IPUT:
case Opcodes.IPUT_BOOLEAN:
case Opcodes.IPUT_BYTE:
case Opcodes.IPUT_CHAR:
case Opcodes.IPUT_SHORT:
case Opcodes.IPUT_WIDE:
case Opcodes.IPUT_OBJECT:
FieldInfo iputFld = FieldInfo.fromDex(dex, insn.getIndex());
case IPUT:
FieldInfo iputFld = FieldInfo.fromData(root, insn.getIndexAsField());
InsnNode iputInsn = new IndexInsnNode(InsnType.IPUT, iputFld, 2);
iputInsn.addArg(InsnArg.reg(insn, 0, tryResolveFieldType(iputFld)));
iputInsn.addArg(InsnArg.reg(insn, 1, iputFld.getDeclClass().getType()));
return iputInsn;
case Opcodes.SGET:
case Opcodes.SGET_BOOLEAN:
case Opcodes.SGET_BYTE:
case Opcodes.SGET_CHAR:
case Opcodes.SGET_SHORT:
case Opcodes.SGET_WIDE:
case Opcodes.SGET_OBJECT:
FieldInfo sgetFld = FieldInfo.fromDex(dex, insn.getIndex());
case SGET:
FieldInfo sgetFld = FieldInfo.fromData(root, insn.getIndexAsField());
InsnNode sgetInsn = new IndexInsnNode(InsnType.SGET, sgetFld, 0);
sgetInsn.setResult(InsnArg.reg(insn, 0, tryResolveFieldType(sgetFld)));
return sgetInsn;
case Opcodes.SPUT:
case Opcodes.SPUT_BOOLEAN:
case Opcodes.SPUT_BYTE:
case Opcodes.SPUT_CHAR:
case Opcodes.SPUT_SHORT:
case Opcodes.SPUT_WIDE:
case Opcodes.SPUT_OBJECT:
FieldInfo sputFld = FieldInfo.fromDex(dex, insn.getIndex());
case SPUT:
FieldInfo sputFld = FieldInfo.fromData(root, insn.getIndexAsField());
InsnNode sputInsn = new IndexInsnNode(InsnType.SPUT, sputFld, 1);
sputInsn.addArg(InsnArg.reg(insn, 0, tryResolveFieldType(sputFld)));
return sputInsn;
case Opcodes.ARRAY_LENGTH:
case ARRAY_LENGTH:
InsnNode arrLenInsn = new InsnNode(InsnType.ARRAY_LENGTH, 1);
arrLenInsn.setResult(InsnArg.reg(insn, 0, ArgType.INT));
arrLenInsn.addArg(InsnArg.reg(insn, 1, ArgType.array(ArgType.UNKNOWN)));
return arrLenInsn;
case Opcodes.AGET:
case AGET:
return arrayGet(insn, ArgType.INT_FLOAT);
case Opcodes.AGET_BOOLEAN:
case AGET_BOOLEAN:
return arrayGet(insn, ArgType.BOOLEAN);
case Opcodes.AGET_BYTE:
case AGET_BYTE:
return arrayGet(insn, ArgType.BYTE);
case Opcodes.AGET_CHAR:
case AGET_CHAR:
return arrayGet(insn, ArgType.CHAR);
case Opcodes.AGET_SHORT:
case AGET_SHORT:
return arrayGet(insn, ArgType.SHORT);
case Opcodes.AGET_WIDE:
case AGET_WIDE:
return arrayGet(insn, ArgType.WIDE);
case Opcodes.AGET_OBJECT:
case AGET_OBJECT:
return arrayGet(insn, ArgType.UNKNOWN_OBJECT);
case Opcodes.APUT:
case APUT:
return arrayPut(insn, ArgType.INT_FLOAT);
case Opcodes.APUT_BOOLEAN:
case APUT_BOOLEAN:
return arrayPut(insn, ArgType.BOOLEAN);
case Opcodes.APUT_BYTE:
case APUT_BYTE:
return arrayPut(insn, ArgType.BYTE);
case Opcodes.APUT_CHAR:
case APUT_CHAR:
return arrayPut(insn, ArgType.CHAR);
case Opcodes.APUT_SHORT:
case APUT_SHORT:
return arrayPut(insn, ArgType.SHORT);
case Opcodes.APUT_WIDE:
case APUT_WIDE:
return arrayPut(insn, ArgType.WIDE);
case Opcodes.APUT_OBJECT:
case APUT_OBJECT:
return arrayPut(insn, ArgType.UNKNOWN_OBJECT);
case Opcodes.INVOKE_STATIC:
case INVOKE_STATIC:
return invoke(insn, offset, InvokeType.STATIC, false);
case Opcodes.INVOKE_STATIC_RANGE:
case INVOKE_STATIC_RANGE:
return invoke(insn, offset, InvokeType.STATIC, true);
case Opcodes.INVOKE_DIRECT:
case INVOKE_DIRECT:
return invoke(insn, offset, InvokeType.DIRECT, false);
case Opcodes.INVOKE_INTERFACE:
case INVOKE_INTERFACE:
return invoke(insn, offset, InvokeType.INTERFACE, false);
case Opcodes.INVOKE_SUPER:
case INVOKE_SUPER:
return invoke(insn, offset, InvokeType.SUPER, false);
case Opcodes.INVOKE_VIRTUAL:
case INVOKE_VIRTUAL:
return invoke(insn, offset, InvokeType.VIRTUAL, false);
case Opcodes.INVOKE_DIRECT_RANGE:
case INVOKE_DIRECT_RANGE:
return invoke(insn, offset, InvokeType.DIRECT, true);
case Opcodes.INVOKE_INTERFACE_RANGE:
case INVOKE_INTERFACE_RANGE:
return invoke(insn, offset, InvokeType.INTERFACE, true);
case Opcodes.INVOKE_SUPER_RANGE:
case INVOKE_SUPER_RANGE:
return invoke(insn, offset, InvokeType.SUPER, true);
case Opcodes.INVOKE_VIRTUAL_RANGE:
case INVOKE_VIRTUAL_RANGE:
return invoke(insn, offset, InvokeType.VIRTUAL, true);
case Opcodes.NEW_INSTANCE:
ArgType clsType = dex.getType(insn.getIndex());
case NEW_INSTANCE:
ArgType clsType = ArgType.parse(insn.getIndexAsType());
IndexInsnNode newInstInsn = new IndexInsnNode(InsnType.NEW_INSTANCE, clsType, 0);
newInstInsn.setResult(InsnArg.reg(insn, 0, clsType));
return newInstInsn;
case Opcodes.NEW_ARRAY:
ArgType arrType = dex.getType(insn.getIndex());
case NEW_ARRAY:
ArgType arrType = ArgType.parse(insn.getIndexAsType());
return new NewArrayNode(arrType,
InsnArg.reg(insn, 0, arrType),
InsnArg.typeImmutableReg(insn, 1, ArgType.INT));
case Opcodes.FILL_ARRAY_DATA:
return fillArray(insn);
case FILL_ARRAY_DATA:
return new FillArrayInsn(InsnArg.reg(insn, 0, ArgType.UNKNOWN_ARRAY), insn.getTarget());
case FILL_ARRAY_DATA_PAYLOAD:
return new FillArrayData(((IArrayPayload) insn.getPayload()));
case Opcodes.FILLED_NEW_ARRAY:
return filledNewArray(insn, offset, false);
case Opcodes.FILLED_NEW_ARRAY_RANGE:
return filledNewArray(insn, offset, true);
case FILLED_NEW_ARRAY:
return filledNewArray(insn, false);
case FILLED_NEW_ARRAY_RANGE:
return filledNewArray(insn, true);
case Opcodes.PACKED_SWITCH:
return decodeSwitch(insn, offset, true);
case PACKED_SWITCH:
return new SwitchInsn(InsnArg.reg(insn, 0, ArgType.UNKNOWN), insn.getTarget(), true);
case SPARSE_SWITCH:
return new SwitchInsn(InsnArg.reg(insn, 0, ArgType.UNKNOWN), insn.getTarget(), false);
case Opcodes.SPARSE_SWITCH:
return decodeSwitch(insn, offset, false);
case PACKED_SWITCH_PAYLOAD:
case SPARSE_SWITCH_PAYLOAD:
return new SwitchData(((ISwitchPayload) insn.getPayload()));
case Opcodes.MONITOR_ENTER:
case MONITOR_ENTER:
return insn(InsnType.MONITOR_ENTER,
null,
InsnArg.reg(insn, 0, ArgType.UNKNOWN_OBJECT));
case Opcodes.MONITOR_EXIT:
case MONITOR_EXIT:
return insn(InsnType.MONITOR_EXIT,
null,
InsnArg.reg(insn, 0, ArgType.UNKNOWN_OBJECT));
default:
throw new DecodeException("Unknown instruction: '" + OpcodeInfo.getName(insn.getOpcode()) + '\'');
throw new DecodeException("Unknown instruction: '" + insn + '\'');
}
}
private ArgType tryResolveFieldType(FieldInfo igetFld) {
FieldNode fieldNode = dex.resolveField(igetFld);
FieldNode fieldNode = root.resolveField(igetFld);
if (fieldNode != null) {
return fieldNode.getType();
}
return igetFld.getType();
}
private InsnNode decodeSwitch(DecodedInstruction insn, int offset, boolean packed) {
int payloadOffset = insn.getTarget();
DecodedInstruction payload = getInsnByOffsetSkipNop(insnArr, payloadOffset);
Object[] keys;
int[] targets;
if (packed) {
PackedSwitchPayloadDecodedInstruction ps = (PackedSwitchPayloadDecodedInstruction) payload;
targets = ps.getTargets();
keys = new Object[targets.length];
int k = ps.getFirstKey();
for (int i = 0; i < keys.length; i++) {
keys[i] = k++;
}
} else {
SparseSwitchPayloadDecodedInstruction ss = (SparseSwitchPayloadDecodedInstruction) payload;
targets = ss.getTargets();
keys = new Object[targets.length];
for (int i = 0; i < keys.length; i++) {
keys[i] = ss.getKeys()[i];
}
}
// convert from relative to absolute offsets
for (int i = 0; i < targets.length; i++) {
targets[i] = targets[i] - payloadOffset + offset;
}
int nextOffset = getNextInsnOffset(insnArr, offset);
return new SwitchNode(InsnArg.reg(insn, 0, ArgType.NARROW), keys, targets, nextOffset, packed);
}
private InsnNode fillArray(DecodedInstruction insn) {
DecodedInstruction payload = getInsnByOffsetSkipNop(insnArr, insn.getTarget());
return new FillArrayNode(insn.getA(), (FillArrayDataPayloadDecodedInstruction) payload);
}
private InsnNode filledNewArray(DecodedInstruction insn, int offset, boolean isRange) {
int resReg = getMoveResultRegister(insnArr, offset);
ArgType arrType = dex.getType(insn.getIndex());
private InsnNode filledNewArray(InsnData insn, boolean isRange) {
ArgType arrType = ArgType.parse(insn.getIndexAsType());
ArgType elType = arrType.getArrayElement();
boolean typeImmutable = elType.isPrimitive();
int regsCount = insn.getRegisterCount();
int regsCount = insn.getRegsCount();
InsnArg[] regs = new InsnArg[regsCount];
if (isRange) {
int r = insn.getA();
int r = insn.getReg(0);
for (int i = 0; i < regsCount; i++) {
regs[i] = InsnArg.reg(r, elType, typeImmutable);
r++;
}
} else {
for (int i = 0; i < regsCount; i++) {
int regNum = InsnUtils.getArg(insn, i);
int regNum = insn.getReg(i);
regs[i] = InsnArg.reg(regNum, elType, typeImmutable);
}
}
InsnNode node = new FilledNewArrayNode(elType, regs.length);
node.setResult(resReg == -1 ? null : InsnArg.reg(resReg, arrType));
// node.setResult(resReg == -1 ? null : InsnArg.reg(resReg, arrType));
for (InsnArg arg : regs) {
node.addArg(arg);
}
return node;
}
private InsnNode cmp(DecodedInstruction insn, InsnType itype, ArgType argType) {
private InsnNode cmp(InsnData insn, InsnType itype, ArgType argType) {
InsnNode inode = new InsnNode(itype, 2);
inode.setResult(InsnArg.reg(insn, 0, ArgType.INT));
inode.addArg(InsnArg.reg(insn, 1, argType));
@ -666,20 +513,19 @@ public class InsnDecoder {
return inode;
}
private InsnNode cast(DecodedInstruction insn, ArgType from, ArgType to) {
private InsnNode cast(InsnData insn, ArgType from, ArgType to) {
InsnNode inode = new IndexInsnNode(InsnType.CAST, to, 1);
inode.setResult(InsnArg.reg(insn, 0, to));
inode.addArg(InsnArg.reg(insn, 1, from));
return inode;
}
private InsnNode invoke(DecodedInstruction insn, int offset, InvokeType type, boolean isRange) {
int resReg = getMoveResultRegister(insnArr, offset);
MethodInfo mth = MethodInfo.fromDex(dex, insn.getIndex());
return new InvokeNode(mth, insn, type, isRange, resReg);
private InsnNode invoke(InsnData insn, int offset, InvokeType type, boolean isRange) {
MethodInfo mth = MethodInfo.fromData(root, insn.getIndexAsMethod());
return new InvokeNode(mth, insn, type, isRange);
}
private InsnNode arrayGet(DecodedInstruction insn, ArgType argType) {
private InsnNode arrayGet(InsnData insn, ArgType argType) {
InsnNode inode = new InsnNode(InsnType.AGET, 2);
inode.setResult(InsnArg.typeImmutableIfKnownReg(insn, 0, argType));
inode.addArg(InsnArg.typeImmutableIfKnownReg(insn, 1, ArgType.array(argType)));
@ -687,7 +533,7 @@ public class InsnDecoder {
return inode;
}
private InsnNode arrayPut(DecodedInstruction insn, ArgType argType) {
private InsnNode arrayPut(InsnData insn, ArgType argType) {
InsnNode inode = new InsnNode(InsnType.APUT, 3);
inode.addArg(InsnArg.typeImmutableIfKnownReg(insn, 1, ArgType.array(argType)));
inode.addArg(InsnArg.reg(insn, 2, ArgType.NARROW_INTEGRAL));
@ -695,11 +541,11 @@ public class InsnDecoder {
return inode;
}
private InsnNode arith(DecodedInstruction insn, ArithOp op, ArgType type) {
private InsnNode arith(InsnData insn, ArithOp op, ArgType type) {
return new ArithNode(insn, op, fixTypeForBitOps(op, type), false);
}
private InsnNode arithLit(DecodedInstruction insn, ArithOp op, ArgType type) {
private InsnNode arithLit(InsnData insn, ArithOp op, ArgType type) {
return new ArithNode(insn, op, fixTypeForBitOps(op, type), true);
}
@ -711,14 +557,14 @@ public class InsnDecoder {
return type;
}
private InsnNode neg(DecodedInstruction insn, ArgType type) {
private InsnNode neg(InsnData insn, ArgType type) {
InsnNode inode = new InsnNode(InsnType.NEG, 1);
inode.setResult(InsnArg.reg(insn, 0, type));
inode.addArg(InsnArg.reg(insn, 1, type));
return inode;
}
private InsnNode not(DecodedInstruction insn, ArgType type) {
private InsnNode not(InsnData insn, ArgType type) {
InsnNode inode = new InsnNode(InsnType.NOT, 1);
inode.setResult(InsnArg.reg(insn, 0, type));
inode.addArg(InsnArg.reg(insn, 1, type));
@ -737,54 +583,4 @@ public class InsnDecoder {
node.addArg(arg);
return node;
}
private int getMoveResultRegister(DecodedInstruction[] insnArr, int offset) {
int nextOffset = getNextInsnOffsetSkipNop(insnArr, offset);
if (nextOffset >= 0) {
DecodedInstruction next = insnArr[nextOffset];
int opc = next.getOpcode();
if (opc == Opcodes.MOVE_RESULT
|| opc == Opcodes.MOVE_RESULT_WIDE
|| opc == Opcodes.MOVE_RESULT_OBJECT) {
return next.getA();
}
}
return -1;
}
private static DecodedInstruction getInsnByOffsetSkipNop(DecodedInstruction[] insnArr, int offset) {
DecodedInstruction payload = insnArr[offset];
if (payload.getOpcode() == Opcodes.NOP) {
return insnArr[getNextInsnOffsetSkipNop(insnArr, offset)];
}
return payload;
}
public static int getNextInsnOffset(DecodedInstruction[] insnArr, int offset) {
return getNextInsnOffset(insnArr, offset, null);
}
public static int getNextInsnOffsetSkipNop(DecodedInstruction[] insnArr, int offset) {
return getNextInsnOffset(insnArr, offset, i -> i.getOpcode() == Opcodes.NOP);
}
public static int getNextInsnOffset(InsnNode[] insnArr, int offset) {
return getNextInsnOffset(insnArr, offset, null);
}
public static <T> int getNextInsnOffset(T[] insnArr, int offset, Predicate<T> skip) {
int i = offset + 1;
while (i < insnArr.length) {
T insn = insnArr[i];
if (insn == null || (skip != null && skip.test(insn))) {
i++;
} else {
break;
}
}
if (i >= insnArr.length) {
return -1;
}
return i;
}
}

View File

@ -23,6 +23,7 @@ public enum InsnType {
CMP_G,
IF,
SWITCH,
SWITCH_DATA,
MONITOR_ENTER,
MONITOR_EXIT,
@ -32,6 +33,7 @@ public enum InsnType {
ARRAY_LENGTH,
FILL_ARRAY,
FILL_ARRAY_DATA,
FILLED_NEW_ARRAY,
AGET,
@ -47,6 +49,7 @@ public enum InsnType {
SPUT,
INVOKE,
MOVE_RESULT,
// *** Additional instructions ***
@ -67,8 +70,5 @@ public enum InsnType {
PHI,
// fake insn to keep arguments which will be used in regions codegen
REGION_ARG,
// TODO: now multidimensional arrays created using Array.newInstance function
NEW_MULTIDIM_ARRAY
REGION_ARG
}

View File

@ -2,37 +2,31 @@ package jadx.core.dex.instructions;
import org.jetbrains.annotations.Nullable;
import com.android.dx.io.instructions.DecodedInstruction;
import jadx.api.plugins.input.insns.InsnData;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.InsnUtils;
public final class InvokeNode extends BaseInvokeNode {
private final InvokeType type;
private final MethodInfo mth;
public InvokeNode(MethodInfo mth, DecodedInstruction insn, InvokeType type, boolean isRange, int resReg) {
public InvokeNode(MethodInfo mth, InsnData insn, InvokeType type, boolean isRange) {
super(InsnType.INVOKE, mth.getArgsCount() + (type == InvokeType.STATIC ? 0 : 1));
this.mth = mth;
this.type = type;
if (resReg >= 0) {
setResult(InsnArg.reg(resReg, mth.getReturnType()));
}
int k = isRange ? insn.getA() : 0;
int k = isRange ? insn.getReg(0) : 0;
if (type != InvokeType.STATIC) {
int r = isRange ? k : InsnUtils.getArg(insn, k);
int r = isRange ? k : insn.getReg(k);
addReg(r, mth.getDeclClass().getType());
k++;
}
for (ArgType arg : mth.getArgumentsTypes()) {
addReg(isRange ? k : InsnUtils.getArg(insn, k), arg);
addReg(isRange ? k : insn.getReg(k), arg);
k += arg.getRegCount();
}
}

View File

@ -0,0 +1,41 @@
package jadx.core.dex.instructions;
import jadx.api.plugins.input.insns.custom.ISwitchPayload;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.InsnUtils;
public class SwitchData extends InsnNode {
private final int size;
private final int[] keys;
private final int[] targets;
public SwitchData(ISwitchPayload payload) {
super(InsnType.SWITCH_DATA, 0);
this.size = payload.getSize();
this.keys = payload.getKeys();
this.targets = payload.getTargets();
}
public int getSize() {
return size;
}
public int[] getKeys() {
return keys;
}
public int[] getTargets() {
return targets;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("switch-data {");
for (int i = 0; i < size; i++) {
sb.append(keys[i]).append("->").append(InsnUtils.formatOffset(targets[i])).append(", ");
}
sb.append('}');
return sb.toString();
}
}

View File

@ -0,0 +1,193 @@
package jadx.core.dex.instructions;
import java.util.List;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import jadx.core.codegen.CodeWriter;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.InsnUtils;
import jadx.core.utils.exceptions.JadxRuntimeException;
import static jadx.core.utils.BlockUtils.getBlockByOffset;
public class SwitchInsn extends TargetInsnNode {
private final int dataTarget;
private final boolean packed; // type of switch insn, if true can contain filler keys
@Nullable
private SwitchData switchData;
private int def; // next instruction
private Object[] modifiedKeys;
private BlockNode[] targetBlocks;
private BlockNode defTargetBlock;
public SwitchInsn(InsnArg arg, int dataTarget, boolean packed) {
super(InsnType.SWITCH, 1);
addArg(arg);
this.dataTarget = dataTarget;
this.packed = packed;
}
public void attachSwitchData(SwitchData data, int def) {
this.switchData = data;
this.def = def;
// fix targets
int switchOffset = getOffset();
int size = data.getSize();
int[] targets = data.getTargets();
for (int i = 0; i < size; i++) {
targets[i] += switchOffset;
}
}
@Override
public void initBlocks(BlockNode curBlock) {
if (switchData == null) {
throw new JadxRuntimeException("Switch data not yet attached");
}
List<BlockNode> successors = curBlock.getSuccessors();
int[] targets = switchData.getTargets();
int len = targets.length;
targetBlocks = new BlockNode[len];
for (int i = 0; i < len; i++) {
targetBlocks[i] = getBlockByOffset(targets[i], successors);
}
defTargetBlock = getBlockByOffset(def, successors);
}
@Override
public boolean replaceTargetBlock(BlockNode origin, BlockNode replace) {
if (targetBlocks == null) {
return false;
}
int count = 0;
int len = targetBlocks.length;
for (int i = 0; i < len; i++) {
if (targetBlocks[i] == origin) {
targetBlocks[i] = replace;
count++;
}
}
if (defTargetBlock == origin) {
defTargetBlock = replace;
count++;
}
return count > 0;
}
@Override
public boolean isSame(InsnNode obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof SwitchInsn) || !super.isSame(obj)) {
return false;
}
SwitchInsn other = (SwitchInsn) obj;
return dataTarget == other.dataTarget
&& packed == other.packed;
}
@Override
public InsnNode copy() {
SwitchInsn copy = new SwitchInsn(getArg(0), dataTarget, packed);
copy.switchData = switchData;
copy.def = def;
copy.targetBlocks = targetBlocks;
copy.defTargetBlock = defTargetBlock;
return copyCommonParams(copy);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(super.toString());
if (switchData == null) {
sb.append("no payload");
} else {
int size = switchData.getSize();
int[] keys = switchData.getKeys();
if (targetBlocks != null) {
for (int i = 0; i < size; i++) {
sb.append(CodeWriter.NL);
sb.append(" case ").append(keys[i]).append(": goto ").append(targetBlocks[i]);
}
if (def != -1) {
sb.append(CodeWriter.NL).append(" default: goto ").append(defTargetBlock);
}
} else {
int[] targets = switchData.getTargets();
for (int i = 0; i < size; i++) {
sb.append(CodeWriter.NL);
sb.append(" case ").append(keys[i]).append(": goto ").append(InsnUtils.formatOffset(targets[i]));
}
if (def != -1) {
sb.append(CodeWriter.NL);
sb.append(" default: goto ").append(InsnUtils.formatOffset(def));
}
}
}
return sb.toString();
}
public int getDataTarget() {
return dataTarget;
}
public boolean isPacked() {
return packed;
}
public int getDefaultCaseOffset() {
return def;
}
@NotNull
private SwitchData getSwitchData() {
if (switchData == null) {
throw new JadxRuntimeException("Switch data not yet attached");
}
return switchData;
}
public int[] getTargets() {
return getSwitchData().getTargets();
}
public int[] getKeys() {
return getSwitchData().getKeys();
}
public Object getKey(int i) {
if (modifiedKeys != null) {
return modifiedKeys[i];
}
return getSwitchData().getKeys()[i];
}
public void modifyKey(int i, Object newKey) {
if (modifiedKeys == null) {
int[] keys = getKeys();
int caseCount = keys.length;
Object[] newKeys = new Object[caseCount];
for (int j = 0; j < caseCount; j++) {
newKeys[j] = keys[j];
}
modifiedKeys = newKeys;
}
modifiedKeys[i] = newKey;
}
public BlockNode[] getTargetBlocks() {
return targetBlocks;
}
public BlockNode getDefTargetBlock() {
return defTargetBlock;
}
}

View File

@ -1,142 +0,0 @@
package jadx.core.dex.instructions;
import java.util.Arrays;
import java.util.List;
import jadx.core.codegen.CodeWriter;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.InsnUtils;
import static jadx.core.utils.BlockUtils.getBlockByOffset;
public class SwitchNode extends TargetInsnNode {
private final Object[] keys;
private final int[] targets;
private final int def; // next instruction
private final boolean packed; // type of switch insn, if true can contain filler keys
private BlockNode[] targetBlocks;
private BlockNode defTargetBlock;
public SwitchNode(InsnArg arg, Object[] keys, int[] targets, int def, boolean packed) {
this(keys, targets, def, packed);
addArg(arg);
}
private SwitchNode(Object[] keys, int[] targets, int def, boolean packed) {
super(InsnType.SWITCH, 1);
this.keys = keys;
this.targets = targets;
this.def = def;
this.packed = packed;
}
public int getCasesCount() {
return keys.length;
}
public Object[] getKeys() {
return keys;
}
public int[] getTargets() {
return targets;
}
public int getDefaultCaseOffset() {
return def;
}
public boolean isPacked() {
return packed;
}
public BlockNode[] getTargetBlocks() {
return targetBlocks;
}
public BlockNode getDefTargetBlock() {
return defTargetBlock;
}
@Override
public void initBlocks(BlockNode curBlock) {
List<BlockNode> successors = curBlock.getSuccessors();
int len = targets.length;
targetBlocks = new BlockNode[len];
for (int i = 0; i < len; i++) {
targetBlocks[i] = getBlockByOffset(targets[i], successors);
}
defTargetBlock = getBlockByOffset(def, successors);
}
@Override
public boolean replaceTargetBlock(BlockNode origin, BlockNode replace) {
if (targetBlocks == null) {
return false;
}
int count = 0;
int len = targetBlocks.length;
for (int i = 0; i < len; i++) {
if (targetBlocks[i] == origin) {
targetBlocks[i] = replace;
count++;
}
}
if (defTargetBlock == origin) {
defTargetBlock = replace;
count++;
}
return count > 0;
}
@Override
public boolean isSame(InsnNode obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof SwitchNode) || !super.isSame(obj)) {
return false;
}
SwitchNode other = (SwitchNode) obj;
return def == other.def
&& Arrays.equals(keys, other.keys)
&& Arrays.equals(targets, other.targets);
}
@Override
public InsnNode copy() {
SwitchNode copy = new SwitchNode(keys, targets, def, packed);
copy.targetBlocks = targetBlocks;
copy.defTargetBlock = defTargetBlock;
return copyCommonParams(copy);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(super.toString());
if (targetBlocks == null) {
for (int i = 0; i < keys.length; i++) {
sb.append(CodeWriter.NL);
sb.append(" case ").append(keys[i]).append(": goto ").append(InsnUtils.formatOffset(targets[i]));
}
if (def != -1) {
sb.append(CodeWriter.NL);
sb.append(" default: goto ").append(InsnUtils.formatOffset(def));
}
} else {
for (int i = 0; i < keys.length; i++) {
sb.append(CodeWriter.NL);
sb.append(" case ").append(keys[i]).append(": goto ").append(targetBlocks[i]);
}
if (def != -1) {
sb.append(CodeWriter.NL).append(" default: goto ").append(defTargetBlock);
}
}
return sb.toString();
}
}

View File

@ -10,7 +10,6 @@ import org.jetbrains.annotations.NotNull;
import jadx.core.Consts;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.visitors.typeinference.TypeCompareEnum;
import jadx.core.utils.Utils;
@ -83,6 +82,8 @@ public abstract class ArgType {
return STRING;
case Consts.CLASS_CLASS:
return CLASS;
case Consts.CLASS_THROWABLE:
return THROWABLE;
default:
return new ObjectType(cleanObjectName);
}
@ -739,12 +740,12 @@ public abstract class ArgType {
return false;
}
public static ArgType tryToResolveClassAlias(DexNode dex, ArgType type) {
public static ArgType tryToResolveClassAlias(RootNode root, ArgType type) {
if (!type.isObject() || type.isGenericType()) {
return type;
}
ClassNode cls = dex.resolveClass(type);
ClassNode cls = root.resolveClass(type);
if (cls == null) {
return type;
}

View File

@ -5,14 +5,12 @@ import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.android.dx.io.instructions.DecodedInstruction;
import jadx.api.plugins.input.insns.InsnData;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.InsnRemover;
import jadx.core.utils.InsnUtils;
import jadx.core.utils.exceptions.JadxRuntimeException;
/**
@ -30,19 +28,19 @@ public abstract class InsnArg extends Typed {
return new RegisterArg(regNum, type);
}
public static RegisterArg reg(DecodedInstruction insn, int argNum, ArgType type) {
return reg(InsnUtils.getArg(insn, argNum), type);
public static RegisterArg reg(InsnData insn, int argNum, ArgType type) {
return reg(insn.getReg(argNum), type);
}
public static RegisterArg typeImmutableIfKnownReg(DecodedInstruction insn, int argNum, ArgType type) {
public static RegisterArg typeImmutableIfKnownReg(InsnData insn, int argNum, ArgType type) {
if (type.isTypeKnown()) {
return typeImmutableReg(InsnUtils.getArg(insn, argNum), type);
return typeImmutableReg(insn.getReg(argNum), type);
}
return reg(InsnUtils.getArg(insn, argNum), type);
return reg(insn.getReg(argNum), type);
}
public static RegisterArg typeImmutableReg(DecodedInstruction insn, int argNum, ArgType type) {
return typeImmutableReg(InsnUtils.getArg(insn, argNum), type);
public static RegisterArg typeImmutableReg(InsnData insn, int argNum, ArgType type) {
return typeImmutableReg(insn.getReg(argNum), type);
}
public static RegisterArg typeImmutableReg(int regNum, ArgType type) {
@ -61,7 +59,7 @@ public abstract class InsnArg extends Typed {
return new LiteralArg(literal, type);
}
public static LiteralArg lit(DecodedInstruction insn, ArgType type) {
public static LiteralArg lit(InsnData insn, ArgType type) {
return lit(insn.getLiteral(), type);
}

View File

@ -1,6 +1,7 @@
package jadx.core.dex.nodes;
import java.io.StringWriter;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@ -9,23 +10,22 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.android.dex.ClassData;
import com.android.dex.ClassData.Field;
import com.android.dex.ClassData.Method;
import com.android.dex.ClassDef;
import com.android.dex.Dex;
import jadx.api.ICodeCache;
import jadx.api.ICodeInfo;
import jadx.api.plugins.input.data.IClassData;
import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.api.plugins.input.data.annotations.IAnnotation;
import jadx.core.Consts;
import jadx.core.ProcessClass;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.annotations.Annotation;
import jadx.core.dex.attributes.FieldInitAttr;
import jadx.core.dex.attributes.annotations.AnnotationsList;
import jadx.core.dex.attributes.nodes.NotificationAttrNode;
import jadx.core.dex.attributes.nodes.SourceFileAttr;
import jadx.core.dex.info.AccessInfo;
@ -35,12 +35,9 @@ import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.nodes.parser.AnnotationsParser;
import jadx.core.dex.nodes.parser.FieldInitAttr;
import jadx.core.dex.nodes.parser.SignatureParser;
import jadx.core.dex.nodes.parser.StaticValuesParser;
import jadx.core.utils.SmaliUtils;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException;
import static jadx.core.dex.nodes.ProcessState.LOADED;
@ -49,8 +46,10 @@ import static jadx.core.dex.nodes.ProcessState.NOT_LOADED;
public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeNode {
private static final Logger LOG = LoggerFactory.getLogger(ClassNode.class);
private final DexNode dex;
private final RootNode root;
private final int clsDefOffset;
private final Path inputPath;
private final ClassInfo clsInfo;
private AccessInfo accessFlags;
private ArgType superClass;
@ -74,63 +73,38 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
// cache maps
private Map<MethodInfo, MethodNode> mthInfoMap = Collections.emptyMap();
public ClassNode(DexNode dex, ClassDef cls) {
this.dex = dex;
this.clsDefOffset = cls.getOffset();
this.clsInfo = ClassInfo.fromDex(dex, cls.getTypeIndex());
public ClassNode(RootNode root, IClassData cls) {
this.root = root;
this.inputPath = cls.getInputPath();
this.clsDefOffset = cls.getClassDefOffset();
this.clsInfo = ClassInfo.fromType(root, ArgType.object(cls.getType()));
try {
if (cls.getSupertypeIndex() == DexNode.NO_INDEX) {
this.superClass = null;
String superType = cls.getSuperType();
if (superType == null) {
// only java.lang.Object don't have super class
if (!clsInfo.getType().getObject().equals(Consts.CLASS_OBJECT)) {
throw new JadxRuntimeException("No super class in " + clsInfo.getType());
}
this.superClass = null;
} else {
this.superClass = dex.getType(cls.getSupertypeIndex());
this.superClass = ArgType.object(superType);
}
this.interfaces = new ArrayList<>(cls.getInterfaces().length);
for (short interfaceIdx : cls.getInterfaces()) {
this.interfaces.add(dex.getType(interfaceIdx));
}
if (cls.getClassDataOffset() != 0) {
ClassData clsData = dex.readClassData(cls);
int mthsCount = clsData.getDirectMethods().length + clsData.getVirtualMethods().length;
int fieldsCount = clsData.getStaticFields().length + clsData.getInstanceFields().length;
this.interfaces = Utils.collectionMap(cls.getInterfacesTypes(), ArgType::object);
methods = new ArrayList<>(mthsCount);
fields = new ArrayList<>(fieldsCount);
methods = new ArrayList<>();
fields = new ArrayList<>();
cls.visitFieldsAndMethods(
fld -> fields.add(FieldNode.build(this, fld)),
mth -> methods.add(MethodNode.build(this, mth)));
for (Method mth : clsData.getDirectMethods()) {
methods.add(new MethodNode(this, mth, false));
}
for (Method mth : clsData.getVirtualMethods()) {
methods.add(new MethodNode(this, mth, true));
}
for (Field f : clsData.getStaticFields()) {
fields.add(new FieldNode(this, f));
}
loadStaticValues(cls, fields);
for (Field f : clsData.getInstanceFields()) {
fields.add(new FieldNode(this, f));
}
} else {
methods = Collections.emptyList();
fields = Collections.emptyList();
}
loadAnnotations(cls);
AnnotationsList.attach(this, cls.getAnnotations());
loadStaticValues(cls, fields);
initAccessFlags(cls);
parseClassSignature();
setFieldsTypesFromSignature();
methods.forEach(MethodNode::initMethodTypes);
int sfIdx = cls.getSourceFileIndex();
if (sfIdx != DexNode.NO_INDEX) {
String fileName = dex.getString(sfIdx);
addSourceFilenameAttr(fileName);
}
addSourceFilenameAttr(cls.getSourceFile());
buildCache();
} catch (Exception e) {
throw new JadxRuntimeException("Error decode class: " + clsInfo, e);
@ -140,11 +114,11 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
/**
* Restore original access flags from Dalvik annotation if present
*/
private void initAccessFlags(ClassDef cls) {
private void initAccessFlags(IClassData cls) {
int accFlagsValue;
Annotation a = getAnnotation(Consts.DALVIK_INNER_CLASS);
IAnnotation a = getAnnotation(Consts.DALVIK_INNER_CLASS);
if (a != null) {
accFlagsValue = (Integer) a.getValues().get("accessFlags");
accFlagsValue = (Integer) a.getValues().get("accessFlags").getValue();
} else {
accFlagsValue = cls.getAccessFlags();
}
@ -152,45 +126,37 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
}
// empty synthetic class
public ClassNode(DexNode dex, String name, int accessFlags) {
this.dex = dex;
public ClassNode(RootNode root, String name, int accessFlags) {
this.root = root;
this.inputPath = null;
this.clsDefOffset = 0;
this.clsInfo = ClassInfo.fromName(dex.root(), name);
this.clsInfo = ClassInfo.fromName(root, name);
this.interfaces = new ArrayList<>();
this.methods = new ArrayList<>();
this.fields = new ArrayList<>();
this.accessFlags = new AccessInfo(accessFlags, AFType.CLASS);
this.parentClass = this;
dex.addClassNode(this);
}
private void loadAnnotations(ClassDef cls) {
int offset = cls.getAnnotationsOffset();
if (offset != 0) {
try {
new AnnotationsParser(this).parse(offset);
} catch (Exception e) {
LOG.error("Error parsing annotations in {}", this, e);
}
private void loadStaticValues(IClassData cls, List<FieldNode> fields) {
if (fields.isEmpty()) {
return;
}
}
private void loadStaticValues(ClassDef cls, List<FieldNode> staticFields) throws DecodeException {
List<FieldNode> staticFields = fields.stream().filter(FieldNode::isStatic).collect(Collectors.toList());
for (FieldNode f : staticFields) {
if (f.getAccessFlags().isFinal()) {
// incorrect initialization will be removed if assign found in constructor
f.addAttr(FieldInitAttr.NULL_VALUE);
}
}
int offset = cls.getStaticValuesOffset();
if (offset == 0) {
List<EncodedValue> values = cls.getStaticFieldInitValues();
int count = values.size();
if (count == 0 || count > staticFields.size()) {
return;
}
Dex.Section section = dex.openSection(offset);
StaticValuesParser parser = new StaticValuesParser(dex, section);
parser.processFields(staticFields);
for (int i = 0; i < count; i++) {
staticFields.get(i).addAttr(FieldInitAttr.constValue(values.get(i)));
}
// process const fields
root().getConstValues().processConstFields(this, staticFields);
}
@ -382,10 +348,6 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
return root().getConstValues().getConstFieldByLiteralArg(this, arg);
}
public FieldNode searchFieldById(int id) {
return searchField(FieldInfo.fromDex(dex, id));
}
public FieldNode searchField(FieldInfo field) {
for (FieldNode f : fields) {
if (f.getFieldInfo().equals(field)) {
@ -441,14 +403,10 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
return null;
}
public MethodNode searchMethodById(int id) {
return searchMethodByShortId(MethodInfo.fromDex(dex, id).getShortId());
}
public ClassNode getParentClass() {
if (parentClass == null) {
if (clsInfo.isInner()) {
ClassNode parent = dex().resolveClass(clsInfo.getParentClass());
ClassNode parent = root.resolveClass(clsInfo.getParentClass());
parentClass = parent == null ? this : parent;
} else {
parentClass = this;
@ -545,14 +503,9 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
this.accessFlags = accessFlags;
}
@Override
public DexNode dex() {
return dex;
}
@Override
public RootNode root() {
return dex.root();
return root;
}
@Override
@ -600,9 +553,14 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
}
protected static boolean getSmali(ClassNode classNode, StringWriter stringWriter) {
Path inputPath = classNode.inputPath;
if (inputPath == null) {
stringWriter.append(String.format("###### Class %s is created by jadx", classNode.getFullName()));
return false;
}
stringWriter.append(String.format("###### Class %s (%s)", classNode.getFullName(), classNode.getRawName()));
stringWriter.append(System.lineSeparator());
return SmaliUtils.getSmaliCode(classNode.dex, classNode.clsDefOffset, stringWriter);
return SmaliUtils.getSmaliCode(inputPath, classNode.clsDefOffset, stringWriter);
}
public ProcessState getState() {
@ -621,6 +579,11 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
this.dependencies = dependencies;
}
@Override
public Path getInputPath() {
return inputPath;
}
@Override
public int hashCode() {
return clsInfo.hashCode();

View File

@ -1,310 +0,0 @@
package jadx.core.dex.nodes;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.android.dex.ClassData;
import com.android.dex.ClassData.Method;
import com.android.dex.ClassDef;
import com.android.dex.Code;
import com.android.dex.Dex;
import com.android.dex.Dex.Section;
import com.android.dex.FieldId;
import com.android.dex.MethodId;
import com.android.dex.ProtoId;
import com.android.dex.TypeList;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.files.DexFile;
public class DexNode implements IDexNode {
private static final Logger LOG = LoggerFactory.getLogger(DexNode.class);
public static final int NO_INDEX = -1;
private final RootNode root;
private final Dex dexBuf;
private final DexFile file;
private final int dexId;
private final List<ClassNode> classes = new ArrayList<>();
private final Map<ClassInfo, ClassNode> clsMap = new HashMap<>();
private final ArgType[] typesCache;
public DexNode(RootNode root, DexFile input, int dexId) {
this.root = root;
this.file = input;
this.dexBuf = input.getDexBuf();
this.dexId = dexId;
this.typesCache = new ArgType[dexBuf.typeIds().size()];
}
public void loadClasses() {
for (ClassDef cls : dexBuf.classDefs()) {
try {
addClassNode(new ClassNode(this, cls));
} catch (Exception e) {
addDummyClass(cls, e);
}
}
// sort classes by name, expect top classes before inner
classes.sort(Comparator.comparing(ClassNode::getFullName));
}
private void addDummyClass(ClassDef classDef, Exception exc) {
int typeIndex = classDef.getTypeIndex();
String name = null;
try {
ClassInfo clsInfo = ClassInfo.fromDex(this, typeIndex);
if (clsInfo != null) {
name = clsInfo.getShortName();
}
} catch (Exception e) {
LOG.error("Failed to get name for class with type {}", typeIndex, e);
}
if (name == null || name.isEmpty()) {
name = "CLASS_" + typeIndex;
}
ClassNode clsNode = new ClassNode(this, name, classDef.getAccessFlags());
ErrorsCounter.error(clsNode, "Load error", exc);
addClassNode(clsNode);
}
public void addClassNode(ClassNode clsNode) {
classes.add(clsNode);
clsMap.put(clsNode.getClassInfo(), clsNode);
}
void initInnerClasses() {
// move inner classes
List<ClassNode> inner = new ArrayList<>();
for (ClassNode cls : classes) {
if (cls.getClassInfo().isInner()) {
inner.add(cls);
}
}
List<ClassNode> updated = new ArrayList<>();
for (ClassNode cls : inner) {
ClassInfo clsInfo = cls.getClassInfo();
ClassNode parent = resolveClass(clsInfo.getParentClass());
if (parent == null) {
clsMap.remove(clsInfo);
clsInfo.notInner(root);
clsMap.put(clsInfo, cls);
updated.add(cls);
} else {
parent.addInnerClass(cls);
}
}
// reload names for inner classes of updated parents
for (ClassNode updCls : updated) {
for (ClassNode innerCls : updCls.getInnerClasses()) {
innerCls.getClassInfo().updateNames(root);
}
}
}
public List<ClassNode> getClasses() {
return classes;
}
@Nullable
ClassNode resolveClassLocal(ClassInfo clsInfo) {
return clsMap.get(clsInfo);
}
@Nullable
public ClassNode resolveClass(ClassInfo clsInfo) {
ClassNode classNode = resolveClassLocal(clsInfo);
if (classNode != null) {
return classNode;
}
return root.resolveClass(clsInfo);
}
@Nullable
public ClassNode resolveClass(@NotNull ArgType type) {
if (type.isGeneric()) {
type = ArgType.object(type.getObject());
}
return resolveClass(ClassInfo.fromType(root, type));
}
@Nullable
public MethodNode resolveMethod(@NotNull MethodInfo mth) {
ClassNode cls = resolveClass(mth.getDeclClass());
if (cls != null) {
return cls.searchMethod(mth);
}
return null;
}
@Nullable
MethodNode deepResolveMethod(@NotNull ClassNode cls, String signature) {
for (MethodNode m : cls.getMethods()) {
if (m.getMethodInfo().getShortId().startsWith(signature)) {
return m;
}
}
MethodNode found;
ArgType superClass = cls.getSuperClass();
if (superClass != null) {
ClassNode superNode = resolveClass(superClass);
if (superNode != null) {
found = deepResolveMethod(superNode, signature);
if (found != null) {
return found;
}
}
}
for (ArgType iFaceType : cls.getInterfaces()) {
ClassNode iFaceNode = resolveClass(iFaceType);
if (iFaceNode != null) {
found = deepResolveMethod(iFaceNode, signature);
if (found != null) {
return found;
}
}
}
return null;
}
@Nullable
public FieldNode resolveField(FieldInfo field) {
ClassNode cls = resolveClass(field.getDeclClass());
if (cls != null) {
return cls.searchField(field);
}
return null;
}
@Nullable
FieldNode deepResolveField(@NotNull ClassNode cls, FieldInfo fieldInfo) {
FieldNode field = cls.searchFieldByNameAndType(fieldInfo);
if (field != null) {
return field;
}
ArgType superClass = cls.getSuperClass();
if (superClass != null) {
ClassNode superNode = resolveClass(superClass);
if (superNode != null) {
FieldNode found = deepResolveField(superNode, fieldInfo);
if (found != null) {
return found;
}
}
}
for (ArgType iFaceType : cls.getInterfaces()) {
ClassNode iFaceNode = resolveClass(iFaceType);
if (iFaceNode != null) {
FieldNode found = deepResolveField(iFaceNode, fieldInfo);
if (found != null) {
return found;
}
}
}
return null;
}
public DexFile getDexFile() {
return file;
}
// DexBuffer wrappers
public String getString(int index) {
if (index == DexNode.NO_INDEX) {
return null;
}
return dexBuf.strings().get(index);
}
public ArgType getType(int index) {
if (index == DexNode.NO_INDEX) {
return null;
}
ArgType type = typesCache[index];
if (type != null) {
return type;
}
// no synchronization because exactly one ArgType instance not needed, just reduce instances count
// note: same types but different instances will exist in other dex nodes
ArgType parsedType = ArgType.parse(getString(dexBuf.typeIds().get(index)));
typesCache[index] = parsedType;
return parsedType;
}
public MethodId getMethodId(int mthIndex) {
return dexBuf.methodIds().get(mthIndex);
}
public FieldId getFieldId(int fieldIndex) {
return dexBuf.fieldIds().get(fieldIndex);
}
public ProtoId getProtoId(int protoIndex) {
return dexBuf.protoIds().get(protoIndex);
}
public ClassData readClassData(ClassDef cls) {
return dexBuf.readClassData(cls);
}
public List<ArgType> readParamList(int parametersOffset) {
TypeList paramList = dexBuf.readTypeList(parametersOffset);
List<ArgType> args = new ArrayList<>(paramList.getTypes().length);
for (short t : paramList.getTypes()) {
args.add(getType(t));
}
return Collections.unmodifiableList(args);
}
public Code readCode(Method mth) {
return dexBuf.readCode(mth);
}
public Section openSection(int offset) {
return dexBuf.open(offset);
}
public boolean checkOffset(int dataOffset) {
return dataOffset >= 0 && dataOffset < dexBuf.getLength();
}
@Override
public RootNode root() {
return root;
}
@Override
public DexNode dex() {
return this;
}
@Override
public String typeName() {
return "dex";
}
public int getDexId() {
return dexId;
}
@Override
public String toString() {
return "DEX: " + file;
}
}

View File

@ -1,7 +1,9 @@
package jadx.core.dex.nodes;
import com.android.dex.ClassData.Field;
import java.nio.file.Path;
import jadx.api.plugins.input.data.IFieldData;
import jadx.core.dex.attributes.annotations.AnnotationsList;
import jadx.core.dex.attributes.nodes.LineAttrNode;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.AccessInfo.AFType;
@ -10,19 +12,21 @@ import jadx.core.dex.instructions.args.ArgType;
public class FieldNode extends LineAttrNode implements ICodeNode {
private final ClassNode parent;
private final ClassNode parentClass;
private final FieldInfo fieldInfo;
private AccessInfo accFlags;
private ArgType type;
public FieldNode(ClassNode cls, Field field) {
this(cls, FieldInfo.fromDex(cls.dex(), field.getFieldIndex()),
field.getAccessFlags());
public static FieldNode build(ClassNode cls, IFieldData fieldData) {
FieldInfo fieldInfo = FieldInfo.fromData(cls.root(), fieldData);
FieldNode fieldNode = new FieldNode(cls, fieldInfo, fieldData.getAccessFlags());
AnnotationsList.attach(fieldNode, fieldData.getAnnotations());
return fieldNode;
}
public FieldNode(ClassNode cls, FieldInfo fieldInfo, int accessFlags) {
this.parent = cls;
this.parentClass = cls;
this.fieldInfo = fieldInfo;
this.type = fieldInfo.getType();
this.accFlags = new AccessInfo(accessFlags, AFType.FIELD);
@ -42,6 +46,10 @@ public class FieldNode extends LineAttrNode implements ICodeNode {
this.accFlags = accFlags;
}
public boolean isStatic() {
return accFlags.isStatic();
}
public String getName() {
return fieldInfo.getName();
}
@ -59,7 +67,7 @@ public class FieldNode extends LineAttrNode implements ICodeNode {
}
public ClassNode getParentClass() {
return parent;
return parentClass;
}
@Override
@ -68,13 +76,13 @@ public class FieldNode extends LineAttrNode implements ICodeNode {
}
@Override
public DexNode dex() {
return parent.dex();
public Path getInputPath() {
return parentClass.getInputPath();
}
@Override
public RootNode root() {
return parent.root();
return parentClass.root();
}
@Override

View File

@ -1,10 +1,12 @@
package jadx.core.dex.nodes;
import java.nio.file.Path;
public interface IDexNode {
String typeName();
DexNode dex();
RootNode root();
Path getInputPath();
}

View File

@ -8,8 +8,7 @@ import java.util.Objects;
import org.jetbrains.annotations.Nullable;
import com.android.dx.io.instructions.DecodedInstruction;
import jadx.api.plugins.input.insns.InsnData;
import jadx.core.codegen.CodeWriter;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.nodes.LineAttrNode;
@ -171,7 +170,7 @@ public class InsnNode extends LineAttrNode {
return -1;
}
protected void addReg(DecodedInstruction insn, int i, ArgType type) {
protected void addReg(InsnData insn, int i, ArgType type) {
addArg(InsnArg.reg(insn, i, type));
}
@ -183,7 +182,7 @@ public class InsnNode extends LineAttrNode {
addArg(InsnArg.lit(literal, type));
}
protected void addLit(DecodedInstruction insn, ArgType type) {
protected void addLit(InsnData insn, ArgType type) {
addArg(InsnArg.lit(insn, type));
}
@ -259,7 +258,6 @@ public class InsnNode extends LineAttrNode {
case FILL_ARRAY:
case FILLED_NEW_ARRAY:
case NEW_ARRAY:
case NEW_MULTIDIM_ARRAY:
case STR_CONCAT:
return true;
@ -470,6 +468,9 @@ public class InsnNode extends LineAttrNode {
}
protected void appendArgs(StringBuilder sb) {
if (arguments.isEmpty()) {
return;
}
String argsStr = Utils.listToString(arguments);
if (argsStr.length() < 120) {
sb.append(argsStr);

View File

@ -1,47 +1,39 @@
package jadx.core.dex.nodes;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.android.dex.ClassData.Method;
import com.android.dex.Code;
import com.android.dex.Code.CatchHandler;
import com.android.dex.Code.Try;
import jadx.api.plugins.input.data.ICodeReader;
import jadx.api.plugins.input.data.IDebugInfo;
import jadx.api.plugins.input.data.IMethodData;
import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.api.plugins.input.data.annotations.IAnnotation;
import jadx.core.Consts;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.annotations.Annotation;
import jadx.core.dex.attributes.nodes.JumpInfo;
import jadx.core.dex.attributes.annotations.AnnotationsList;
import jadx.core.dex.attributes.annotations.MethodParameters;
import jadx.core.dex.attributes.nodes.LoopInfo;
import jadx.core.dex.attributes.nodes.NotificationAttrNode;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.AccessInfo.AFType;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.GotoNode;
import jadx.core.dex.instructions.IfNode;
import jadx.core.dex.instructions.InsnDecoder;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.SwitchNode;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.args.SSAVar;
import jadx.core.dex.nodes.parser.SignatureParser;
import jadx.core.dex.regions.Region;
import jadx.core.dex.trycatch.ExcHandlerAttr;
import jadx.core.dex.trycatch.ExceptionHandler;
import jadx.core.dex.trycatch.TryCatchBlock;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.exceptions.JadxRuntimeException;
@ -55,13 +47,11 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
private final ClassNode parentClass;
private AccessInfo accFlags;
private final Method methodData;
private final ICodeReader codeReader;
private final boolean methodIsVirtual;
private boolean noCode;
private int regsCount;
private int codeSize;
private int debugInfoOffset;
private boolean loaded;
@ -82,13 +72,20 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
private List<LoopInfo> loops;
private Region region;
public MethodNode(ClassNode classNode, Method mthData, boolean isVirtual) {
this.mthInfo = MethodInfo.fromDex(classNode.dex(), mthData.getMethodIndex());
public static MethodNode build(ClassNode classNode, IMethodData methodData) {
MethodNode methodNode = new MethodNode(classNode, methodData);
AnnotationsList.attach(methodNode, methodData.getAnnotations());
MethodParameters.attach(methodNode, methodData.getParamsAnnotations());
return methodNode;
}
public MethodNode(ClassNode classNode, IMethodData mthData) {
this.mthInfo = MethodInfo.fromData(classNode.root(), mthData);
this.parentClass = classNode;
this.accFlags = new AccessInfo(mthData.getAccessFlags(), AFType.METHOD);
this.noCode = mthData.getCodeOffset() == 0;
this.methodData = noCode ? null : mthData;
this.methodIsVirtual = isVirtual;
this.noCode = mthData.getCodeReader() == null;
this.codeReader = noCode ? null : mthData.getCodeReader().copy();
this.methodIsVirtual = !mthData.isDirect();
unload();
}
@ -122,26 +119,15 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
loaded = true;
if (noCode) {
regsCount = 0;
codeSize = 0;
// TODO: registers not needed without code
initArguments(this.argTypes);
return;
}
DexNode dex = parentClass.dex();
Code mthCode = dex.readCode(methodData);
this.regsCount = mthCode.getRegistersSize();
this.regsCount = codeReader.getRegistersCount();
initArguments(this.argTypes);
InsnDecoder decoder = new InsnDecoder(this);
decoder.decodeInsns(mthCode);
this.instructions = decoder.process();
this.codeSize = instructions.length;
initTryCatches(this, mthCode, instructions);
initJumps(instructions);
this.debugInfoOffset = mthCode.getDebugInfoOffset();
this.instructions = decoder.process(codeReader);
} catch (Exception e) {
if (!noCode) {
unload();
@ -331,130 +317,6 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
return typeParameters;
}
private static void initTryCatches(MethodNode mth, Code mthCode, InsnNode[] insnByOffset) {
CatchHandler[] catchBlocks = mthCode.getCatchHandlers();
Try[] tries = mthCode.getTries();
if (catchBlocks.length == 0 && tries.length == 0) {
return;
}
int handlersCount = 0;
Set<Integer> addrs = new HashSet<>();
List<TryCatchBlock> catches = new ArrayList<>(catchBlocks.length);
for (CatchHandler handler : catchBlocks) {
TryCatchBlock tcBlock = new TryCatchBlock();
catches.add(tcBlock);
int[] handlerAddrArr = handler.getAddresses();
for (int i = 0; i < handlerAddrArr.length; i++) {
int addr = handlerAddrArr[i];
ClassInfo type = ClassInfo.fromDex(mth.dex(), handler.getTypeIndexes()[i]);
tcBlock.addHandler(mth, addr, type);
addrs.add(addr);
handlersCount++;
}
int addr = handler.getCatchAllAddress();
if (addr >= 0) {
tcBlock.addHandler(mth, addr, null);
addrs.add(addr);
handlersCount++;
}
}
if (handlersCount > 0 && handlersCount != addrs.size()) {
// resolve nested try blocks:
// inner block contains all handlers from outer block => remove these handlers from inner block
// each handler must be only in one try/catch block
for (TryCatchBlock outerTry : catches) {
for (TryCatchBlock innerTry : catches) {
if (outerTry != innerTry
&& innerTry.containsAllHandlers(outerTry)) {
innerTry.removeSameHandlers(outerTry);
}
}
}
}
// attach EXC_HANDLER attributes to instructions
addrs.clear();
for (TryCatchBlock ct : catches) {
for (ExceptionHandler eh : ct.getHandlers()) {
int addr = eh.getHandleOffset();
ExcHandlerAttr ehAttr = new ExcHandlerAttr(ct, eh);
// TODO: don't override existing attribute
insnByOffset[addr].addAttr(ehAttr);
}
}
// attach TRY_ENTER, TRY_LEAVE attributes to instructions
for (Try aTry : tries) {
int catchNum = aTry.getCatchHandlerIndex();
TryCatchBlock catchBlock = catches.get(catchNum);
int offset = aTry.getStartAddress();
int end = offset + aTry.getInstructionCount() - 1;
boolean tryBlockStarted = false;
InsnNode insn = null;
while (offset <= end && offset >= 0) {
insn = insnByOffset[offset];
if (insn != null && insn.getType() != InsnType.NOP) {
if (tryBlockStarted) {
catchBlock.addInsn(insn);
} else if (insn.canThrowException()) {
insn.add(AFlag.TRY_ENTER);
catchBlock.addInsn(insn);
tryBlockStarted = true;
}
}
offset = InsnDecoder.getNextInsnOffset(insnByOffset, offset);
}
if (tryBlockStarted && insn != null) {
insn.add(AFlag.TRY_LEAVE);
}
}
}
private static void initJumps(InsnNode[] insnByOffset) {
for (int offset = 0; offset < insnByOffset.length; offset++) {
InsnNode insn = insnByOffset[offset];
if (insn == null) {
continue;
}
switch (insn.getType()) {
case SWITCH:
SwitchNode sw = (SwitchNode) insn;
for (int target : sw.getTargets()) {
addJump(insnByOffset, offset, target);
}
// default case
int nextInsnOffset = InsnDecoder.getNextInsnOffset(insnByOffset, offset);
if (nextInsnOffset != -1) {
addJump(insnByOffset, offset, nextInsnOffset);
}
break;
case IF:
int next = InsnDecoder.getNextInsnOffset(insnByOffset, offset);
if (next != -1) {
addJump(insnByOffset, offset, next);
}
addJump(insnByOffset, offset, ((IfNode) insn).getTarget());
break;
case GOTO:
addJump(insnByOffset, offset, ((GotoNode) insn).getTarget());
break;
default:
break;
}
}
}
private static void addJump(InsnNode[] insnByOffset, int offset, int target) {
insnByOffset[target].addAttr(AType.JUMP, new JumpInfo(offset, target));
}
public String getName() {
return mthInfo.getName();
}
@ -471,10 +333,6 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
return noCode;
}
public int getCodeSize() {
return codeSize;
}
public InsnNode[] getInstructions() {
return instructions;
}
@ -601,11 +459,12 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
@Override
@SuppressWarnings("unchecked")
public List<ArgType> getThrows() {
Annotation an = getAnnotation(Consts.DALVIK_THROWS);
IAnnotation an = getAnnotation(Consts.DALVIK_THROWS);
if (an == null) {
return Collections.emptyList();
}
return (List<ArgType>) an.getDefaultValue();
List<EncodedValue> types = (List<EncodedValue>) an.getDefaultValue().getValue();
return Utils.collectionMap(types, ev -> ArgType.object((String) ev.getValue()));
}
/**
@ -654,10 +513,6 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
return regsCount;
}
public int getDebugInfoOffset() {
return debugInfoOffset;
}
public SSAVar makeNewSVar(@NotNull RegisterArg assignArg) {
int regNum = assignArg.getRegNum();
return makeNewSVar(regNum, getNextSVarVersion(regNum), assignArg);
@ -709,14 +564,9 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
this.region = region;
}
@Override
public DexNode dex() {
return parentClass.dex();
}
@Override
public RootNode root() {
return dex().root();
return parentClass.root();
}
@Override
@ -724,13 +574,23 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
return "method";
}
@Override
public Path getInputPath() {
return parentClass.getInputPath();
}
@Override
public MethodInfo getMethodInfo() {
return mthInfo;
}
public long getMethodCodeOffset() {
return noCode ? 0 : methodData.getCodeOffset();
return noCode ? 0 : codeReader.getCodeOffset();
}
@Nullable
public IDebugInfo getDebugInfo() {
return noCode ? null : codeReader.getDebugInfo();
}
/**
@ -756,6 +616,10 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
return loaded;
}
public ICodeReader getCodeReader() {
return codeReader;
}
@Override
public int hashCode() {
return mthInfo.hashCode();

View File

@ -1,8 +1,10 @@
package jadx.core.dex.nodes;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ -14,6 +16,8 @@ import jadx.api.JadxArgs;
import jadx.api.ResourceFile;
import jadx.api.ResourceType;
import jadx.api.ResourcesLoader;
import jadx.api.plugins.input.data.IClassData;
import jadx.api.plugins.input.data.ILoadResult;
import jadx.core.Jadx;
import jadx.core.clsp.ClspGraph;
import jadx.core.dex.info.ClassInfo;
@ -31,8 +35,6 @@ import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.StringUtils;
import jadx.core.utils.android.AndroidResourcesUtils;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.DexFile;
import jadx.core.utils.files.InputFile;
import jadx.core.xmlgen.ResTableParser;
import jadx.core.xmlgen.ResourceStorage;
@ -53,8 +55,10 @@ public class RootNode {
private final ICodeCache codeCache;
private final List<ClassNode> classes = new ArrayList<>();
private final Map<ClassInfo, ClassNode> clsMap = new HashMap<>();
private ClspGraph clsp;
private List<DexNode> dexNodes;
@Nullable
private String appPackage;
@Nullable
@ -69,29 +73,47 @@ public class RootNode {
this.codeCache = args.getCodeCache();
this.methodUtils = new MethodUtils(this);
this.typeUtils = new TypeUtils(this);
this.dexNodes = Collections.emptyList();
}
public void load(List<InputFile> inputFiles) {
dexNodes = new ArrayList<>();
for (InputFile input : inputFiles) {
for (DexFile dexFile : input.getDexFiles()) {
public void loadClasses(List<ILoadResult> loadedInputs) {
for (ILoadResult loadedInput : loadedInputs) {
loadedInput.visitClasses(cls -> {
try {
LOG.debug("Load: {}", dexFile);
DexNode dexNode = new DexNode(this, dexFile, dexNodes.size());
dexNodes.add(dexNode);
addClassNode(new ClassNode(RootNode.this, cls));
} catch (Exception e) {
throw new JadxRuntimeException("Error decode file: " + dexFile, e);
addDummyClass(cls, e);
}
}
}
for (DexNode dexNode : dexNodes) {
dexNode.loadClasses();
});
}
// sort classes by name, expect top classes before inner
classes.sort(Comparator.comparing(ClassNode::getFullName));
initInnerClasses();
}
private void addDummyClass(IClassData classData, Exception exc) {
String typeStr = classData.getType();
String name = null;
try {
ClassInfo clsInfo = ClassInfo.fromName(this, typeStr);
if (clsInfo != null) {
name = clsInfo.getShortName();
}
} catch (Exception e) {
LOG.error("Failed to get name for class with type {}", typeStr, e);
}
if (name == null || name.isEmpty()) {
name = "CLASS_" + typeStr;
}
ClassNode clsNode = new ClassNode(this, name, classData.getAccessFlags());
ErrorsCounter.error(clsNode, "Load error", exc);
addClassNode(clsNode);
}
public void addClassNode(ClassNode clsNode) {
classes.add(clsNode);
clsMap.put(clsNode.getClassInfo(), clsNode);
}
public void loadResources(List<ResourceFile> resources) {
ResourceFile arsc = null;
for (ResourceFile rf : resources) {
@ -110,7 +132,9 @@ public class RootNode {
parser.decode(is);
return parser.getResStorage();
});
processResources(resStorage);
if (resStorage != null) {
processResources(resStorage);
}
} catch (Exception e) {
LOG.error("Failed to parse '.arsc' file", e);
}
@ -127,11 +151,6 @@ public class RootNode {
if (this.clsp == null) {
ClspGraph newClsp = new ClspGraph(this);
newClsp.load();
List<ClassNode> classes = new ArrayList<>();
for (DexNode dexNode : dexNodes) {
classes.addAll(dexNode.getClasses());
}
newClsp.addApp(classes);
this.clsp = newClsp;
@ -142,36 +161,54 @@ public class RootNode {
}
private void initInnerClasses() {
for (DexNode dexNode : dexNodes) {
dexNode.initInnerClasses();
// move inner classes
List<ClassNode> inner = new ArrayList<>();
for (ClassNode cls : classes) {
if (cls.getClassInfo().isInner()) {
inner.add(cls);
}
}
List<ClassNode> updated = new ArrayList<>();
for (ClassNode cls : inner) {
ClassInfo clsInfo = cls.getClassInfo();
ClassNode parent = resolveClass(clsInfo.getParentClass());
if (parent == null) {
clsMap.remove(clsInfo);
clsInfo.notInner(this);
clsMap.put(clsInfo, cls);
updated.add(cls);
} else {
parent.addInnerClass(cls);
}
}
// reload names for inner classes of updated parents
for (ClassNode updCls : updated) {
for (ClassNode innerCls : updCls.getInnerClasses()) {
innerCls.getClassInfo().updateNames(this);
}
}
}
public List<ClassNode> getClasses() {
return classes;
}
public List<ClassNode> getClasses(boolean includeInner) {
List<ClassNode> classes = new ArrayList<>();
for (DexNode dex : dexNodes) {
if (includeInner) {
classes.addAll(dex.getClasses());
} else {
for (ClassNode cls : dex.getClasses()) {
if (!cls.getClassInfo().isInner()) {
classes.add(cls);
}
}
if (includeInner) {
return classes;
}
List<ClassNode> notInnerClasses = new ArrayList<>();
for (ClassNode cls : classes) {
if (!cls.getClassInfo().isInner()) {
notInnerClasses.add(cls);
}
}
return classes;
return notInnerClasses;
}
@Nullable
public ClassNode resolveClass(ClassInfo clsInfo) {
for (DexNode dexNode : dexNodes) {
ClassNode cls = dexNode.resolveClassLocal(clsInfo);
if (cls != null) {
return cls;
}
}
return null;
return clsMap.get(clsInfo);
}
@Nullable
@ -185,30 +222,22 @@ public class RootNode {
if (clsType.isGeneric()) {
clsType = ArgType.object(clsType.getObject());
}
for (DexNode dexNode : dexNodes) {
ClassNode cls = dexNode.resolveClass(clsType);
if (cls != null) {
return cls;
}
}
return null;
return resolveClass(ClassInfo.fromType(this, clsType));
}
@Nullable
public ClassNode searchClassByName(String fullName) {
public ClassNode resolveClass(String fullName) {
ClassInfo clsInfo = ClassInfo.fromName(this, fullName);
return resolveClass(clsInfo);
}
@Nullable
public ClassNode searchClassByFullAlias(String fullName) {
for (DexNode dexNode : dexNodes) {
for (ClassNode cls : dexNode.getClasses()) {
ClassInfo classInfo = cls.getClassInfo();
if (classInfo.getFullName().equals(fullName)
|| classInfo.getAliasFullName().equals(fullName)) {
return cls;
}
for (ClassNode cls : classes) {
ClassInfo classInfo = cls.getClassInfo();
if (classInfo.getFullName().equals(fullName)
|| classInfo.getAliasFullName().equals(fullName)) {
return cls;
}
}
return null;
@ -216,16 +245,23 @@ public class RootNode {
public List<ClassNode> searchClassByShortName(String shortName) {
List<ClassNode> list = new ArrayList<>();
for (DexNode dexNode : dexNodes) {
for (ClassNode cls : dexNode.getClasses()) {
if (cls.getClassInfo().getShortName().equals(shortName)) {
list.add(cls);
}
for (ClassNode cls : classes) {
if (cls.getClassInfo().getShortName().equals(shortName)) {
list.add(cls);
}
}
return list;
}
@Nullable
public MethodNode resolveMethod(@NotNull MethodInfo mth) {
ClassNode cls = resolveClass(mth.getDeclClass());
if (cls != null) {
return cls.searchMethod(mth);
}
return null;
}
@Nullable
public MethodNode deepResolveMethod(@NotNull MethodInfo mth) {
ClassNode cls = resolveClass(mth.getDeclClass());
@ -236,7 +272,46 @@ public class RootNode {
if (methodNode != null) {
return methodNode;
}
return cls.dex().deepResolveMethod(cls, mth.makeSignature(false));
return deepResolveMethod(cls, mth.makeSignature(false));
}
@Nullable
private MethodNode deepResolveMethod(@NotNull ClassNode cls, String signature) {
for (MethodNode m : cls.getMethods()) {
if (m.getMethodInfo().getShortId().startsWith(signature)) {
return m;
}
}
MethodNode found;
ArgType superClass = cls.getSuperClass();
if (superClass != null) {
ClassNode superNode = resolveClass(superClass);
if (superNode != null) {
found = deepResolveMethod(superNode, signature);
if (found != null) {
return found;
}
}
}
for (ArgType iFaceType : cls.getInterfaces()) {
ClassNode iFaceNode = resolveClass(iFaceType);
if (iFaceNode != null) {
found = deepResolveMethod(iFaceNode, signature);
if (found != null) {
return found;
}
}
}
return null;
}
@Nullable
public FieldNode resolveField(FieldInfo field) {
ClassNode cls = resolveClass(field.getDeclClass());
if (cls != null) {
return cls.searchField(field);
}
return null;
}
@Nullable
@ -245,7 +320,35 @@ public class RootNode {
if (cls == null) {
return null;
}
return cls.dex().deepResolveField(cls, field);
return deepResolveField(cls, field);
}
@Nullable
private FieldNode deepResolveField(@NotNull ClassNode cls, FieldInfo fieldInfo) {
FieldNode field = cls.searchFieldByNameAndType(fieldInfo);
if (field != null) {
return field;
}
ArgType superClass = cls.getSuperClass();
if (superClass != null) {
ClassNode superNode = resolveClass(superClass);
if (superNode != null) {
FieldNode found = deepResolveField(superNode, fieldInfo);
if (found != null) {
return found;
}
}
}
for (ArgType iFaceType : cls.getInterfaces()) {
ClassNode iFaceNode = resolveClass(iFaceType);
if (iFaceNode != null) {
FieldNode found = deepResolveField(iFaceNode, fieldInfo);
if (found != null) {
return found;
}
}
}
return null;
}
public List<IDexTreeVisitor> getPasses() {
@ -262,10 +365,6 @@ public class RootNode {
}
}
public List<DexNode> getDexNodes() {
return dexNodes;
}
public ClspGraph getClsp() {
return clsp;
}

View File

@ -1,112 +0,0 @@
package jadx.core.dex.nodes.parser;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import com.android.dex.Dex.Section;
import jadx.core.dex.attributes.annotations.Annotation;
import jadx.core.dex.attributes.annotations.Annotation.Visibility;
import jadx.core.dex.attributes.annotations.AnnotationsList;
import jadx.core.dex.attributes.annotations.MethodParameters;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.exceptions.DecodeException;
public class AnnotationsParser {
private static final Visibility[] VISIBILITIES = {
Visibility.BUILD,
Visibility.RUNTIME,
Visibility.SYSTEM
};
private final DexNode dex;
private final ClassNode cls;
public AnnotationsParser(ClassNode cls) {
this.cls = cls;
this.dex = cls.dex();
}
public void parse(int offset) throws DecodeException {
Section section = dex.openSection(offset);
// TODO read as unsigned int
int classAnnotationsOffset = section.readInt();
int fieldsCount = section.readInt();
int annotatedMethodsCount = section.readInt();
int annotatedParametersCount = section.readInt();
if (classAnnotationsOffset != 0) {
cls.addAttr(readAnnotationSet(classAnnotationsOffset));
}
for (int i = 0; i < fieldsCount; i++) {
FieldNode f = cls.searchFieldById(section.readInt());
f.addAttr(readAnnotationSet(section.readInt()));
}
for (int i = 0; i < annotatedMethodsCount; i++) {
MethodNode m = cls.searchMethodById(section.readInt());
m.addAttr(readAnnotationSet(section.readInt()));
}
for (int i = 0; i < annotatedParametersCount; i++) {
MethodNode mth = cls.searchMethodById(section.readInt());
// read annotation ref list
Section ss = dex.openSection(section.readInt());
int size = ss.readInt();
MethodParameters params = new MethodParameters(size);
for (int j = 0; j < size; j++) {
params.getParamList().add(readAnnotationSet(ss.readInt()));
}
mth.addAttr(params);
}
}
private AnnotationsList readAnnotationSet(int offset) throws DecodeException {
if (offset == 0) {
return AnnotationsList.EMPTY;
}
Section section = dex.openSection(offset);
int size = section.readInt();
if (size == 0) {
return AnnotationsList.EMPTY;
}
List<Annotation> list = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
Section anSection = dex.openSection(section.readInt());
Annotation a = readAnnotation(dex, anSection, true);
list.add(a);
}
return new AnnotationsList(list);
}
public static Annotation readAnnotation(DexNode dex, Section s, boolean readVisibility) throws DecodeException {
EncValueParser parser = new EncValueParser(dex, s);
Visibility visibility = null;
if (readVisibility) {
byte v = s.readByte();
visibility = VISIBILITIES[v];
}
int typeIndex = s.readUleb128();
int size = s.readUleb128();
Map<String, Object> values = new LinkedHashMap<>(size);
for (int i = 0; i < size; i++) {
String name = dex.getString(s.readUleb128());
values.put(name, parser.parseValue());
}
ArgType type = dex.getType(typeIndex);
Annotation annotation = new Annotation(visibility, type, values);
if (!type.isObject()) {
throw new DecodeException("Incorrect type for annotation: " + annotation);
}
return annotation;
}
}

View File

@ -1,132 +0,0 @@
package jadx.core.dex.nodes.parser;
import java.util.ArrayList;
import java.util.List;
import com.android.dex.Dex.Section;
import com.android.dex.Leb128;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.nodes.DexNode;
import jadx.core.utils.exceptions.DecodeException;
public class EncValueParser {
private static final int ENCODED_BYTE = 0x00;
private static final int ENCODED_SHORT = 0x02;
private static final int ENCODED_CHAR = 0x03;
private static final int ENCODED_INT = 0x04;
private static final int ENCODED_LONG = 0x06;
private static final int ENCODED_FLOAT = 0x10;
private static final int ENCODED_DOUBLE = 0x11;
private static final int ENCODED_STRING = 0x17;
private static final int ENCODED_TYPE = 0x18;
private static final int ENCODED_FIELD = 0x19;
private static final int ENCODED_ENUM = 0x1b;
private static final int ENCODED_METHOD = 0x1a;
private static final int ENCODED_ARRAY = 0x1c;
private static final int ENCODED_ANNOTATION = 0x1d;
private static final int ENCODED_NULL = 0x1e;
private static final int ENCODED_BOOLEAN = 0x1f;
protected final Section in;
private final DexNode dex;
public EncValueParser(DexNode dex, Section in) {
this.in = in;
this.dex = dex;
}
public Object parseValue() throws DecodeException {
int argAndType = readByte();
int type = argAndType & 0x1F;
int arg = (argAndType & 0xE0) >> 5;
int size = arg + 1;
switch (type) {
case ENCODED_NULL:
return null;
case ENCODED_BOOLEAN:
return arg == 1;
case ENCODED_BYTE:
return in.readByte();
case ENCODED_SHORT:
return (short) parseNumber(size, true);
case ENCODED_CHAR:
return (char) parseUnsignedInt(size);
case ENCODED_INT:
return (int) parseNumber(size, true);
case ENCODED_LONG:
return parseNumber(size, true);
case ENCODED_FLOAT:
return Float.intBitsToFloat((int) parseNumber(size, false, 4));
case ENCODED_DOUBLE:
return Double.longBitsToDouble(parseNumber(size, false, 8));
case ENCODED_STRING:
return dex.getString(parseUnsignedInt(size));
case ENCODED_TYPE:
return dex.getType(parseUnsignedInt(size));
case ENCODED_METHOD:
return MethodInfo.fromDex(dex, parseUnsignedInt(size));
case ENCODED_FIELD:
case ENCODED_ENUM:
return FieldInfo.fromDex(dex, parseUnsignedInt(size));
case ENCODED_ARRAY:
int count = Leb128.readUnsignedLeb128(in);
List<Object> values = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
values.add(parseValue());
}
return values;
case ENCODED_ANNOTATION:
return AnnotationsParser.readAnnotation(dex, in, false);
default:
throw new DecodeException("Unknown encoded value type: 0x" + Integer.toHexString(type));
}
}
private int parseUnsignedInt(int byteCount) {
return (int) parseNumber(byteCount, false, 0);
}
private long parseNumber(int byteCount, boolean isSignExtended) {
return parseNumber(byteCount, isSignExtended, 0);
}
private long parseNumber(int byteCount, boolean isSignExtended, int fillOnRight) {
long result = 0;
long last = 0;
for (int i = 0; i < byteCount; i++) {
last = readByte();
result |= last << i * 8;
}
if (fillOnRight != 0) {
for (int i = byteCount; i < fillOnRight; i++) {
result <<= 8;
}
} else {
if (isSignExtended && (last & 0x80) != 0) {
for (int i = byteCount; i < 8; i++) {
result |= (long) 0xFF << i * 8;
}
}
}
return result;
}
private int readByte() {
return in.readByte() & 0xFF;
}
}

View File

@ -9,11 +9,13 @@ import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.api.plugins.input.data.annotations.IAnnotation;
import jadx.core.Consts;
import jadx.core.dex.attributes.IAttributeNode;
import jadx.core.dex.attributes.annotations.Annotation;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.GenericTypeParameter;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException;
public class SignatureParser {
@ -45,11 +47,13 @@ public class SignatureParser {
@SuppressWarnings("unchecked")
@Nullable
public static String getSignature(IAttributeNode node) {
Annotation a = node.getAnnotation(Consts.DALVIK_SIGNATURE);
IAnnotation a = node.getAnnotation(Consts.DALVIK_SIGNATURE);
if (a == null) {
return null;
}
return mergeSignature((List<String>) a.getDefaultValue());
List<EncodedValue> values = (List<EncodedValue>) a.getDefaultValue().getValue();
List<String> strings = Utils.collectionMap(values, ev -> ((String) ev.getValue()));
return mergeSignature(strings);
}
private char next() {

View File

@ -1,28 +0,0 @@
package jadx.core.dex.nodes.parser;
import java.util.List;
import com.android.dex.Dex.Section;
import com.android.dex.Leb128;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.utils.exceptions.DecodeException;
public class StaticValuesParser extends EncValueParser {
public StaticValuesParser(DexNode dex, Section in) {
super(dex, in);
}
public int processFields(List<FieldNode> fields) throws DecodeException {
int count = Leb128.readUnsignedLeb128(in);
for (int i = 0; i < count; i++) {
Object value = parseValue();
if (i < fields.size()) {
fields.get(i).addAttr(FieldInitAttr.constValue(value));
}
}
return count;
}
}

View File

@ -2,7 +2,6 @@ package jadx.core.dex.trycatch;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import org.jetbrains.annotations.Nullable;
@ -24,8 +23,8 @@ public class TryCatchBlock {
private final List<InsnNode> insns;
private final CatchAttr attr;
public TryCatchBlock() {
handlers = new LinkedList<>();
public TryCatchBlock(int handlersCount) {
handlers = new ArrayList<>(handlersCount);
insns = new ArrayList<>();
attr = new CatchAttr(this);
}

View File

@ -0,0 +1,135 @@
package jadx.core.dex.visitors;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import jadx.api.plugins.input.data.ICatch;
import jadx.api.plugins.input.data.ICodeReader;
import jadx.api.plugins.input.data.ITry;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.trycatch.ExcHandlerAttr;
import jadx.core.dex.trycatch.ExceptionHandler;
import jadx.core.dex.trycatch.TryCatchBlock;
import jadx.core.utils.exceptions.JadxException;
import static jadx.core.dex.visitors.ProcessInstructionsVisitor.getNextInsnOffset;
@JadxVisitor(
name = "Attach Try/Catch Visitor",
desc = "Attach try/catch info to instructions",
runBefore = {
ProcessInstructionsVisitor.class
}
)
public class AttachTryCatchVisitor extends AbstractVisitor {
@Override
public void visit(MethodNode mth) throws JadxException {
if (mth.isNoCode()) {
return;
}
initTryCatches(mth, mth.getCodeReader(), mth.getInstructions());
}
private static void initTryCatches(MethodNode mth, ICodeReader codeReader, InsnNode[] insnByOffset) {
List<ITry> tries = codeReader.getTries();
if (tries.isEmpty()) {
return;
}
int handlersCount = 0;
Set<Integer> addrs = new HashSet<>();
List<TryCatchBlock> catches = new ArrayList<>(tries.size());
for (ITry tryData : tries) {
TryCatchBlock catchBlock = processHandlers(mth, addrs, tryData.getCatch());
catches.add(catchBlock);
handlersCount += catchBlock.getHandlersCount();
}
// TODO: run modify in later passes
if (handlersCount > 0 && handlersCount != addrs.size()) {
// resolve nested try blocks:
// inner block contains all handlers from outer block => remove these handlers from inner block
// each handler must be only in one try/catch block
for (TryCatchBlock outerTry : catches) {
for (TryCatchBlock innerTry : catches) {
if (outerTry != innerTry
&& innerTry.containsAllHandlers(outerTry)) {
innerTry.removeSameHandlers(outerTry);
}
}
}
}
addrs.clear();
for (TryCatchBlock tryCatchBlock : catches) {
if (tryCatchBlock.getHandlersCount() == 0) {
continue;
}
for (ExceptionHandler handler : tryCatchBlock.getHandlers()) {
int addr = handler.getHandleOffset();
ExcHandlerAttr ehAttr = new ExcHandlerAttr(tryCatchBlock, handler);
// TODO: don't override existing attribute
insnByOffset[addr].addAttr(ehAttr);
}
}
int k = 0;
for (ITry tryData : tries) {
TryCatchBlock catchBlock = catches.get(k++);
if (catchBlock.getHandlersCount() != 0) {
markTryBounds(insnByOffset, tryData, catchBlock);
}
}
}
private static void markTryBounds(InsnNode[] insnByOffset, ITry aTry, TryCatchBlock catchBlock) {
int offset = aTry.getStartAddress();
int end = offset + aTry.getInstructionCount() - 1;
boolean tryBlockStarted = false;
InsnNode insn = null;
while (offset <= end && offset >= 0) {
insn = insnByOffset[offset];
if (insn != null && insn.getType() != InsnType.NOP) {
if (tryBlockStarted) {
catchBlock.addInsn(insn);
} else if (insn.canThrowException()) {
insn.add(AFlag.TRY_ENTER);
catchBlock.addInsn(insn);
tryBlockStarted = true;
}
}
offset = getNextInsnOffset(insnByOffset, offset);
}
if (tryBlockStarted && insn != null) {
insn.add(AFlag.TRY_LEAVE);
}
}
private static TryCatchBlock processHandlers(MethodNode mth, Set<Integer> addrs, ICatch catchBlock) {
int[] handlerAddrArr = catchBlock.getAddresses();
String[] handlerTypes = catchBlock.getTypes();
int handlersCount = handlerAddrArr.length;
TryCatchBlock tcBlock = new TryCatchBlock(handlersCount);
for (int i = 0; i < handlersCount; i++) {
int addr = handlerAddrArr[i];
ClassInfo type = ClassInfo.fromName(mth.root(), handlerTypes[i]);
tcBlock.addHandler(mth, addr, type);
addrs.add(addr);
}
int addr = catchBlock.getCatchAllAddress();
if (addr >= 0) {
tcBlock.addHandler(mth, addr, null);
addrs.add(addr);
}
return tcBlock;
}
}

View File

@ -4,8 +4,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import com.android.dx.rop.code.AccessFlags;
import jadx.api.plugins.input.data.AccessFlags;
import jadx.core.Consts;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
@ -80,7 +79,7 @@ public class ClassModifier extends AbstractVisitor {
for (FieldNode field : cls.getFields()) {
if (field.getAccessFlags().isSynthetic() && field.getType().isObject()) {
ClassInfo clsInfo = ClassInfo.fromType(cls.root(), field.getType());
ClassNode fieldsCls = cls.dex().resolveClass(clsInfo);
ClassNode fieldsCls = cls.root().resolveClass(clsInfo);
ClassInfo parentClass = cls.getClassInfo().getParentClass();
if (fieldsCls != null
&& (inline || parentClass.equals(fieldsCls.getClassInfo()))) {
@ -171,7 +170,7 @@ public class ClassModifier extends AbstractVisitor {
if (!argType.isObject()) {
continue;
}
ClassNode argCls = cls.dex().resolveClass(argType);
ClassNode argCls = cls.root().resolveClass(argType);
if (argCls == null) {
// check if missing class from current top class
ClassInfo argClsInfo = ClassInfo.fromType(cls.root(), argType);
@ -268,7 +267,7 @@ public class ClassModifier extends AbstractVisitor {
// remove confirmed, change visibility and name if needed
if (!wrappedAccFlags.isPublic()) {
// must be public
FixAccessModifiers.changeVisibility(wrappedMth, AccessFlags.ACC_PUBLIC);
FixAccessModifiers.changeVisibility(wrappedMth, AccessFlags.PUBLIC);
}
String alias = mth.getAlias();
if (!Objects.equals(wrappedMth.getAlias(), alias)) {
@ -341,7 +340,7 @@ public class ClassModifier extends AbstractVisitor {
}
} else if (type == InsnType.IPUT) {
FieldInfo fldInfo = (FieldInfo) ((IndexInsnNode) insn).getIndex();
FieldNode fieldNode = mth.dex().resolveField(fldInfo);
FieldNode fieldNode = mth.root().resolveField(fldInfo);
if (fieldNode != null && fieldNode.contains(AFlag.DONT_GENERATE)) {
insn.add(AFlag.DONT_GENERATE);
}

View File

@ -114,13 +114,13 @@ public class ConstructorVisitor extends AbstractVisitor {
*/
@Nullable
private static ConstructorInsn processConstructor(MethodNode mth, ConstructorInsn co) {
MethodNode callMth = mth.dex().resolveMethod(co.getCallMth());
MethodNode callMth = mth.root().resolveMethod(co.getCallMth());
if (callMth == null
|| !callMth.getAccessFlags().isSynthetic()
|| !allArgsNull(co)) {
return null;
}
ClassNode classNode = mth.dex().resolveClass(callMth.getParentClass().getClassInfo());
ClassNode classNode = mth.root().resolveClass(callMth.getParentClass().getClassInfo());
if (classNode == null) {
return null;
}

View File

@ -7,33 +7,32 @@ import java.util.List;
import java.util.Set;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.FieldInitAttr;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.instructions.BaseInvokeNode;
import jadx.core.dex.instructions.IndexInsnNode;
import jadx.core.dex.instructions.InvokeNode;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.InsnWrapArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.mods.ConstructorInsn;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.parser.FieldInitAttr;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.exceptions.JadxException;
public class DependencyCollector extends AbstractVisitor {
@Override
public boolean visit(ClassNode cls) throws JadxException {
DexNode dex = cls.dex();
RootNode root = cls.root();
Set<ClassNode> depSet = new HashSet<>();
processClass(cls, dex, depSet);
processClass(cls, root, depSet);
for (ClassNode inner : cls.getInnerClasses()) {
processClass(inner, dex, depSet);
processClass(inner, root, depSet);
}
depSet.remove(cls);
@ -43,18 +42,18 @@ public class DependencyCollector extends AbstractVisitor {
return false;
}
private static void processClass(ClassNode cls, DexNode dex, Set<ClassNode> depList) {
addDep(dex, depList, cls.getSuperClass());
private static void processClass(ClassNode cls, RootNode root, Set<ClassNode> depList) {
addDep(root, depList, cls.getSuperClass());
for (ArgType iType : cls.getInterfaces()) {
addDep(dex, depList, iType);
addDep(root, depList, iType);
}
for (FieldNode fieldNode : cls.getFields()) {
addDep(dex, depList, fieldNode.getType());
addDep(root, depList, fieldNode.getType());
// process instructions from field init
FieldInitAttr fieldInitAttr = fieldNode.get(AType.FIELD_INIT);
if (fieldInitAttr != null && fieldInitAttr.getValueType() == FieldInitAttr.InitType.INSN) {
processInsn(dex, depList, fieldInitAttr.getInsn());
processInsn(root, depList, fieldInitAttr.getInsn());
}
}
// TODO: process annotations and generics
@ -62,80 +61,77 @@ public class DependencyCollector extends AbstractVisitor {
if (methodNode.isNoCode() || methodNode.contains(AType.JADX_ERROR)) {
continue;
}
processMethod(dex, depList, methodNode);
processMethod(root, depList, methodNode);
}
}
private static void processMethod(DexNode dex, Set<ClassNode> depList, MethodNode methodNode) {
addDep(dex, depList, methodNode.getParentClass());
addDep(dex, depList, methodNode.getReturnType());
private static void processMethod(RootNode root, Set<ClassNode> depList, MethodNode methodNode) {
addDep(root, depList, methodNode.getParentClass());
addDep(root, depList, methodNode.getReturnType());
for (ArgType arg : methodNode.getMethodInfo().getArgumentsTypes()) {
addDep(dex, depList, arg);
addDep(root, depList, arg);
}
for (BlockNode block : methodNode.getBasicBlocks()) {
for (InsnNode insnNode : block.getInstructions()) {
processInsn(dex, depList, insnNode);
processInsn(root, depList, insnNode);
}
}
}
// TODO: add custom instructions processing
private static void processInsn(DexNode dex, Set<ClassNode> depList, InsnNode insnNode) {
private static void processInsn(RootNode root, Set<ClassNode> depList, InsnNode insnNode) {
RegisterArg result = insnNode.getResult();
if (result != null) {
addDep(dex, depList, result.getType());
addDep(root, depList, result.getType());
}
for (InsnArg arg : insnNode.getArguments()) {
if (arg.isInsnWrap()) {
processInsn(dex, depList, ((InsnWrapArg) arg).getWrapInsn());
processInsn(root, depList, ((InsnWrapArg) arg).getWrapInsn());
} else {
addDep(dex, depList, arg.getType());
addDep(root, depList, arg.getType());
}
}
processCustomInsn(dex, depList, insnNode);
processCustomInsn(root, depList, insnNode);
}
private static void processCustomInsn(DexNode dex, Set<ClassNode> depList, InsnNode insn) {
private static void processCustomInsn(RootNode root, Set<ClassNode> depList, InsnNode insn) {
if (insn instanceof IndexInsnNode) {
Object index = ((IndexInsnNode) insn).getIndex();
if (index instanceof FieldInfo) {
addDep(dex, depList, ((FieldInfo) index).getDeclClass());
addDep(root, depList, ((FieldInfo) index).getDeclClass());
} else if (index instanceof ArgType) {
addDep(dex, depList, (ArgType) index);
addDep(root, depList, (ArgType) index);
}
} else if (insn instanceof InvokeNode) {
ClassInfo declClass = ((InvokeNode) insn).getCallMth().getDeclClass();
addDep(dex, depList, declClass);
} else if (insn instanceof ConstructorInsn) {
ClassInfo declClass = ((ConstructorInsn) insn).getCallMth().getDeclClass();
addDep(dex, depList, declClass);
} else if (insn instanceof BaseInvokeNode) {
ClassInfo declClass = ((BaseInvokeNode) insn).getCallMth().getDeclClass();
addDep(root, depList, declClass);
}
}
private static void addDep(DexNode dex, Set<ClassNode> depList, ArgType type) {
private static void addDep(RootNode root, Set<ClassNode> depList, ArgType type) {
if (type != null) {
if (type.isObject() && !type.isGenericType()) {
addDep(dex, depList, ClassInfo.fromType(dex.root(), type));
addDep(root, depList, ClassInfo.fromType(root, type));
ArgType[] genericTypes = type.getGenericTypes();
if (type.isGeneric() && genericTypes != null) {
for (ArgType argType : genericTypes) {
addDep(dex, depList, argType);
addDep(root, depList, argType);
}
}
} else if (type.isArray()) {
addDep(dex, depList, type.getArrayRootElement());
addDep(root, depList, type.getArrayRootElement());
}
}
}
private static void addDep(DexNode dex, Set<ClassNode> depList, ClassInfo clsInfo) {
private static void addDep(RootNode root, Set<ClassNode> depList, ClassInfo clsInfo) {
if (clsInfo != null) {
ClassNode node = dex.resolveClass(clsInfo);
addDep(dex, depList, node);
ClassNode node = root.resolveClass(clsInfo);
addDep(root, depList, node);
}
}
private static void addDep(DexNode dex, Set<ClassNode> depList, ClassNode clsNode) {
private static void addDep(RootNode root, Set<ClassNode> depList, ClassNode clsNode) {
if (clsNode != null) {
// add only top classes
depList.add(clsNode.getTopParentClass());

View File

@ -1,6 +1,5 @@
package jadx.core.dex.visitors;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.DebugChecks;
@ -19,9 +18,6 @@ public class DepthTraversal {
}
public static void visit(IDexTreeVisitor visitor, MethodNode mth) {
if (mth.contains(AType.JADX_ERROR)) {
return;
}
try {
visitor.visit(mth);
if (DebugChecks.checksEnabled) {

View File

@ -11,8 +11,7 @@ import java.util.stream.Collectors;
import org.jetbrains.annotations.Nullable;
import com.android.dx.rop.code.AccessFlags;
import jadx.api.plugins.input.data.AccessFlags;
import jadx.core.codegen.TypeGen;
import jadx.core.deobf.NameMapper;
import jadx.core.dex.attributes.AFlag;
@ -36,7 +35,6 @@ import jadx.core.dex.instructions.args.SSAVar;
import jadx.core.dex.instructions.mods.ConstructorInsn;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
@ -76,7 +74,7 @@ public class EnumVisitor extends AbstractVisitor {
if (!convertToEnum(cls)) {
AccessInfo accessFlags = cls.getAccessFlags();
if (accessFlags.isEnum()) {
cls.setAccessFlags(accessFlags.remove(AccessFlags.ACC_ENUM));
cls.setAccessFlags(accessFlags.remove(AccessFlags.ENUM));
cls.addAttr(AType.COMMENTS, "JADX INFO: Failed to restore enum class, 'enum' modifier removed");
}
}
@ -162,7 +160,7 @@ public class EnumVisitor extends AbstractVisitor {
FieldNode fieldNode = enumField.getField();
// use string arg from the constructor as enum field name
String name = getConstString(cls.dex(), co.getArg(0));
String name = getConstString(cls.root(), co.getArg(0));
if (name != null
&& !fieldNode.getAlias().equals(name)
&& NameMapper.isValidAndPrintable(name)
@ -196,7 +194,7 @@ public class EnumVisitor extends AbstractVisitor {
if (!regs.isEmpty()) {
cls.addWarnComment("Init of enum " + enumField.getField().getName() + " can be incorrect");
}
MethodNode ctrMth = cls.dex().resolveMethod(co.getCallMth());
MethodNode ctrMth = cls.root().resolveMethod(co.getCallMth());
if (ctrMth != null) {
markArgsForSkip(ctrMth);
}
@ -293,14 +291,14 @@ public class EnumVisitor extends AbstractVisitor {
return null;
}
ClassInfo clsInfo = co.getClassType();
ClassNode constrCls = cls.dex().resolveClass(clsInfo);
ClassNode constrCls = cls.root().resolveClass(clsInfo);
if (constrCls == null) {
return null;
}
if (!clsInfo.equals(cls.getClassInfo()) && !constrCls.getAccessFlags().isEnum()) {
return null;
}
MethodNode ctrMth = cls.dex().resolveMethod(co.getCallMth());
MethodNode ctrMth = cls.root().resolveMethod(co.getCallMth());
if (ctrMth == null) {
return null;
}
@ -415,10 +413,10 @@ public class EnumVisitor extends AbstractVisitor {
return null;
}
private String getConstString(DexNode dex, InsnArg arg) {
private String getConstString(RootNode root, InsnArg arg) {
if (arg.isInsnWrap()) {
InsnNode constInsn = ((InsnWrapArg) arg).getWrapInsn();
Object constValue = InsnUtils.getConstValueByInsn(dex, constInsn);
Object constValue = InsnUtils.getConstValueByInsn(root, constInsn);
if (constValue instanceof String) {
return (String) constValue;
}

View File

@ -8,6 +8,7 @@ import java.util.Set;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.FieldInitAttr;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.instructions.IndexInsnNode;
@ -20,7 +21,6 @@ import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.parser.FieldInitAttr;
import jadx.core.utils.BlockUtils;
import jadx.core.utils.InsnRemover;
import jadx.core.utils.exceptions.JadxException;
@ -155,7 +155,7 @@ public class ExtractFieldInit extends AbstractVisitor {
Set<FieldInfo> fields = new HashSet<>();
for (InsnNode insn : common.getPutInsns()) {
FieldInfo fieldInfo = (FieldInfo) ((IndexInsnNode) insn).getIndex();
FieldNode field = cls.dex().resolveField(fieldInfo);
FieldNode field = cls.root().resolveField(fieldInfo);
if (field == null) {
return;
}
@ -175,7 +175,7 @@ public class ExtractFieldInit extends AbstractVisitor {
}
for (InsnNode insn : common.getPutInsns()) {
FieldInfo fieldInfo = (FieldInfo) ((IndexInsnNode) insn).getIndex();
FieldNode field = cls.dex().resolveField(fieldInfo);
FieldNode field = cls.root().resolveField(fieldInfo);
addFieldInitAttr(common.getConstrMth(), field, insn);
}
}
@ -202,7 +202,7 @@ public class ExtractFieldInit extends AbstractVisitor {
// exclude fields from super classes
return false;
}
FieldNode fieldNode = cls.dex().resolveField(fieldInfo);
FieldNode fieldNode = cls.root().resolveField(fieldInfo);
if (fieldNode == null) {
// exclude inherited fields (not declared in this class)
return false;

View File

@ -1,7 +1,6 @@
package jadx.core.dex.visitors;
import com.android.dx.rop.code.AccessFlags;
import jadx.api.plugins.input.data.AccessFlags;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.nodes.ICodeNode;
@ -45,12 +44,12 @@ public class FixAccessModifiers extends AbstractVisitor {
private static int fixVisibility(MethodNode mth) {
if (mth.isVirtual()) {
// make virtual methods public
return AccessFlags.ACC_PUBLIC;
return AccessFlags.PUBLIC;
} else {
AccessInfo accessFlags = mth.getAccessFlags();
if (accessFlags.isAbstract()) {
// make abstract methods public
return AccessFlags.ACC_PUBLIC;
return AccessFlags.PUBLIC;
}
// enum constructor can't be public
if (accessFlags.isConstructor()
@ -63,7 +62,7 @@ public class FixAccessModifiers extends AbstractVisitor {
return -1;
}
// make other direct methods private
return AccessFlags.ACC_PRIVATE;
return AccessFlags.PRIVATE;
}
}
}

View File

@ -49,7 +49,7 @@ public class GenericTypesVisitor extends AbstractVisitor {
if (argType == null || argType.getGenericTypes() == null) {
return;
}
ClassNode cls = mth.dex().resolveClass(insn.getClassType());
ClassNode cls = mth.root().resolveClass(insn.getClassType());
if (cls != null && cls.getGenericTypeParameters().isEmpty()) {
return;
}

View File

@ -3,8 +3,7 @@ package jadx.core.dex.visitors;
import java.util.ArrayList;
import java.util.List;
import com.android.dx.rop.code.AccessFlags;
import jadx.api.plugins.input.data.AccessFlags;
import jadx.core.Consts;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
@ -87,7 +86,7 @@ public class MethodInlineVisitor extends AbstractVisitor {
}
private static boolean fixVisibilityOfInlineCode(MethodNode mth, InsnNode insn) {
int newVisFlag = AccessFlags.ACC_PUBLIC; // TODO: calculate more precisely
int newVisFlag = AccessFlags.PUBLIC; // TODO: calculate more precisely
InsnType insnType = insn.getType();
if (insnType == InsnType.INVOKE) {
InvokeNode invoke = (InvokeNode) insn;

View File

@ -9,10 +9,13 @@ import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.plugins.input.data.annotations.AnnotationVisibility;
import jadx.api.plugins.input.data.annotations.EncodedType;
import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.api.plugins.input.data.annotations.IAnnotation;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.AttrNode;
import jadx.core.dex.attributes.annotations.Annotation;
import jadx.core.dex.attributes.annotations.AnnotationsList;
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
import jadx.core.dex.info.FieldInfo;
@ -20,14 +23,14 @@ import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.ArithNode;
import jadx.core.dex.instructions.ConstClassNode;
import jadx.core.dex.instructions.ConstStringNode;
import jadx.core.dex.instructions.FillArrayNode;
import jadx.core.dex.instructions.FillArrayInsn;
import jadx.core.dex.instructions.FilledNewArrayNode;
import jadx.core.dex.instructions.IfNode;
import jadx.core.dex.instructions.IfOp;
import jadx.core.dex.instructions.IndexInsnNode;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.NewArrayNode;
import jadx.core.dex.instructions.SwitchNode;
import jadx.core.dex.instructions.SwitchInsn;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.LiteralArg;
@ -108,7 +111,7 @@ public class ModVisitor extends AbstractVisitor {
break;
case SWITCH:
replaceConstKeys(parentClass, (SwitchNode) insn);
replaceConstKeys(parentClass, (SwitchInsn) insn);
break;
case NEW_ARRAY:
@ -116,7 +119,7 @@ public class ModVisitor extends AbstractVisitor {
NewArrayNode newArrInsn = (NewArrayNode) insn;
InsnNode nextInsn = getFirstUseSkipMove(insn.getResult());
if (nextInsn != null && nextInsn.getType() == InsnType.FILL_ARRAY) {
FillArrayNode fillArrInsn = (FillArrayNode) nextInsn;
FillArrayInsn fillArrInsn = (FillArrayInsn) nextInsn;
if (checkArrSizes(mth, newArrInsn, fillArrInsn)) {
InsnNode filledArr = makeFilledArrayInsn(mth, newArrInsn, fillArrInsn);
replaceInsn(mth, block, i, filledArr);
@ -149,11 +152,13 @@ public class ModVisitor extends AbstractVisitor {
}
}
private static void replaceConstKeys(ClassNode parentClass, SwitchNode insn) {
for (int k = 0; k < insn.getCasesCount(); k++) {
FieldNode f = parentClass.getConstField(insn.getKeys()[k]);
private static void replaceConstKeys(ClassNode parentClass, SwitchInsn insn) {
int[] keys = insn.getKeys();
int len = keys.length;
for (int k = 0; k < len; k++) {
FieldNode f = parentClass.getConstField(keys[k]);
if (f != null) {
insn.getKeys()[k] = f;
insn.modifyKey(k, f);
}
}
}
@ -194,30 +199,30 @@ public class ModVisitor extends AbstractVisitor {
if (annotationsList == null) {
return;
}
for (Annotation annotation : annotationsList.getAll()) {
if (annotation.getVisibility() == Annotation.Visibility.SYSTEM) {
for (IAnnotation annotation : annotationsList.getAll()) {
if (annotation.getVisibility() == AnnotationVisibility.SYSTEM) {
continue;
}
for (Map.Entry<String, Object> entry : annotation.getValues().entrySet()) {
for (Map.Entry<String, EncodedValue> entry : annotation.getValues().entrySet()) {
entry.setValue(replaceConstValue(parentCls, entry.getValue()));
}
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private Object replaceConstValue(ClassNode parentCls, @Nullable Object value) {
if (value instanceof List) {
List listVal = (List) value;
@SuppressWarnings("unchecked")
private EncodedValue replaceConstValue(ClassNode parentCls, EncodedValue encodedValue) {
if (encodedValue.getType() == EncodedType.ENCODED_ARRAY) {
List<EncodedValue> listVal = (List<EncodedValue>) encodedValue.getValue();
if (!listVal.isEmpty()) {
listVal.replaceAll(v -> replaceConstValue(parentCls, v));
}
return listVal;
return new EncodedValue(EncodedType.ENCODED_ARRAY, listVal);
}
FieldNode constField = parentCls.getConstField(value);
FieldNode constField = parentCls.getConstField(encodedValue.getValue());
if (constField != null) {
return constField.getFieldInfo();
return new EncodedValue(EncodedType.ENCODED_FIELD, constField.getFieldInfo());
}
return value;
return encodedValue;
}
private static void replaceConst(MethodNode mth, ClassNode parentClass, BlockNode block, int i, InsnNode insn) {
@ -252,10 +257,10 @@ public class ModVisitor extends AbstractVisitor {
}
}
private static boolean checkArrSizes(MethodNode mth, NewArrayNode newArrInsn, FillArrayNode fillArrInsn) {
private static boolean checkArrSizes(MethodNode mth, NewArrayNode newArrInsn, FillArrayInsn fillArrInsn) {
int dataSize = fillArrInsn.getSize();
InsnArg arrSizeArg = newArrInsn.getArg(0);
Object value = InsnUtils.getConstValueByArg(mth.dex(), arrSizeArg);
Object value = InsnUtils.getConstValueByArg(mth.root(), arrSizeArg);
if (value instanceof LiteralArg) {
long literal = ((LiteralArg) value).getLiteral();
return dataSize == (int) literal;
@ -344,7 +349,7 @@ public class ModVisitor extends AbstractVisitor {
private static void processAnonymousConstructor(MethodNode mth, ConstructorInsn co) {
MethodInfo callMth = co.getCallMth();
MethodNode callMthNode = mth.dex().resolveMethod(callMth);
MethodNode callMthNode = mth.root().resolveMethod(callMth);
if (callMthNode == null) {
return;
}
@ -456,7 +461,7 @@ public class ModVisitor extends AbstractVisitor {
return parentInsn;
}
private static InsnNode makeFilledArrayInsn(MethodNode mth, NewArrayNode newArrayNode, FillArrayNode insn) {
private static InsnNode makeFilledArrayInsn(MethodNode mth, NewArrayNode newArrayNode, FillArrayInsn insn) {
ArgType insnArrayType = newArrayNode.getArrayType();
ArgType insnElementType = insnArrayType.getArrayElement();
ArgType elType = insn.getElementType();

View File

@ -0,0 +1,153 @@
package jadx.core.dex.visitors;
import org.jetbrains.annotations.Nullable;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.JumpInfo;
import jadx.core.dex.instructions.BaseInvokeNode;
import jadx.core.dex.instructions.FillArrayData;
import jadx.core.dex.instructions.FillArrayInsn;
import jadx.core.dex.instructions.FilledNewArrayNode;
import jadx.core.dex.instructions.GotoNode;
import jadx.core.dex.instructions.IfNode;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.SwitchData;
import jadx.core.dex.instructions.SwitchInsn;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.visitors.blocksmaker.BlockSplitter;
import jadx.core.utils.InsnUtils;
import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.exceptions.JadxRuntimeException;
@JadxVisitor(
name = "Process Instructions Visitor",
desc = "Init instructions info",
runBefore = {
BlockSplitter.class
}
)
public class ProcessInstructionsVisitor extends AbstractVisitor {
@Override
public void visit(MethodNode mth) throws JadxException {
if (mth.isNoCode()) {
return;
}
initJumps(mth, mth.getInstructions());
}
private static void initJumps(MethodNode mth, InsnNode[] insnByOffset) {
for (int offset = 0; offset < insnByOffset.length; offset++) {
InsnNode insn = insnByOffset[offset];
if (insn == null) {
continue;
}
switch (insn.getType()) {
case SWITCH:
SwitchInsn sw = (SwitchInsn) insn;
// default case
int nextInsnOffset = getNextInsnOffset(insnByOffset, offset);
if (nextInsnOffset != -1) {
addJump(mth, insnByOffset, offset, nextInsnOffset);
}
int dataTarget = sw.getDataTarget();
InsnNode switchDataInsn = getInsnAtOffset(insnByOffset, dataTarget);
if (switchDataInsn != null && switchDataInsn.getType() == InsnType.SWITCH_DATA) {
sw.attachSwitchData((SwitchData) switchDataInsn, nextInsnOffset);
} else {
throw new JadxRuntimeException("Payload for fill-array not found at " + InsnUtils.formatOffset(dataTarget));
}
for (int target : sw.getTargets()) {
addJump(mth, insnByOffset, offset, target);
}
break;
case IF:
int next = getNextInsnOffset(insnByOffset, offset);
if (next != -1) {
addJump(mth, insnByOffset, offset, next);
}
addJump(mth, insnByOffset, offset, ((IfNode) insn).getTarget());
break;
case GOTO:
addJump(mth, insnByOffset, offset, ((GotoNode) insn).getTarget());
break;
case INVOKE:
ArgType retType = ((BaseInvokeNode) insn).getCallMth().getReturnType();
mergeMoveResult(insnByOffset, offset, insn, retType);
break;
case FILLED_NEW_ARRAY:
ArgType arrType = ((FilledNewArrayNode) insn).getArrayType();
mergeMoveResult(insnByOffset, offset, insn, arrType);
break;
case FILL_ARRAY:
FillArrayInsn fillArrayInsn = (FillArrayInsn) insn;
int target = fillArrayInsn.getTarget();
InsnNode arrDataInsn = getInsnAtOffset(insnByOffset, target);
if (arrDataInsn != null && arrDataInsn.getType() == InsnType.FILL_ARRAY_DATA) {
fillArrayInsn.setArrayData((FillArrayData) arrDataInsn);
} else {
throw new JadxRuntimeException("Payload for fill-array not found at " + InsnUtils.formatOffset(target));
}
break;
default:
break;
}
}
}
private static void mergeMoveResult(InsnNode[] insnByOffset, int offset, InsnNode insn, ArgType resType) {
int nextInsnOffset = getNextInsnOffset(insnByOffset, offset);
if (nextInsnOffset == -1) {
return;
}
InsnNode nextInsn = insnByOffset[nextInsnOffset];
if (nextInsn.getType() != InsnType.MOVE_RESULT) {
return;
}
RegisterArg moveRes = nextInsn.getResult();
insn.setResult(moveRes.duplicate(resType));
insn.copyAttributesFrom(nextInsn);
insnByOffset[nextInsnOffset] = null;
}
private static void addJump(MethodNode mth, InsnNode[] insnByOffset, int offset, int target) {
try {
insnByOffset[target].addAttr(AType.JUMP, new JumpInfo(offset, target));
} catch (Exception e) {
mth.addError("Failed to set jump: " + InsnUtils.formatOffset(offset) + " -> " + InsnUtils.formatOffset(target), e);
}
}
public static int getNextInsnOffset(InsnNode[] insnByOffset, int offset) {
int len = insnByOffset.length;
for (int i = offset + 1; i < len; i++) {
InsnNode insnNode = insnByOffset[i];
if (insnNode != null && insnNode.getType() != InsnType.NOP) {
return i;
}
}
return -1;
}
@Nullable
private static InsnNode getInsnAtOffset(InsnNode[] insnByOffset, int offset) {
int len = insnByOffset.length;
for (int i = offset; i < len; i++) {
InsnNode insnNode = insnByOffset[i];
if (insnNode != null && insnNode.getType() != InsnType.NOP) {
return insnNode;
}
}
return null;
}
}

View File

@ -17,7 +17,7 @@ import jadx.core.dex.instructions.IndexInsnNode;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.InvokeNode;
import jadx.core.dex.instructions.NewArrayNode;
import jadx.core.dex.instructions.SwitchNode;
import jadx.core.dex.instructions.SwitchInsn;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.InsnWrapArg;
@ -26,10 +26,10 @@ import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.args.SSAVar;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
import jadx.core.utils.InsnList;
import jadx.core.utils.InsnRemover;
@ -80,7 +80,7 @@ public class ReSugarCode extends AbstractVisitor {
break;
case SWITCH:
processEnumSwitch(mth, (SwitchNode) insn);
processEnumSwitch(mth, (SwitchInsn) insn);
break;
default:
@ -156,7 +156,7 @@ public class ReSugarCode extends AbstractVisitor {
return false;
}
InsnArg indexArg = insn.getArg(1);
Object value = InsnUtils.getConstValueByArg(mth.dex(), indexArg);
Object value = InsnUtils.getConstValueByArg(mth.root(), indexArg);
if (value instanceof LiteralArg) {
int index = (int) ((LiteralArg) value).getLiteral();
return index == putIndex;
@ -164,7 +164,7 @@ public class ReSugarCode extends AbstractVisitor {
return false;
}
private static void processEnumSwitch(MethodNode mth, SwitchNode insn) {
private static void processEnumSwitch(MethodNode mth, SwitchInsn insn) {
InsnArg arg = insn.getArg(0);
if (!arg.isInsnWrap()) {
return;
@ -173,7 +173,7 @@ public class ReSugarCode extends AbstractVisitor {
if (wrapInsn.getType() != InsnType.AGET) {
return;
}
EnumMapInfo enumMapInfo = checkEnumMapAccess(mth.dex(), wrapInsn);
EnumMapInfo enumMapInfo = checkEnumMapAccess(mth.root(), wrapInsn);
if (enumMapInfo == null) {
return;
}
@ -184,8 +184,9 @@ public class ReSugarCode extends AbstractVisitor {
if (valueMap == null) {
return;
}
Object[] keys = insn.getKeys();
for (Object key : keys) {
int caseCount = insn.getKeys().length;
for (int i = 0; i < caseCount; i++) {
Object key = insn.getKey(i);
Object newKey = valueMap.get(key);
if (newKey == null) {
return;
@ -195,8 +196,8 @@ public class ReSugarCode extends AbstractVisitor {
if (!insn.replaceArg(arg, invArg)) {
return;
}
for (int i = 0; i < keys.length; i++) {
keys[i] = valueMap.get(keys[i]);
for (int i = 0; i < caseCount; i++) {
insn.modifyKey(i, valueMap.get(insn.getKey(i)));
}
enumMapField.add(AFlag.DONT_GENERATE);
checkAndHideClass(enumMapField.getParentClass());
@ -211,7 +212,7 @@ public class ReSugarCode extends AbstractVisitor {
for (BlockNode block : clsInitMth.getBasicBlocks()) {
for (InsnNode insn : block.getInstructions()) {
if (insn.getType() == InsnType.APUT) {
addToEnumMap(enumCls.dex(), mapAttr, insn);
addToEnumMap(enumCls.root(), mapAttr, insn);
}
}
}
@ -230,12 +231,12 @@ public class ReSugarCode extends AbstractVisitor {
return mapAttr.getMap(field);
}
private static void addToEnumMap(DexNode dex, EnumMapAttr mapAttr, InsnNode aputInsn) {
private static void addToEnumMap(RootNode root, EnumMapAttr mapAttr, InsnNode aputInsn) {
InsnArg litArg = aputInsn.getArg(2);
if (!litArg.isLiteral()) {
return;
}
EnumMapInfo mapInfo = checkEnumMapAccess(dex, aputInsn);
EnumMapInfo mapInfo = checkEnumMapAccess(root, aputInsn);
if (mapInfo == null) {
return;
}
@ -252,7 +253,7 @@ public class ReSugarCode extends AbstractVisitor {
if (!(index instanceof FieldInfo)) {
return;
}
FieldNode fieldNode = dex.resolveField((FieldInfo) index);
FieldNode fieldNode = root.resolveField((FieldInfo) index);
if (fieldNode == null) {
return;
}
@ -260,7 +261,7 @@ public class ReSugarCode extends AbstractVisitor {
mapAttr.add(field, literal, fieldNode);
}
public static EnumMapInfo checkEnumMapAccess(DexNode dex, InsnNode checkInsn) {
public static EnumMapInfo checkEnumMapAccess(RootNode root, InsnNode checkInsn) {
InsnArg sgetArg = checkInsn.getArg(0);
InsnArg invArg = checkInsn.getArg(1);
if (!sgetArg.isInsnWrap() || !invArg.isInsnWrap()) {
@ -275,7 +276,7 @@ public class ReSugarCode extends AbstractVisitor {
if (!inv.getCallMth().getShortId().equals("ordinal()I")) {
return null;
}
ClassNode enumCls = dex.resolveClass(inv.getCallMth().getDeclClass());
ClassNode enumCls = root.resolveClass(inv.getCallMth().getDeclClass());
if (enumCls == null || !enumCls.isEnum()) {
return null;
}
@ -283,7 +284,7 @@ public class ReSugarCode extends AbstractVisitor {
if (!(index instanceof FieldInfo)) {
return null;
}
FieldNode enumMapField = dex.resolveField((FieldInfo) index);
FieldNode enumMapField = root.resolveField((FieldInfo) index);
if (enumMapField == null || !enumMapField.getAccessFlags().isSynthetic()) {
return null;
}

View File

@ -1,5 +1,6 @@
package jadx.core.dex.visitors;
import java.io.File;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashSet;
@ -19,28 +20,25 @@ import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.files.FileUtils;
import jadx.core.utils.files.InputFile;
public class RenameVisitor extends AbstractVisitor {
@Override
public void init(RootNode root) {
List<DexNode> dexNodes = root.getDexNodes();
if (dexNodes.isEmpty()) {
List<File> inputFiles = root.getArgs().getInputFiles();
if (inputFiles.isEmpty()) {
return;
}
InputFile firstInputFile = dexNodes.get(0).getDexFile().getInputFile();
Path inputFilePath = firstInputFile.getFile().getAbsoluteFile().toPath();
Path inputFilePath = inputFiles.get(0).getAbsoluteFile().toPath();
String baseName = FileUtils.getPathBaseName(inputFilePath);
Path deobfMapPath = inputFilePath.getParent().resolve(baseName + ".jobf");
JadxArgs args = root.getArgs();
Deobfuscator deobfuscator = new Deobfuscator(args, dexNodes, deobfMapPath);
Deobfuscator deobfuscator = new Deobfuscator(args, root, deobfMapPath);
if (args.isDeobfuscationOn()) {
deobfuscator.execute();
}

View File

@ -9,6 +9,7 @@ import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.plugins.input.data.ILocalVar;
import jadx.core.Consts;
import jadx.core.deobf.NameMapper;
import jadx.core.dex.attributes.AFlag;
@ -111,15 +112,16 @@ public class DebugInfoApplyVisitor extends AbstractVisitor {
int startOffset = getInsnOffsetByArg(ssaVar.getAssign());
int endOffset = max.get();
int regNum = ssaVar.getRegNum();
for (LocalVar localVar : debugInfoAttr.getLocalVars()) {
for (ILocalVar localVar : debugInfoAttr.getLocalVars()) {
if (localVar.getRegNum() == regNum) {
int startAddr = localVar.getStartAddr();
int endAddr = localVar.getEndAddr();
int startAddr = localVar.getStartOffset();
int endAddr = localVar.getEndOffset();
if (isInside(startOffset, startAddr, endAddr) || isInside(endOffset, startAddr, endAddr)) {
if (Consts.DEBUG) {
LOG.debug("Apply debug info by offset for: {} to {}", ssaVar, localVar);
}
applyDebugInfo(mth, ssaVar, localVar.getType(), localVar.getName());
ArgType type = DebugInfoAttachVisitor.getVarType(localVar);
applyDebugInfo(mth, ssaVar, type, localVar.getName());
break;
}
}

View File

@ -0,0 +1,163 @@
package jadx.core.dex.visitors.debuginfo;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.plugins.input.data.IDebugInfo;
import jadx.api.plugins.input.data.ILocalVar;
import jadx.core.dex.attributes.nodes.LocalVarsDebugInfoAttr;
import jadx.core.dex.attributes.nodes.RegDebugInfoAttr;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.parser.SignatureParser;
import jadx.core.dex.visitors.AbstractVisitor;
import jadx.core.dex.visitors.JadxVisitor;
import jadx.core.dex.visitors.blocksmaker.BlockSplitter;
import jadx.core.dex.visitors.ssa.SSATransform;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxException;
import static jadx.core.codegen.CodeWriter.NL;
@JadxVisitor(
name = "Debug Info Parser",
desc = "Attach debug information (variable names and types, instruction lines)",
runBefore = {
BlockSplitter.class,
SSATransform.class
}
)
public class DebugInfoAttachVisitor extends AbstractVisitor {
private static final Logger LOG = LoggerFactory.getLogger(DebugInfoAttachVisitor.class);
@Override
public void visit(MethodNode mth) throws JadxException {
try {
IDebugInfo debugInfo = mth.getDebugInfo();
if (debugInfo != null) {
processDebugInfo(mth, debugInfo);
}
} catch (Exception e) {
mth.addComment("JADX WARNING: Error to parse debug info: "
+ ErrorsCounter.formatMsg(mth, e.getMessage())
+ NL + Utils.getStackTrace(e));
}
}
private void processDebugInfo(MethodNode mth, IDebugInfo debugInfo) {
InsnNode[] insnArr = mth.getInstructions();
attachSourceLines(debugInfo.getSourceLineMapping(), insnArr);
attachDebugInfo(mth, debugInfo.getLocalVars(), insnArr);
setMethodSourceLine(mth, insnArr);
}
private void attachSourceLines(Map<Integer, Integer> lineMapping, InsnNode[] insnArr) {
for (InsnNode insn : insnArr) {
if (insn != null) {
Integer sourceLine = lineMapping.get(insn.getOffset());
if (sourceLine != null) {
insn.setSourceLine(sourceLine);
}
}
}
}
private void attachDebugInfo(MethodNode mth, List<ILocalVar> localVars, InsnNode[] insnArr) {
if (localVars.isEmpty()) {
return;
}
for (ILocalVar var : localVars) {
int regNum = var.getRegNum();
int start = var.getStartOffset();
int end = var.getEndOffset();
ArgType type = getVarType(var);
RegDebugInfoAttr debugInfoAttr = new RegDebugInfoAttr(type, var.getName());
if (start < 0) {
// attach to method arguments
RegisterArg thisArg = mth.getThisArg();
if (thisArg != null) {
attachDebugInfo(thisArg, debugInfoAttr, regNum);
}
for (RegisterArg arg : mth.getArgRegs()) {
attachDebugInfo(arg, debugInfoAttr, regNum);
}
start = 0;
}
for (int i = start; i <= end; i++) {
InsnNode insn = insnArr[i];
if (insn != null) {
attachDebugInfo(insn.getResult(), debugInfoAttr, regNum);
for (InsnArg arg : insn.getArguments()) {
attachDebugInfo(arg, debugInfoAttr, regNum);
}
}
}
}
mth.addAttr(new LocalVarsDebugInfoAttr(localVars));
}
private void attachDebugInfo(InsnArg arg, RegDebugInfoAttr debugInfoAttr, int regNum) {
if (arg instanceof RegisterArg) {
RegisterArg reg = (RegisterArg) arg;
if (regNum == reg.getRegNum()) {
reg.addAttr(debugInfoAttr);
}
}
}
public static ArgType getVarType(ILocalVar var) {
ArgType type = ArgType.parse(var.getType());
String sign = var.getSignature();
if (sign == null) {
return type;
}
try {
ArgType gType = new SignatureParser(sign).consumeType();
if (checkSignature(type, gType)) {
return gType;
}
} catch (Exception e) {
LOG.error("Can't parse signature for local variable: {}", sign, e);
}
return type;
}
private static boolean checkSignature(ArgType type, ArgType gType) {
boolean apply;
ArgType el = gType.getArrayRootElement();
if (el.isGeneric()) {
if (!type.getArrayRootElement().getObject().equals(el.getObject())) {
LOG.warn("Generic type in debug info not equals: {} != {}", type, gType);
}
apply = true;
} else {
apply = el.isGenericType();
}
return apply;
}
/**
* Set method source line from first instruction
*/
private void setMethodSourceLine(MethodNode mth, InsnNode[] insnArr) {
for (InsnNode insn : insnArr) {
if (insn != null) {
int line = insn.getSourceLine();
if (line != 0) {
mth.setSourceLine(line - 1);
return;
}
}
}
}
}

View File

@ -1,119 +0,0 @@
package jadx.core.dex.visitors.debuginfo;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.Consts;
import jadx.core.dex.attributes.nodes.LocalVarsDebugInfoAttr;
import jadx.core.dex.attributes.nodes.RegDebugInfoAttr;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.visitors.AbstractVisitor;
import jadx.core.dex.visitors.JadxVisitor;
import jadx.core.dex.visitors.blocksmaker.BlockSplitter;
import jadx.core.dex.visitors.ssa.SSATransform;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxException;
import static jadx.core.codegen.CodeWriter.NL;
@JadxVisitor(
name = "Debug Info Parser",
desc = "Parse debug information (variable names and types, instruction lines)",
runBefore = {
BlockSplitter.class,
SSATransform.class
}
)
public class DebugInfoParseVisitor extends AbstractVisitor {
private static final Logger LOG = LoggerFactory.getLogger(DebugInfoParseVisitor.class);
@Override
public void visit(MethodNode mth) throws JadxException {
try {
int debugOffset = mth.getDebugInfoOffset();
if (debugOffset > 0 && mth.dex().checkOffset(debugOffset)) {
processDebugInfo(mth, debugOffset);
}
} catch (Exception e) {
mth.addComment("JADX WARNING: Error to parse debug info: "
+ ErrorsCounter.formatMsg(mth, e.getMessage())
+ NL + Utils.getStackTrace(e));
}
}
private void processDebugInfo(MethodNode mth, int debugOffset) {
InsnNode[] insnArr = mth.getInstructions();
DebugInfoParser debugInfoParser = new DebugInfoParser(mth, debugOffset, insnArr);
List<LocalVar> localVars = debugInfoParser.process();
attachDebugInfo(mth, localVars, insnArr);
setMethodSourceLine(mth, insnArr);
}
private void attachDebugInfo(MethodNode mth, List<LocalVar> localVars, InsnNode[] insnArr) {
if (localVars.isEmpty()) {
return;
}
if (Consts.DEBUG) {
LOG.debug("Parsed debug info for {}: ", mth);
localVars.forEach(v -> LOG.debug(" {}", v));
}
localVars.forEach(var -> {
int start = var.getStartAddr();
int end = var.getEndAddr();
RegDebugInfoAttr debugInfoAttr = new RegDebugInfoAttr(var);
if (start < 0) {
// attach to method arguments
RegisterArg thisArg = mth.getThisArg();
if (thisArg != null) {
attachDebugInfo(thisArg, var, debugInfoAttr);
}
for (RegisterArg arg : mth.getArgRegs()) {
attachDebugInfo(arg, var, debugInfoAttr);
}
start = 0;
}
for (int i = start; i <= end; i++) {
InsnNode insn = insnArr[i];
if (insn != null) {
attachDebugInfo(insn.getResult(), var, debugInfoAttr);
for (InsnArg arg : insn.getArguments()) {
attachDebugInfo(arg, var, debugInfoAttr);
}
}
}
});
mth.addAttr(new LocalVarsDebugInfoAttr(localVars));
}
private void attachDebugInfo(InsnArg arg, LocalVar var, RegDebugInfoAttr debugInfoAttr) {
if (arg instanceof RegisterArg) {
RegisterArg reg = (RegisterArg) arg;
if (var.getRegNum() == reg.getRegNum()) {
reg.addAttr(debugInfoAttr);
}
}
}
/**
* Set method source line from first instruction
*/
private void setMethodSourceLine(MethodNode mth, InsnNode[] insnArr) {
for (InsnNode insn : insnArr) {
if (insn != null) {
int line = insn.getSourceLine();
if (line != 0) {
mth.setSourceLine(line - 1);
return;
}
}
}
}
}

View File

@ -1,120 +0,0 @@
package jadx.core.dex.visitors.debuginfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.parser.SignatureParser;
import jadx.core.utils.InsnUtils;
public final class LocalVar {
private static final Logger LOG = LoggerFactory.getLogger(LocalVar.class);
private final int regNum;
private final String name;
private final ArgType type;
private boolean isEnd;
private int startAddr;
private int endAddr;
public LocalVar(DexNode dex, int rn, int nameId, int typeId, int signId) {
this(rn, dex.getString(nameId), dex.getType(typeId), dex.getString(signId));
}
public LocalVar(int regNum, String name, ArgType type) {
this(regNum, name, type, null);
}
public LocalVar(int regNum, String name, ArgType type, String sign) {
this.regNum = regNum;
this.name = name;
if (sign != null) {
try {
ArgType gType = new SignatureParser(sign).consumeType();
if (checkSignature(type, gType)) {
type = gType;
}
} catch (Exception e) {
LOG.error("Can't parse signature for local variable: {}", sign, e);
}
}
this.type = type;
}
private boolean checkSignature(ArgType type, ArgType gType) {
boolean apply;
ArgType el = gType.getArrayRootElement();
if (el.isGeneric()) {
if (!type.getArrayRootElement().getObject().equals(el.getObject())) {
LOG.warn("Generic type in debug info not equals: {} != {}", type, gType);
}
apply = true;
} else {
apply = el.isGenericType();
}
return apply;
}
public void start(int addr) {
this.isEnd = false;
this.startAddr = addr;
}
/**
* Sets end address of local variable
*
* @param addr address
* @return <b>true</b> if local variable was active, else <b>false</b>
*/
public boolean end(int addr) {
if (isEnd) {
return false;
}
this.isEnd = true;
this.endAddr = addr;
return true;
}
public int getRegNum() {
return regNum;
}
public String getName() {
return name;
}
public ArgType getType() {
return type;
}
public boolean isEnd() {
return isEnd;
}
public int getStartAddr() {
return startAddr;
}
public int getEndAddr() {
return endAddr;
}
@Override
public boolean equals(Object obj) {
return super.equals(obj);
}
@Override
public int hashCode() {
return super.hashCode();
}
@Override
public String toString() {
return InsnUtils.formatOffset(startAddr)
+ '-' + (isEnd ? InsnUtils.formatOffset(endAddr) : " ")
+ ": r" + regNum + " '" + name + "' " + type;
}
}

View File

@ -61,7 +61,7 @@ public class ProcessTryCatchRegions extends AbstractRegionVisitor {
// for each try block search nearest dominator block
for (TryCatchBlock tb : tryBlocks) {
if (tb.getHandlersCount() == 0) {
mth.addWarn("No exception handlers in catch block: " + tb);
// mth.addWarn("No exception handlers in catch block: " + tb);
continue;
}
processTryCatchBlock(mth, tb, tryBlocksMap);

View File

@ -22,7 +22,7 @@ import jadx.core.dex.attributes.nodes.LoopInfo;
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
import jadx.core.dex.instructions.IfNode;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.SwitchNode;
import jadx.core.dex.instructions.SwitchInsn;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.Edge;
@ -127,7 +127,7 @@ public class RegionMaker {
break;
case SWITCH:
next = processSwitch(r, block, (SwitchNode) insn, stack);
next = processSwitch(r, block, (SwitchInsn) insn, stack);
processed = true;
break;
@ -738,15 +738,14 @@ public class RegionMaker {
region.add(start);
}
private BlockNode processSwitch(IRegion currentRegion, BlockNode block, SwitchNode insn, RegionStack stack) {
private BlockNode processSwitch(IRegion currentRegion, BlockNode block, SwitchInsn insn, RegionStack stack) {
// map case blocks to keys
int len = insn.getTargets().length;
Map<BlockNode, List<Object>> blocksMap = new LinkedHashMap<>(len);
Object[] keysArr = insn.getKeys();
BlockNode[] targetBlocksArr = insn.getTargetBlocks();
for (int i = 0; i < len; i++) {
List<Object> keys = blocksMap.computeIfAbsent(targetBlocksArr[i], k -> new ArrayList<>(2));
keys.add(keysArr[i]);
keys.add(insn.getKey(i));
}
BlockNode defCase = insn.getDefTargetBlock();
if (defCase != null) {
@ -899,7 +898,7 @@ public class RegionMaker {
* 1. single 'default' case
* 2. filler cases if switch is 'packed' and 'default' case is empty
*/
private void removeEmptyCases(SwitchNode insn, SwitchRegion sw, BlockNode defCase) {
private void removeEmptyCases(SwitchInsn insn, SwitchRegion sw, BlockNode defCase) {
boolean defaultCaseIsEmpty;
if (defCase == null) {
defaultCaseIsEmpty = true;

View File

@ -4,7 +4,6 @@ import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.slf4j.Logger;
@ -12,7 +11,6 @@ import org.slf4j.LoggerFactory;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.FileUtils;
@ -62,14 +60,11 @@ public class ExportGradleProject {
}
private void skipGeneratedClasses() {
for (DexNode dexNode : root.getDexNodes()) {
List<ClassNode> classes = dexNode.getClasses();
for (ClassNode cls : classes) {
String shortName = cls.getClassInfo().getShortName();
if (IGNORE_CLS_NAMES.contains(shortName)) {
cls.add(AFlag.DONT_GENERATE);
LOG.debug("Skip class: {}", cls);
}
for (ClassNode cls : root.getClasses()) {
String shortName = cls.getClassInfo().getShortName();
if (IGNORE_CLS_NAMES.contains(shortName)) {
cls.add(AFlag.DONT_GENERATE);
LOG.debug("Skip class: {}", cls);
}
}
}

View File

@ -1,22 +0,0 @@
package jadx.core.utils;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import org.objectweb.asm.ClassReader;
public class AsmUtils {
private AsmUtils() {
}
public static String getNameFromClassFile(File file) throws IOException {
String className;
try (FileInputStream in = new FileInputStream(file)) {
ClassReader classReader = new ClassReader(in);
className = classReader.getClassName();
}
return className;
}
}

View File

@ -1,5 +1,6 @@
package jadx.core.utils;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
@ -36,7 +37,12 @@ public class ErrorsCounter {
}
public static String formatMsg(IDexNode node, String msg) {
return msg + " in " + node.typeName() + ": " + node + ", dex: " + node.dex().getDexFile().getName();
return msg + " in " + node.typeName() + ": " + node + ", file: " + getNodeFile(node);
}
private static String getNodeFile(IDexNode node) {
Path inputPath = node.getInputPath();
return inputPath == null ? "synthetic" : inputPath.toString();
}
private synchronized <N extends IDexNode & IAttributeNode> String addError(N node, String error, @Nullable Throwable e) {

View File

@ -6,10 +6,9 @@ import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.android.dx.io.instructions.DecodedInstruction;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.FieldInitAttr;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.instructions.ConstClassNode;
import jadx.core.dex.instructions.ConstStringNode;
@ -20,12 +19,10 @@ import jadx.core.dex.instructions.args.InsnWrapArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.args.SSAVar;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.parser.FieldInitAttr;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.dex.nodes.RootNode;
public class InsnUtils {
@ -34,23 +31,6 @@ public class InsnUtils {
private InsnUtils() {
}
public static int getArg(DecodedInstruction insn, int arg) {
switch (arg) {
case 0:
return insn.getA();
case 1:
return insn.getB();
case 2:
return insn.getC();
case 3:
return insn.getD();
case 4:
return insn.getE();
default:
throw new JadxRuntimeException("Wrong argument number: " + arg);
}
}
public static String formatOffset(int offset) {
if (offset < 0) {
return "?";
@ -77,7 +57,7 @@ public class InsnUtils {
*
* @return LiteralArg, String, ArgType or null
*/
public static Object getConstValueByArg(DexNode dex, InsnArg arg) {
public static Object getConstValueByArg(RootNode root, InsnArg arg) {
if (arg.isLiteral()) {
return arg;
}
@ -88,13 +68,13 @@ public class InsnUtils {
return null;
}
if (parInsn.getType() == InsnType.MOVE) {
return getConstValueByArg(dex, parInsn.getArg(0));
return getConstValueByArg(root, parInsn.getArg(0));
}
return getConstValueByInsn(dex, parInsn);
return getConstValueByInsn(root, parInsn);
}
if (arg.isInsnWrap()) {
InsnNode insn = ((InsnWrapArg) arg).getWrapInsn();
return getConstValueByInsn(dex, insn);
return getConstValueByInsn(root, insn);
}
return null;
}
@ -105,7 +85,7 @@ public class InsnUtils {
* @return LiteralArg, String, ArgType or null
*/
@Nullable
public static Object getConstValueByInsn(DexNode dex, InsnNode insn) {
public static Object getConstValueByInsn(RootNode root, InsnNode insn) {
switch (insn.getType()) {
case CONST:
return insn.getArg(0);
@ -115,13 +95,19 @@ public class InsnUtils {
return ((ConstClassNode) insn).getClsType();
case SGET:
FieldInfo f = (FieldInfo) ((IndexInsnNode) insn).getIndex();
FieldNode fieldNode = dex.root().deepResolveField(f);
FieldNode fieldNode = root.deepResolveField(f);
if (fieldNode == null) {
LOG.warn("Field {} not found in dex {}", f, dex);
LOG.warn("Field {} not found", f);
return null;
}
FieldInitAttr attr = fieldNode.get(AType.FIELD_INIT);
return attr != null ? attr.getValue() : null;
if (attr != null) {
if (attr.getValueType() == FieldInitAttr.InitType.CONST) {
return attr.getEncodedValue().getValue();
}
return attr.getInsn();
}
return null;
default:
return null;

View File

@ -1,12 +1,14 @@
package jadx.core.utils;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.nio.file.Files;
import java.nio.file.Path;
import org.jf.baksmali.Adaptors.ClassDefinition;
import org.jf.baksmali.BaksmaliOptions;
import org.jf.dexlib2.DexFileFactory;
import org.jf.dexlib2.dexbacked.DexBackedClassDef;
import org.jf.dexlib2.dexbacked.DexBackedDexFile;
import org.jf.smali.Smali;
@ -15,7 +17,6 @@ import org.jf.util.IndentingWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.dex.nodes.DexNode;
import jadx.core.utils.exceptions.JadxRuntimeException;
// TODO: move smali dependencies out from jadx-core
@ -33,10 +34,12 @@ public class SmaliUtils {
}
}
public static boolean getSmaliCode(DexNode dex, int clsDefOffset, StringWriter stringWriter) {
try {
Path path = dex.getDexFile().getPath();
DexBackedDexFile dexFile = DexFileFactory.loadDexFile(path.toFile(), null);
public static boolean getSmaliCode(Path path, int clsDefOffset, StringWriter stringWriter) {
if (clsDefOffset == 0) {
return false;
}
try (InputStream inputStream = new BufferedInputStream(Files.newInputStream(path))) {
DexBackedDexFile dexFile = DexBackedDexFile.fromInputStream(null, inputStream);
DexBackedClassDef dexBackedClassDef = new DexBackedClassDef(dexFile, clsDefOffset, 0);
getSmaliCode(dexBackedClassDef, stringWriter);
return true;

View File

@ -10,24 +10,24 @@ import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.android.dx.rop.code.AccessFlags;
import jadx.api.plugins.input.data.AccessFlags;
import jadx.api.plugins.input.data.annotations.EncodedType;
import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.core.codegen.ClassGen;
import jadx.core.codegen.CodeWriter;
import jadx.core.deobf.NameMapper;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.FieldInitAttr;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.ConstStorage;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.ProcessState;
import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.nodes.parser.FieldInitAttr;
import jadx.core.xmlgen.ResourceStorage;
import jadx.core.xmlgen.entry.ResourceEntry;
@ -44,7 +44,7 @@ public class AndroidResourcesUtils {
public static ClassNode searchAppResClass(RootNode root, ResourceStorage resStorage) {
String appPackage = root.getAppPackage();
String fullName = appPackage != null ? appPackage + ".R" : "R";
ClassNode resCls = root.searchClassByName(fullName);
ClassNode resCls = root.resolveClass(fullName);
if (resCls != null) {
addResourceFields(resCls, resStorage, true);
return resCls;
@ -82,11 +82,7 @@ public class AndroidResourcesUtils {
@Nullable
private static ClassNode makeClass(RootNode root, String clsName, ResourceStorage resStorage) {
List<DexNode> dexNodes = root.getDexNodes();
if (dexNodes.isEmpty()) {
return null;
}
ClassNode rCls = new ClassNode(dexNodes.get(0), clsName, AccessFlags.ACC_PUBLIC | AccessFlags.ACC_FINAL);
ClassNode rCls = new ClassNode(root, clsName, AccessFlags.PUBLIC | AccessFlags.FINAL);
rCls.addAttr(AType.COMMENTS, "This class is generated by JADX");
rCls.setState(ProcessState.PROCESS_COMPLETE);
return rCls;
@ -113,9 +109,10 @@ public class AndroidResourcesUtils {
}
FieldNode rField = typeCls.searchFieldByName(resName);
if (rField == null) {
FieldInfo rFieldInfo = FieldInfo.from(typeCls.dex(), typeCls.getClassInfo(), resName, ArgType.INT);
rField = new FieldNode(typeCls, rFieldInfo, AccessFlags.ACC_PUBLIC | AccessFlags.ACC_STATIC | AccessFlags.ACC_FINAL);
rField.addAttr(FieldInitAttr.constValue(resource.getId()));
FieldInfo rFieldInfo = FieldInfo.from(typeCls.root(), typeCls.getClassInfo(), resName, ArgType.INT);
rField = new FieldNode(typeCls, rFieldInfo, AccessFlags.PUBLIC | AccessFlags.STATIC | AccessFlags.FINAL);
EncodedValue value = new EncodedValue(EncodedType.ENCODED_INT, resource.getId());
rField.addAttr(FieldInitAttr.constValue(value));
typeCls.getFields().add(rField);
if (rClsExists) {
rField.addAttr(AType.COMMENTS, "added by JADX");
@ -134,8 +131,8 @@ public class AndroidResourcesUtils {
@NotNull
private static ClassNode addClassForResType(ClassNode resCls, boolean rClsExists, String typeName) {
ClassNode newTypeCls = new ClassNode(resCls.dex(), resCls.getFullName() + '$' + typeName,
AccessFlags.ACC_PUBLIC | AccessFlags.ACC_STATIC | AccessFlags.ACC_FINAL);
ClassNode newTypeCls = new ClassNode(resCls.root(), resCls.getFullName() + '$' + typeName,
AccessFlags.PUBLIC | AccessFlags.STATIC | AccessFlags.FINAL);
resCls.addInnerClass(newTypeCls);
if (rClsExists) {
newTypeCls.addAttr(AType.COMMENTS, "added by JADX");

View File

@ -1,40 +0,0 @@
package jadx.core.utils.files;
import java.nio.file.Path;
import com.android.dex.Dex;
public class DexFile {
private final InputFile inputFile;
private final String name;
private final Dex dexBuf;
private final Path path;
public DexFile(InputFile inputFile, String name, Dex dexBuf, Path path) {
this.inputFile = inputFile;
this.name = name;
this.dexBuf = dexBuf;
this.path = path;
}
public String getName() {
return name;
}
public Dex getDexBuf() {
return dexBuf;
}
public Path getPath() {
return path;
}
public InputFile getInputFile() {
return inputFile;
}
@Override
public String toString() {
return inputFile + (name.isEmpty() ? "" : ':' + name);
}
}

View File

@ -1,233 +0,0 @@
package jadx.core.utils.files;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.android.dex.Dex;
import com.android.dex.DexException;
import jadx.core.utils.AsmUtils;
import jadx.core.utils.SmaliUtils;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import static jadx.core.codegen.CodeWriter.NL;
import static jadx.core.utils.files.FileUtils.isApkFile;
import static jadx.core.utils.files.FileUtils.isZipDexFile;
public class InputFile {
private static final Logger LOG = LoggerFactory.getLogger(InputFile.class);
private final File file;
private final List<DexFile> dexFiles = new ArrayList<>();
public static void addFilesFrom(File file, List<InputFile> list, boolean skipSources) throws IOException, DecodeException {
InputFile inputFile = new InputFile(file);
inputFile.searchDexFiles(skipSources);
list.add(inputFile);
}
private InputFile(File file) throws IOException {
if (!file.exists()) {
throw new IOException("File not found: " + file.getAbsolutePath());
}
this.file = file;
}
private void searchDexFiles(boolean skipSources) throws IOException, DecodeException {
String fileName = file.getName();
if (fileName.endsWith(".dex")) {
addDexFile(fileName, file.toPath());
return;
}
if (fileName.endsWith(".smali")) {
Path output = FileUtils.createTempFile(".dex");
SmaliUtils.assembleDex(output.toAbsolutePath().toString(), file.getAbsolutePath());
addDexFile(fileName, output);
return;
}
if (fileName.endsWith(".class")) {
for (Path path : loadFromClassFile(file)) {
addDexFile(fileName, path);
}
return;
}
if (isApkFile(file) || isZipDexFile(file)) {
loadFromZip(".dex");
return;
}
if (fileName.endsWith(".jar") || fileName.endsWith(".aar")) {
// check if jar/aar contains '.dex' files
if (loadFromZip(".dex")) {
return;
}
if (fileName.endsWith(".jar")) {
for (Path path : loadFromJar(file.toPath())) {
addDexFile(fileName, path);
}
return;
}
if (fileName.endsWith(".aar")) {
loadFromZip(".jar");
return;
}
return;
}
if (skipSources) {
return;
}
LOG.warn("No dex files found in {}", file);
}
private boolean loadFromZip(String ext) throws IOException, DecodeException {
int index = 0;
try (ZipFile zf = new ZipFile(file)) {
// Input file could be .apk or .zip files
// we should consider the input file could contain only one single dex, multi-dex,
// or instantRun support dex for Android .apk files
String instantRunDexSuffix = "classes" + ext;
for (Enumeration<? extends ZipEntry> e = zf.entries(); e.hasMoreElements();) {
ZipEntry entry = e.nextElement();
if (!ZipSecurity.isValidZipEntry(entry)) {
continue;
}
String entryName = entry.getName();
try (InputStream inputStream = zf.getInputStream(entry)) {
if ((entryName.startsWith("classes") && entryName.endsWith(ext))
|| entryName.endsWith(instantRunDexSuffix)) {
switch (ext) {
case ".dex":
Path path = copyToTmpDex(entryName, inputStream);
if (addDexFile(entryName, path)) {
index++;
}
break;
case ".jar":
index++;
Path jarFile = FileUtils.createTempFile(entryName);
Files.copy(inputStream, jarFile, StandardCopyOption.REPLACE_EXISTING);
for (Path p : loadFromJar(jarFile)) {
addDexFile(entryName, p);
}
break;
default:
throw new JadxRuntimeException("Unexpected extension in zip: " + ext);
}
} else if (entryName.equals("instant-run.zip") && ext.equals(".dex")) {
Path jarFile = FileUtils.createTempFile("instant-run.zip");
Files.copy(inputStream, jarFile, StandardCopyOption.REPLACE_EXISTING);
InputFile tempFile = new InputFile(jarFile.toFile());
tempFile.loadFromZip(ext);
List<DexFile> files = tempFile.getDexFiles();
if (!files.isEmpty()) {
index += files.size();
this.dexFiles.addAll(files);
}
}
}
}
}
return index > 0;
}
private boolean addDexFile(String entryName, @Nullable Path filePath) {
if (filePath == null) {
return false;
}
Dex dexBuf = loadDexBufFromPath(filePath, entryName);
if (dexBuf == null) {
return false;
}
dexFiles.add(new DexFile(this, entryName, dexBuf, filePath));
return true;
}
@Nullable
private Dex loadDexBufFromPath(Path path, String entryName) {
try {
return new Dex(Files.readAllBytes(path));
} catch (DexException e) {
LOG.error("Failed to load dex file: {}, error: {}", entryName, e.getMessage());
} catch (Exception e) {
LOG.error("Failed to load dex file: {}, error: {}", entryName, e.getMessage(), e);
}
return null;
}
@Nullable
private Path copyToTmpDex(String entryName, InputStream inputStream) {
try {
Path path = FileUtils.createTempFile(".dex");
Files.copy(inputStream, path, StandardCopyOption.REPLACE_EXISTING);
return path;
} catch (Exception e) {
LOG.error("Failed to load file: {}, error: {}", entryName, e.getMessage(), e);
return null;
}
}
private static List<Path> loadFromJar(Path jar) throws DecodeException {
JavaToDex j2d = new JavaToDex();
try {
LOG.info("converting to dex: {} ...", jar.getFileName());
List<Path> pathList = j2d.convert(jar);
if (pathList.isEmpty()) {
throw new JadxException("Empty dx output");
}
if (LOG.isDebugEnabled()) {
LOG.debug("result dex files: {}", pathList);
}
return pathList;
} catch (Exception e) {
throw new DecodeException("java class to dex conversion error:" + NL + " " + e.getMessage(), e);
} finally {
if (j2d.isError()) {
LOG.warn("dx message: {}", j2d.getDxErrors());
}
}
}
private static List<Path> loadFromClassFile(File file) throws IOException, DecodeException {
Path outFile = FileUtils.createTempFile(".jar");
try (JarOutputStream jo = new JarOutputStream(Files.newOutputStream(outFile))) {
String clsName = AsmUtils.getNameFromClassFile(file);
if (clsName == null || !ZipSecurity.isValidZipEntryName(clsName)) {
throw new IOException("Can't read class name from file: " + file);
}
FileUtils.addFileToJar(jo, file, clsName + ".class");
}
return loadFromJar(outFile);
}
public File getFile() {
return file;
}
public List<DexFile> getDexFiles() {
return dexFiles;
}
@Override
public String toString() {
return file.getAbsolutePath();
}
}

View File

@ -1,74 +0,0 @@
package jadx.core.utils.files;
import java.io.ByteArrayOutputStream;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import com.android.dx.command.dexer.DxContext;
import com.android.dx.command.dexer.Main;
import com.android.dx.command.dexer.Main.Arguments;
import jadx.core.utils.exceptions.JadxException;
public class JavaToDex {
private static final String CHARSET_NAME = "UTF-8";
private static class DxArgs extends Arguments {
public DxArgs(DxContext context, String dexDir, String[] input) {
super(context);
outName = dexDir;
fileNames = input;
jarOutput = false;
multiDex = true;
optimize = true;
localInfo = true;
coreLibrary = true;
debug = true;
warnings = true;
minSdkVersion = 28;
}
}
private String dxErrors;
public List<Path> convert(Path jar) throws JadxException {
try (ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayOutputStream errOut = new ByteArrayOutputStream()) {
DxContext context = new DxContext(out, errOut);
Path dir = FileUtils.createTempDir("jar-to-dex-");
DxArgs args = new DxArgs(
context,
dir.toAbsolutePath().toString(),
new String[] { jar.toAbsolutePath().toString() });
int result = new Main(context).runDx(args);
dxErrors = errOut.toString(CHARSET_NAME);
if (result != 0) {
throw new JadxException("Java to dex conversion error, code: " + result);
}
List<Path> list = new ArrayList<>();
try (DirectoryStream<Path> ds = Files.newDirectoryStream(dir)) {
for (Path child : ds) {
list.add(child);
child.toFile().deleteOnExit();
}
}
return list;
} catch (Exception e) {
throw new JadxException("dx exception: " + e.getMessage(), e);
}
}
public String getDxErrors() {
return dxErrors;
}
public boolean isError() {
return dxErrors != null && !dxErrors.isEmpty();
}
}

View File

@ -1,20 +1,21 @@
package jadx.api;
import java.io.File;
import java.net.URL;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.Test;
import jadx.core.utils.files.FileUtils;
import jadx.core.utils.files.InputFileTest;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.notNullValue;
public class JadxDecompilerTest {
@Test
public void testExampleUsage() {
File sampleApk = InputFileTest.getFileFromSampleDir("app-with-fake-dex.apk");
File sampleApk = getFileFromSampleDir("app-with-fake-dex.apk");
File outDir = FileUtils.createTempDir("jadx-usage-example").toFile();
// test simple apk loading
@ -22,18 +23,28 @@ public class JadxDecompilerTest {
args.getInputFiles().add(sampleApk);
args.setOutDir(outDir);
JadxDecompiler jadx = new JadxDecompiler(args);
jadx.load();
jadx.save();
jadx.printErrorsReport();
try (JadxDecompiler jadx = new JadxDecompiler(args)) {
jadx.load();
jadx.save();
jadx.printErrorsReport();
// test class print
for (JavaClass cls : jadx.getClasses()) {
System.out.println(cls.getCode());
// test class print
for (JavaClass cls : jadx.getClasses()) {
System.out.println(cls.getCode());
}
assertThat(jadx.getClasses(), Matchers.hasSize(3));
assertThat(jadx.getErrorsCount(), Matchers.is(0));
}
}
assertThat(jadx.getClasses(), Matchers.hasSize(3));
assertThat(jadx.getErrorsCount(), Matchers.is(0));
private static final String TEST_SAMPLES_DIR = "test-samples/";
public static File getFileFromSampleDir(String fileName) {
URL resource = JadxDecompilerTest.class.getClassLoader().getResource(TEST_SAMPLES_DIR + fileName);
assertThat(resource, notNullValue());
String pathStr = resource.getFile();
return new File(pathStr);
}
// TODO add more tests

View File

@ -2,8 +2,7 @@ package jadx.core.dex.info;
import org.junit.jupiter.api.Test;
import com.android.dx.rop.code.AccessFlags;
import jadx.api.plugins.input.data.AccessFlags;
import jadx.core.dex.info.AccessInfo.AFType;
import static org.hamcrest.MatcherAssert.assertThat;
@ -14,8 +13,8 @@ public class AccessInfoTest {
@Test
public void changeVisibility() {
AccessInfo accessInfo = new AccessInfo(AccessFlags.ACC_PROTECTED | AccessFlags.ACC_STATIC, AFType.METHOD);
AccessInfo result = accessInfo.changeVisibility(AccessFlags.ACC_PUBLIC);
AccessInfo accessInfo = new AccessInfo(AccessFlags.PROTECTED | AccessFlags.STATIC, AFType.METHOD);
AccessInfo result = accessInfo.changeVisibility(AccessFlags.PUBLIC);
assertThat(result.isPublic(), is(true));
assertThat(result.isPrivate(), is(false));
@ -26,8 +25,8 @@ public class AccessInfoTest {
@Test
public void changeVisibilityNoOp() {
AccessInfo accessInfo = new AccessInfo(AccessFlags.ACC_PUBLIC, AFType.METHOD);
AccessInfo result = accessInfo.changeVisibility(AccessFlags.ACC_PUBLIC);
AccessInfo accessInfo = new AccessInfo(AccessFlags.PUBLIC, AFType.METHOD);
AccessInfo result = accessInfo.changeVisibility(AccessFlags.PUBLIC);
assertSame(accessInfo, result);
}
}

View File

@ -43,7 +43,7 @@ public class TypeCompareTest {
public void init() {
JadxArgs args = new JadxArgs();
RootNode root = new RootNode(args);
root.load(Collections.emptyList());
root.loadClasses(Collections.emptyList());
root.initClassPath();
compare = new TypeCompare(root);
}

View File

@ -1,37 +0,0 @@
package jadx.core.utils.files;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import org.junit.jupiter.api.Test;
import jadx.core.utils.exceptions.DecodeException;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.notNullValue;
public class InputFileTest {
private static final String TEST_SAMPLES_DIR = "test-samples/";
@Test
public void testApkWithFakeDex() throws IOException, DecodeException {
File sample = getFileFromSampleDir("app-with-fake-dex.apk");
List<InputFile> list = new ArrayList<>();
InputFile.addFilesFrom(sample, list, false);
assertThat(list, hasSize(1));
InputFile inputFile = list.get(0);
assertThat(inputFile.getDexFiles(), hasSize(1));
}
public static File getFileFromSampleDir(String fileName) {
URL resource = InputFileTest.class.getClassLoader().getResource(TEST_SAMPLES_DIR + fileName);
assertThat(resource, notNullValue());
String pathStr = resource.getFile();
return new File(pathStr);
}
}

View File

@ -104,6 +104,8 @@ public abstract class IntegrationTest extends TestUtils {
DebugChecks.checksEnabled = true;
}
private JadxDecompiler jadxDecompiler;
@BeforeEach
public void init() {
this.deleteTmpFiles = true;
@ -124,6 +126,9 @@ public abstract class IntegrationTest extends TestUtils {
@AfterEach
public void after() {
FileUtils.clearTempRootDir();
if (jadxDecompiler != null) {
jadxDecompiler.close();
}
}
public String getTestName() {
@ -146,14 +151,14 @@ public abstract class IntegrationTest extends TestUtils {
}
public ClassNode getClassNodeFromFile(File file, String clsName) {
JadxDecompiler d = loadFiles(Collections.singletonList(file));
RootNode root = JadxInternalAccess.getRoot(d);
jadxDecompiler = loadFiles(Collections.singletonList(file));
RootNode root = JadxInternalAccess.getRoot(jadxDecompiler);
ClassNode cls = root.searchClassByName(clsName);
ClassNode cls = root.resolveClass(clsName);
assertThat("Class not found: " + clsName, cls, notNullValue());
assertThat(clsName, is(cls.getClassInfo().getFullName()));
decompileAndCheck(d, Collections.singletonList(cls));
decompileAndCheck(jadxDecompiler, Collections.singletonList(cls));
return cls;
}
@ -169,12 +174,13 @@ public abstract class IntegrationTest extends TestUtils {
protected JadxDecompiler loadFiles(List<File> inputFiles) {
JadxDecompiler d;
args.setInputFiles(inputFiles);
d = new JadxDecompiler(args);
try {
args.setInputFiles(inputFiles);
d = new JadxDecompiler(args);
d.load();
} catch (Exception e) {
e.printStackTrace();
d.close();
fail(e.getMessage());
return null;
}
@ -487,6 +493,7 @@ public abstract class IntegrationTest extends TestUtils {
}
protected void setFallback() {
disableCompilation();
this.args.setFallbackMode(true);
}

View File

@ -100,7 +100,7 @@ public abstract class BaseExternalTest extends IntegrationTest {
throw new JadxRuntimeException("Class process failed", e);
}
LOG.info("----------------------------------------------------------------");
LOG.info("Print class: {}, {}", classNode.getFullName(), classNode.dex());
LOG.info("Print class: {}, {}", classNode.getFullName(), classNode.getInputPath());
if (mthPattern != null) {
printMethods(classNode, mthPattern);
} else {

View File

@ -26,7 +26,7 @@ public class JadxClasspathTest {
@BeforeEach
public void initClsp() {
this.root = new RootNode(new JadxArgs());
this.root.load(Collections.emptyList());
this.root.loadClasses(Collections.emptyList());
this.root.initClassPath();
this.clsp = root.getClsp();
}

View File

@ -1,52 +0,0 @@
package jadx.tests.integration.others;
import java.io.EOFException;
import org.junit.jupiter.api.Test;
import com.android.dex.Code;
import com.android.dx.io.instructions.DecodedInstruction;
import com.android.dx.io.instructions.ShortArrayCodeInput;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.exceptions.DecodeException;
import jadx.tests.api.IntegrationTest;
import static jadx.tests.api.utils.JadxMatchers.containsOne;
import static org.hamcrest.MatcherAssert.assertThat;
public class TestLoopInTry2 extends IntegrationTest {
public static class TestCls {
private MethodNode method;
public DecodedInstruction[] insnArr;
public void test(Code mthCode) throws DecodeException {
short[] encodedInstructions = mthCode.getInstructions();
int size = encodedInstructions.length;
DecodedInstruction[] decoded = new DecodedInstruction[size];
ShortArrayCodeInput in = new ShortArrayCodeInput(encodedInstructions);
try {
while (in.hasMore()) {
decoded[in.cursor()] = DecodedInstruction.decode(in);
}
} catch (EOFException e) {
throw new DecodeException(method, "", e);
}
insnArr = decoded;
}
}
@Test
public void test() {
ClassNode cls = getClassNode(TestCls.class);
String code = cls.getCode().toString();
assertThat(code, containsOne("try {"));
assertThat(code, containsOne("while (in.hasMore()) {"));
assertThat(code, containsOne("decoded[in.cursor()] = DecodedInstruction.decode("));
assertThat(code, containsOne("} catch (EOFException e) {"));
assertThat(code, containsOne("throw new DecodeException"));
}
}

View File

@ -57,6 +57,7 @@ public class TestFinally extends IntegrationTest {
ClassNode cls = getClassNode(TestCls.class);
String code = cls.getCode().toString();
assertThat(code, containsOne("} finally {"));
assertThat(code, containsOne("cursor.getString(columnIndex);"));
assertThat(code, not(containsOne("String str = true;")));
}

View File

@ -49,6 +49,8 @@ public class TestTryCatchFinally8 extends IntegrationTest {
String code = cls.getCode().toString();
assertThat(code, containsString("try {"));
assertThat(code, containsString("} catch (IOException e) {"));
assertThat(code, containsString("} finally {"));
assertThat(code, containsString("file.delete();"));
}

View File

@ -2,11 +2,9 @@ package jadx.tests.integration.trycatch;
import org.junit.jupiter.api.Test;
import jadx.core.dex.nodes.ClassNode;
import jadx.tests.api.SmaliTest;
import static jadx.tests.api.utils.JadxMatchers.containsOne;
import static org.hamcrest.MatcherAssert.assertThat;
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
public class TestTryCatchLastInsn extends SmaliTest {
@ -16,7 +14,7 @@ public class TestTryCatchLastInsn extends SmaliTest {
? r1 = "result"; // String
try {
r1 = call(); // Exception
} catch(Exception e) {
} catch (Exception e) {
System.out.println(r1); // String
r1 = e;
}
@ -27,9 +25,9 @@ public class TestTryCatchLastInsn extends SmaliTest {
@Test
public void test() {
ClassNode cls = getClassNodeFromSmali();
String code = cls.getCode().toString();
assertThat(code, containsOne("return call();"));
assertThat(getClassNodeFromSmali())
.code()
.containsOne("return call();")
.containsOne("} catch (Exception e) {");
}
}

Some files were not shown because too many files have changed in this diff Show More