feat(gui): save usage data into disk cache
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
package jadx.api;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Path;
|
||||
@@ -24,11 +25,13 @@ import jadx.api.deobf.IAliasProvider;
|
||||
import jadx.api.deobf.IRenameCondition;
|
||||
import jadx.api.impl.AnnotatedCodeWriter;
|
||||
import jadx.api.impl.InMemoryCodeCache;
|
||||
import jadx.api.usage.IUsageInfoCache;
|
||||
import jadx.api.usage.impl.InMemoryUsageInfoCache;
|
||||
import jadx.core.deobf.DeobfAliasProvider;
|
||||
import jadx.core.deobf.DeobfCondition;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
|
||||
public class JadxArgs {
|
||||
public class JadxArgs implements Closeable {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JadxArgs.class);
|
||||
|
||||
public static final int DEFAULT_THREADS_COUNT = Math.max(1, Runtime.getRuntime().availableProcessors() / 2);
|
||||
@@ -44,6 +47,13 @@ public class JadxArgs {
|
||||
private File outDirRes;
|
||||
|
||||
private ICodeCache codeCache = new InMemoryCodeCache();
|
||||
|
||||
/**
|
||||
* Usage data cache. Saves use places of classes, methods and fields between code reloads.
|
||||
* Can be set to {@link jadx.api.usage.impl.EmptyUsageInfoCache} if code reload not needed.
|
||||
*/
|
||||
private IUsageInfoCache usageInfoCache = new InMemoryUsageInfoCache();
|
||||
|
||||
private Function<JadxArgs, ICodeWriter> codeWriterProvider = AnnotatedCodeWriter::new;
|
||||
|
||||
private int threadsCount = DEFAULT_THREADS_COUNT;
|
||||
@@ -148,16 +158,21 @@ public class JadxArgs {
|
||||
setOutDirRes(new File(rootDir, DEFAULT_RES_DIR));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
try {
|
||||
inputFiles = null;
|
||||
if (codeCache != null) {
|
||||
codeCache.close();
|
||||
}
|
||||
if (usageInfoCache != null) {
|
||||
usageInfoCache.close();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to close JadxArgs", e);
|
||||
} finally {
|
||||
codeCache = null;
|
||||
usageInfoCache = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -553,6 +568,14 @@ public class JadxArgs {
|
||||
this.codeWriterProvider = codeWriterProvider;
|
||||
}
|
||||
|
||||
public IUsageInfoCache getUsageInfoCache() {
|
||||
return usageInfoCache;
|
||||
}
|
||||
|
||||
public void setUsageInfoCache(IUsageInfoCache usageInfoCache) {
|
||||
this.usageInfoCache = usageInfoCache;
|
||||
}
|
||||
|
||||
public ICodeData getCodeData() {
|
||||
return codeData;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package jadx.api.usage;
|
||||
|
||||
import java.io.Closeable;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
|
||||
public interface IUsageInfoCache extends Closeable {
|
||||
|
||||
@Nullable
|
||||
IUsageInfoData get(RootNode root);
|
||||
|
||||
void set(RootNode root, IUsageInfoData data);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package jadx.api.usage;
|
||||
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
|
||||
public interface IUsageInfoData {
|
||||
|
||||
void apply();
|
||||
|
||||
void applyForClass(ClassNode cls);
|
||||
|
||||
void visitUsageData(IUsageInfoVisitor visitor);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package jadx.api.usage;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
|
||||
public interface IUsageInfoVisitor {
|
||||
|
||||
void visitClassDeps(ClassNode cls, List<ClassNode> deps);
|
||||
|
||||
void visitClassUsage(ClassNode cls, List<ClassNode> usage);
|
||||
|
||||
void visitClassUseInMethods(ClassNode cls, List<MethodNode> methods);
|
||||
|
||||
void visitFieldsUsage(FieldNode fld, List<MethodNode> methods);
|
||||
|
||||
void visitMethodsUsage(MethodNode mth, List<MethodNode> methods);
|
||||
|
||||
void visitComplete();
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package jadx.api.usage.impl;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.usage.IUsageInfoCache;
|
||||
import jadx.api.usage.IUsageInfoData;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
|
||||
public class EmptyUsageInfoCache implements IUsageInfoCache {
|
||||
@Override
|
||||
public @Nullable IUsageInfoData get(RootNode root) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(RootNode root, IUsageInfoData data) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package jadx.api.usage.impl;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.usage.IUsageInfoCache;
|
||||
import jadx.api.usage.IUsageInfoData;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
|
||||
public class InMemoryUsageInfoCache implements IUsageInfoCache {
|
||||
|
||||
private IUsageInfoData data;
|
||||
|
||||
/**
|
||||
* `data` field tied to root node instance, keep hash to reset cache on change
|
||||
*/
|
||||
private int rootNodeHash;
|
||||
|
||||
@Override
|
||||
public @Nullable IUsageInfoData get(RootNode root) {
|
||||
return rootNodeHash == root.hashCode() ? data : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(RootNode root, IUsageInfoData data) {
|
||||
this.rootNodeHash = root.hashCode();
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
this.rootNodeHash = 0;
|
||||
this.data = null;
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,7 @@ import jadx.api.plugins.input.data.attributes.types.InnerClassesAttr;
|
||||
import jadx.api.plugins.input.data.attributes.types.InnerClsInfo;
|
||||
import jadx.api.plugins.input.data.attributes.types.SourceFileAttr;
|
||||
import jadx.api.plugins.input.data.impl.ListConsumer;
|
||||
import jadx.api.usage.IUsageInfoData;
|
||||
import jadx.core.Consts;
|
||||
import jadx.core.ProcessClass;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
@@ -106,10 +107,10 @@ public class ClassNode extends NotificationAttrNode
|
||||
this.clsInfo = ClassInfo.fromType(root, ArgType.object(cls.getType()));
|
||||
this.packageNode = PackageNode.getForClass(root, clsInfo.getPackage(), this);
|
||||
this.clsData = cls.copy();
|
||||
initialLoad(clsData);
|
||||
load(clsData, false);
|
||||
}
|
||||
|
||||
private void initialLoad(IClassData cls) {
|
||||
private void load(IClassData cls, boolean reloading) {
|
||||
try {
|
||||
addAttrs(cls.getAttributes());
|
||||
this.accessFlags = new AccessInfo(getAccessFlags(cls), AFType.CLASS);
|
||||
@@ -119,13 +120,11 @@ public class ClassNode extends NotificationAttrNode
|
||||
ListConsumer<IFieldData, FieldNode> fieldsConsumer = new ListConsumer<>(fld -> FieldNode.build(this, fld));
|
||||
ListConsumer<IMethodData, MethodNode> methodsConsumer = new ListConsumer<>(mth -> MethodNode.build(this, mth));
|
||||
cls.visitFieldsAndMethods(fieldsConsumer, methodsConsumer);
|
||||
if (this.fields != null && this.methods != null) {
|
||||
// TODO: temporary solution for restore usage info in reloaded methods and fields
|
||||
restoreUsageData(this.fields, this.methods, fieldsConsumer.getResult(), methodsConsumer.getResult());
|
||||
}
|
||||
this.fields = fieldsConsumer.getResult();
|
||||
this.methods = methodsConsumer.getResult();
|
||||
|
||||
if (reloading) {
|
||||
restoreUsageData();
|
||||
}
|
||||
initStaticValues(fields);
|
||||
processAttributes(this);
|
||||
buildCache();
|
||||
@@ -139,21 +138,10 @@ public class ClassNode extends NotificationAttrNode
|
||||
}
|
||||
}
|
||||
|
||||
private void restoreUsageData(List<FieldNode> oldFields, List<MethodNode> oldMethods,
|
||||
List<FieldNode> newFields, List<MethodNode> newMethods) {
|
||||
Map<FieldInfo, FieldNode> oldFieldMap = Utils.groupBy(oldFields, FieldNode::getFieldInfo);
|
||||
for (FieldNode newField : newFields) {
|
||||
FieldNode oldField = oldFieldMap.get(newField.getFieldInfo());
|
||||
if (oldField != null) {
|
||||
newField.setUseIn(oldField.getUseIn());
|
||||
}
|
||||
}
|
||||
Map<MethodInfo, MethodNode> oldMethodsMap = Utils.groupBy(oldMethods, MethodNode::getMethodInfo);
|
||||
for (MethodNode newMethod : newMethods) {
|
||||
MethodNode oldMethod = oldMethodsMap.get(newMethod.getMethodInfo());
|
||||
if (oldMethod != null) {
|
||||
newMethod.setUseIn(oldMethod.getUseIn());
|
||||
}
|
||||
private void restoreUsageData() {
|
||||
IUsageInfoData usageInfoData = root.getArgs().getUsageInfoCache().get(root);
|
||||
if (usageInfoData != null) {
|
||||
usageInfoData.applyForClass(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -357,7 +345,7 @@ public class ClassNode extends NotificationAttrNode
|
||||
unload();
|
||||
clearAttributes();
|
||||
root().getConstValues().removeForClass(this);
|
||||
initialLoad(clsData);
|
||||
load(clsData, true);
|
||||
|
||||
innerClasses.forEach(ClassNode::deepUnload);
|
||||
}
|
||||
|
||||
@@ -472,6 +472,18 @@ public class RootNode {
|
||||
return deepResolveMethod(cls, mth.makeSignature(false));
|
||||
}
|
||||
|
||||
public @NotNull MethodNode resolveDirectMethod(String rawClsName, String mthShortId) {
|
||||
ClassNode clsNode = resolveClass(rawClsName);
|
||||
if (clsNode == null) {
|
||||
throw new RuntimeException("Class not found: " + rawClsName);
|
||||
}
|
||||
MethodNode methodNode = clsNode.searchMethodByShortId(mthShortId);
|
||||
if (methodNode == null) {
|
||||
throw new RuntimeException("Method not found: " + rawClsName + "." + mthShortId);
|
||||
}
|
||||
return methodNode;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private MethodNode deepResolveMethod(@NotNull ClassNode cls, String signature) {
|
||||
for (MethodNode m : cls.getMethods()) {
|
||||
|
||||
@@ -6,6 +6,8 @@ import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import jadx.api.usage.IUsageInfoData;
|
||||
import jadx.api.usage.IUsageInfoVisitor;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
@@ -14,7 +16,7 @@ import jadx.core.dex.nodes.RootNode;
|
||||
|
||||
import static jadx.core.utils.Utils.notEmpty;
|
||||
|
||||
public class UsageInfo {
|
||||
public class UsageInfo implements IUsageInfoData {
|
||||
private final RootNode root;
|
||||
|
||||
private final UseSet<ClassNode, ClassNode> clsDeps = new UseSet<>();
|
||||
@@ -27,6 +29,7 @@ public class UsageInfo {
|
||||
this.root = root;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply() {
|
||||
clsDeps.visit((cls, deps) -> cls.setDependencies(sortedList(deps)));
|
||||
clsUsage.visit((cls, deps) -> cls.setUseIn(sortedList(deps)));
|
||||
@@ -35,6 +38,29 @@ public class UsageInfo {
|
||||
mthUsage.visit((mth, methods) -> mth.setUseIn(sortedList(methods)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyForClass(ClassNode cls) {
|
||||
cls.setDependencies(sortedList(clsDeps.get(cls)));
|
||||
cls.setUseIn(sortedList(clsUsage.get(cls)));
|
||||
cls.setUseInMth(sortedList(clsUseInMth.get(cls)));
|
||||
for (FieldNode fld : cls.getFields()) {
|
||||
fld.setUseIn(sortedList(fieldUsage.get(fld)));
|
||||
}
|
||||
for (MethodNode mth : cls.getMethods()) {
|
||||
mth.setUseIn(sortedList(mthUsage.get(mth)));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitUsageData(IUsageInfoVisitor visitor) {
|
||||
clsDeps.visit((cls, deps) -> visitor.visitClassDeps(cls, sortedList(deps)));
|
||||
clsUsage.visit((cls, deps) -> visitor.visitClassUsage(cls, sortedList(deps)));
|
||||
clsUseInMth.visit((cls, methods) -> visitor.visitClassUseInMethods(cls, sortedList(methods)));
|
||||
fieldUsage.visit((field, methods) -> visitor.visitFieldsUsage(field, sortedList(methods)));
|
||||
mthUsage.visit((mth, methods) -> visitor.visitMethodsUsage(mth, sortedList(methods)));
|
||||
visitor.visitComplete();
|
||||
}
|
||||
|
||||
public void clsUse(ClassNode cls, ArgType useType) {
|
||||
processType(useType, depCls -> clsUse(cls, depCls));
|
||||
}
|
||||
@@ -101,6 +127,9 @@ public class UsageInfo {
|
||||
}
|
||||
|
||||
private static <T extends Comparable<T>> List<T> sortedList(Set<T> deps) {
|
||||
if (deps == null || deps.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<T> list = new ArrayList<>(deps);
|
||||
Collections.sort(list);
|
||||
return list;
|
||||
|
||||
@@ -3,6 +3,9 @@ package jadx.core.dex.visitors.usage;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.plugins.input.data.ICallSite;
|
||||
import jadx.api.plugins.input.data.ICodeReader;
|
||||
import jadx.api.plugins.input.data.IMethodHandle;
|
||||
@@ -10,6 +13,8 @@ import jadx.api.plugins.input.data.IMethodRef;
|
||||
import jadx.api.plugins.input.insns.InsnData;
|
||||
import jadx.api.plugins.input.insns.Opcode;
|
||||
import jadx.api.plugins.input.insns.custom.ICustomPayload;
|
||||
import jadx.api.usage.IUsageInfoCache;
|
||||
import jadx.api.usage.IUsageInfoData;
|
||||
import jadx.core.dex.info.FieldInfo;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
@@ -33,14 +38,39 @@ import jadx.core.utils.input.InsnDataUtils;
|
||||
}
|
||||
)
|
||||
public class UsageInfoVisitor extends AbstractVisitor {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(UsageInfoVisitor.class);
|
||||
|
||||
@Override
|
||||
public void init(RootNode root) {
|
||||
IUsageInfoCache usageCache = root.getArgs().getUsageInfoCache();
|
||||
IUsageInfoData usageInfoData = usageCache.get(root);
|
||||
if (usageInfoData != null) {
|
||||
try {
|
||||
apply(usageInfoData);
|
||||
return;
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to apply cached usage data", e);
|
||||
}
|
||||
}
|
||||
IUsageInfoData collectedInfoData = buildUsageData(root);
|
||||
usageCache.set(root, collectedInfoData);
|
||||
apply(collectedInfoData);
|
||||
}
|
||||
|
||||
private static void apply(IUsageInfoData usageInfoData) {
|
||||
long start = System.currentTimeMillis();
|
||||
usageInfoData.apply();
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Apply usage data in {}ms", System.currentTimeMillis() - start);
|
||||
}
|
||||
}
|
||||
|
||||
private static IUsageInfoData buildUsageData(RootNode root) {
|
||||
UsageInfo usageInfo = new UsageInfo(root);
|
||||
for (ClassNode cls : root.getClasses()) {
|
||||
processClass(cls, usageInfo);
|
||||
}
|
||||
usageInfo.apply();
|
||||
return usageInfo;
|
||||
}
|
||||
|
||||
private static void processClass(ClassNode cls, UsageInfo usageInfo) {
|
||||
|
||||
@@ -3,6 +3,7 @@ package jadx.core.utils.files;
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.Closeable;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
@@ -17,6 +18,7 @@ import java.nio.file.Paths;
|
||||
import java.nio.file.SimpleFileVisitor;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.nio.file.attribute.FileTime;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@@ -358,4 +360,24 @@ public class FileUtils {
|
||||
throw new JadxRuntimeException("Failed to build hash", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash timestamps of input files
|
||||
*/
|
||||
public static String buildInputsHash(List<Path> inputPaths) {
|
||||
try (ByteArrayOutputStream bout = new ByteArrayOutputStream();
|
||||
DataOutputStream data = new DataOutputStream(bout)) {
|
||||
List<Path> inputFiles = FileUtils.expandDirs(inputPaths);
|
||||
Collections.sort(inputFiles);
|
||||
data.write(inputPaths.size());
|
||||
data.write(inputFiles.size());
|
||||
for (Path inputFile : inputFiles) {
|
||||
FileTime modifiedTime = Files.getLastModifiedTime(inputFile);
|
||||
data.writeLong(modifiedTime.toMillis());
|
||||
}
|
||||
return FileUtils.md5Sum(bout.toByteArray());
|
||||
} catch (Exception e) {
|
||||
throw new JadxRuntimeException("Failed to build hash for inputs", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,18 +21,22 @@ import jadx.api.ResourceFile;
|
||||
import jadx.api.impl.InMemoryCodeCache;
|
||||
import jadx.api.impl.plugins.PluginsContext;
|
||||
import jadx.api.metadata.ICodeNodeRef;
|
||||
import jadx.api.usage.impl.EmptyUsageInfoCache;
|
||||
import jadx.api.usage.impl.InMemoryUsageInfoCache;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.ProcessState;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
import jadx.gui.cache.code.CodeStringCache;
|
||||
import jadx.gui.cache.code.disk.BufferCodeCache;
|
||||
import jadx.gui.cache.code.disk.DiskCodeCache;
|
||||
import jadx.gui.cache.usage.UsageInfoCache;
|
||||
import jadx.gui.plugins.context.GuiPluginsContext;
|
||||
import jadx.gui.settings.JadxProject;
|
||||
import jadx.gui.settings.JadxSettings;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
import jadx.gui.utils.CacheObject;
|
||||
import jadx.gui.utils.codecache.CodeStringCache;
|
||||
import jadx.gui.utils.codecache.disk.BufferCodeCache;
|
||||
import jadx.gui.utils.codecache.disk.DiskCodeCache;
|
||||
|
||||
import static jadx.core.dex.nodes.ProcessState.GENERATED_AND_UNLOADED;
|
||||
import static jadx.core.dex.nodes.ProcessState.NOT_LOADED;
|
||||
@@ -60,10 +64,10 @@ public class JadxWrapper {
|
||||
JadxArgs jadxArgs = getSettings().toJadxArgs();
|
||||
project.fillJadxArgs(jadxArgs);
|
||||
|
||||
this.decompiler = new JadxDecompiler(jadxArgs);
|
||||
this.guiPluginsContext = new GuiPluginsContext(mainWindow);
|
||||
this.decompiler.getPluginsContext().setGuiContext(guiPluginsContext);
|
||||
this.decompiler.load();
|
||||
decompiler = new JadxDecompiler(jadxArgs);
|
||||
initGuiPluginsContext();
|
||||
initUsageCache(jadxArgs);
|
||||
decompiler.load();
|
||||
initCodeCache();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
@@ -119,6 +123,25 @@ public class JadxWrapper {
|
||||
return new BufferCodeCache(diskCache);
|
||||
}
|
||||
|
||||
private void initUsageCache(JadxArgs jadxArgs) {
|
||||
switch (getSettings().getUsageCacheMode()) {
|
||||
case NONE:
|
||||
jadxArgs.setUsageInfoCache(new EmptyUsageInfoCache());
|
||||
break;
|
||||
case MEMORY:
|
||||
jadxArgs.setUsageInfoCache(new InMemoryUsageInfoCache());
|
||||
break;
|
||||
case DISK:
|
||||
jadxArgs.setUsageInfoCache(new UsageInfoCache(getProject().getCacheDir(), jadxArgs.getInputFiles()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void initGuiPluginsContext() {
|
||||
guiPluginsContext = new GuiPluginsContext(mainWindow);
|
||||
decompiler.getPluginsContext().setGuiContext(guiPluginsContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the complete list of classes
|
||||
*/
|
||||
@@ -201,6 +224,8 @@ public class JadxWrapper {
|
||||
return decompiler.getPluginsContext();
|
||||
}
|
||||
try (JadxDecompiler tmpDecompiler = new JadxDecompiler()) {
|
||||
// TODO: override input file checks
|
||||
tmpDecompiler.getArgs().setInputFile(FileUtils.createTempFile("tmp.txt").toFile());
|
||||
tmpDecompiler.load();
|
||||
return tmpDecompiler.getPluginsContext();
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package jadx.gui.utils.codecache;
|
||||
package jadx.gui.cache.code;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package jadx.gui.utils.codecache;
|
||||
package jadx.gui.cache.code;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package jadx.gui.utils.codecache;
|
||||
package jadx.gui.cache.code;
|
||||
|
||||
import jadx.api.ICodeCache;
|
||||
import jadx.api.ICodeInfo;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package jadx.gui.utils.codecache.disk;
|
||||
package jadx.gui.cache.code.disk;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Deque;
|
||||
+9
-10
@@ -1,4 +1,4 @@
|
||||
package jadx.gui.utils.codecache.disk;
|
||||
package jadx.gui.cache.code.disk;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
@@ -23,10 +23,9 @@ import jadx.api.metadata.ICodeAnnotation;
|
||||
import jadx.api.metadata.ICodeMetadata;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
import jadx.gui.utils.codecache.disk.adapters.CodeAnnotationAdapter;
|
||||
import jadx.gui.cache.code.disk.adapters.CodeAnnotationAdapter;
|
||||
import jadx.gui.cache.code.disk.adapters.DataAdapterHelper;
|
||||
|
||||
import static jadx.gui.utils.codecache.disk.adapters.DataAdapterHelper.readUVInt;
|
||||
import static jadx.gui.utils.codecache.disk.adapters.DataAdapterHelper.writeUVInt;
|
||||
import static java.nio.file.StandardOpenOption.CREATE;
|
||||
import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
|
||||
import static java.nio.file.StandardOpenOption.WRITE;
|
||||
@@ -70,8 +69,8 @@ public class CodeMetadataAdapter {
|
||||
private void writeLines(DataOutput out, Map<Integer, Integer> lines) throws IOException {
|
||||
out.writeInt(lines.size());
|
||||
for (Map.Entry<Integer, Integer> entry : lines.entrySet()) {
|
||||
writeUVInt(out, entry.getKey());
|
||||
writeUVInt(out, entry.getValue());
|
||||
DataAdapterHelper.writeUVInt(out, entry.getKey());
|
||||
DataAdapterHelper.writeUVInt(out, entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,8 +81,8 @@ public class CodeMetadataAdapter {
|
||||
}
|
||||
Map<Integer, Integer> lines = new HashMap<>(size);
|
||||
for (int i = 0; i < size; i++) {
|
||||
int key = readUVInt(in);
|
||||
int value = readUVInt(in);
|
||||
int key = DataAdapterHelper.readUVInt(in);
|
||||
int value = DataAdapterHelper.readUVInt(in);
|
||||
lines.put(key, value);
|
||||
}
|
||||
return lines;
|
||||
@@ -92,7 +91,7 @@ public class CodeMetadataAdapter {
|
||||
private void writeAnnotations(DataOutputStream out, Map<Integer, ICodeAnnotation> annotations) throws IOException {
|
||||
out.writeInt(annotations.size());
|
||||
for (Map.Entry<Integer, ICodeAnnotation> entry : annotations.entrySet()) {
|
||||
writeUVInt(out, entry.getKey());
|
||||
DataAdapterHelper.writeUVInt(out, entry.getKey());
|
||||
codeAnnotationAdapter.write(out, entry.getValue());
|
||||
}
|
||||
}
|
||||
@@ -104,7 +103,7 @@ public class CodeMetadataAdapter {
|
||||
}
|
||||
Map<Integer, ICodeAnnotation> map = new HashMap<>(size);
|
||||
for (int i = 0; i < size; i++) {
|
||||
int pos = readUVInt(in);
|
||||
int pos = DataAdapterHelper.readUVInt(in);
|
||||
ICodeAnnotation ann = codeAnnotationAdapter.read(in);
|
||||
if (ann != null) {
|
||||
map.put(pos, ann);
|
||||
+2
-26
@@ -1,8 +1,7 @@
|
||||
package jadx.gui.utils.codecache.disk;
|
||||
package jadx.gui.cache.code.disk;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.File;
|
||||
@@ -13,9 +12,7 @@ import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.attribute.FileTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -213,28 +210,7 @@ public class DiskCodeCache implements ICodeCache {
|
||||
return DATA_FORMAT_VERSION
|
||||
+ ":" + Jadx.getVersion()
|
||||
+ ":" + args.makeCodeArgsHash()
|
||||
+ ":" + buildInputsHash(inputFiles);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash timestamps of all input files
|
||||
*/
|
||||
private String buildInputsHash(List<File> inputs) {
|
||||
try (ByteArrayOutputStream bout = new ByteArrayOutputStream();
|
||||
DataOutputStream data = new DataOutputStream(bout)) {
|
||||
List<Path> inputPaths = Utils.collectionMap(inputs, File::toPath);
|
||||
List<Path> inputFiles = FileUtils.expandDirs(inputPaths);
|
||||
Collections.sort(inputFiles);
|
||||
data.write(inputs.size());
|
||||
data.write(inputFiles.size());
|
||||
for (Path inputFile : inputFiles) {
|
||||
FileTime modifiedTime = Files.getLastModifiedTime(inputFile);
|
||||
data.writeLong(modifiedTime.toMillis());
|
||||
}
|
||||
return FileUtils.md5Sum(bout.toByteArray());
|
||||
} catch (Exception e) {
|
||||
throw new JadxRuntimeException("Failed to build hash for inputs", e);
|
||||
}
|
||||
+ ":" + FileUtils.buildInputsHash(Utils.collectionMap(inputFiles, File::toPath));
|
||||
}
|
||||
|
||||
private int getClsId(String clsFullName) {
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package jadx.gui.utils.codecache.disk.adapters;
|
||||
package jadx.gui.cache.code.disk.adapters;
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.DataOutput;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package jadx.gui.utils.codecache.disk.adapters;
|
||||
package jadx.gui.cache.code.disk.adapters;
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.DataOutput;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package jadx.gui.utils.codecache.disk.adapters;
|
||||
package jadx.gui.cache.code.disk.adapters;
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.DataOutput;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package jadx.gui.utils.codecache.disk.adapters;
|
||||
package jadx.gui.cache.code.disk.adapters;
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.DataOutput;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package jadx.gui.utils.codecache.disk.adapters;
|
||||
package jadx.gui.cache.code.disk.adapters;
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.DataOutput;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package jadx.gui.utils.codecache.disk.adapters;
|
||||
package jadx.gui.cache.code.disk.adapters;
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.DataOutput;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package jadx.gui.utils.codecache.disk.adapters;
|
||||
package jadx.gui.cache.code.disk.adapters;
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.DataOutput;
|
||||
+2
-11
@@ -1,11 +1,10 @@
|
||||
package jadx.gui.utils.codecache.disk.adapters;
|
||||
package jadx.gui.cache.code.disk.adapters;
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.DataOutput;
|
||||
import java.io.IOException;
|
||||
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
|
||||
@@ -27,14 +26,6 @@ public class MethodNodeAdapter implements DataAdapter<MethodNode> {
|
||||
public MethodNode read(DataInput in) throws IOException {
|
||||
String cls = in.readUTF();
|
||||
String sign = in.readUTF();
|
||||
ClassNode clsNode = root.resolveClass(cls);
|
||||
if (clsNode == null) {
|
||||
throw new RuntimeException("Class not found: " + cls);
|
||||
}
|
||||
MethodNode methodNode = clsNode.searchMethodByShortId(sign);
|
||||
if (methodNode == null) {
|
||||
throw new RuntimeException("Method not found: " + cls + "." + sign);
|
||||
}
|
||||
return methodNode;
|
||||
return root.resolveDirectMethod(cls, sign);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package jadx.gui.utils.codecache.disk.adapters;
|
||||
package jadx.gui.cache.code.disk.adapters;
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.DataOutput;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package jadx.gui.utils.codecache.disk.adapters;
|
||||
package jadx.gui.cache.code.disk.adapters;
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.DataOutput;
|
||||
+5
-5
@@ -1,4 +1,4 @@
|
||||
package jadx.gui.utils.codecache.disk.adapters;
|
||||
package jadx.gui.cache.code.disk.adapters;
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.DataOutput;
|
||||
@@ -8,10 +8,10 @@ import jadx.api.metadata.annotations.VarNode;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
|
||||
import static jadx.gui.utils.codecache.disk.adapters.DataAdapterHelper.readNullableUTF;
|
||||
import static jadx.gui.utils.codecache.disk.adapters.DataAdapterHelper.readUVInt;
|
||||
import static jadx.gui.utils.codecache.disk.adapters.DataAdapterHelper.writeNullableUTF;
|
||||
import static jadx.gui.utils.codecache.disk.adapters.DataAdapterHelper.writeUVInt;
|
||||
import static jadx.gui.cache.code.disk.adapters.DataAdapterHelper.readNullableUTF;
|
||||
import static jadx.gui.cache.code.disk.adapters.DataAdapterHelper.readUVInt;
|
||||
import static jadx.gui.cache.code.disk.adapters.DataAdapterHelper.writeNullableUTF;
|
||||
import static jadx.gui.cache.code.disk.adapters.DataAdapterHelper.writeUVInt;
|
||||
|
||||
public class VarNodeAdapter implements DataAdapter<VarNode> {
|
||||
private final MethodNodeAdapter mthAdapter;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package jadx.gui.utils.codecache.disk.adapters;
|
||||
package jadx.gui.cache.code.disk.adapters;
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.DataOutput;
|
||||
@@ -0,0 +1,56 @@
|
||||
package jadx.gui.cache.usage;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
final class ClsUsageData {
|
||||
private final String rawName;
|
||||
|
||||
private List<String> clsDeps;
|
||||
private List<String> clsUsage;
|
||||
private List<MthRef> clsUseInMth;
|
||||
|
||||
private final Map<String, FldUsageData> fldUsage = new HashMap<>();
|
||||
private final Map<String, MthUsageData> mthUsage = new HashMap<>();
|
||||
|
||||
ClsUsageData(String rawName) {
|
||||
this.rawName = rawName;
|
||||
}
|
||||
|
||||
public String getRawName() {
|
||||
return rawName;
|
||||
}
|
||||
|
||||
public List<String> getClsDeps() {
|
||||
return clsDeps;
|
||||
}
|
||||
|
||||
public void setClsDeps(List<String> clsDeps) {
|
||||
this.clsDeps = clsDeps;
|
||||
}
|
||||
|
||||
public List<String> getClsUsage() {
|
||||
return clsUsage;
|
||||
}
|
||||
|
||||
public void setClsUsage(List<String> clsUsage) {
|
||||
this.clsUsage = clsUsage;
|
||||
}
|
||||
|
||||
public List<MthRef> getClsUseInMth() {
|
||||
return clsUseInMth;
|
||||
}
|
||||
|
||||
public void setClsUseInMth(List<MthRef> clsUseInMth) {
|
||||
this.clsUseInMth = clsUseInMth;
|
||||
}
|
||||
|
||||
public Map<String, FldUsageData> getFldUsage() {
|
||||
return fldUsage;
|
||||
}
|
||||
|
||||
public Map<String, MthUsageData> getMthUsage() {
|
||||
return mthUsage;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package jadx.gui.cache.usage;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import jadx.api.usage.IUsageInfoVisitor;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
final class CollectUsageData implements IUsageInfoVisitor {
|
||||
private final RawUsageData data;
|
||||
|
||||
public CollectUsageData(RawUsageData usageData) {
|
||||
data = usageData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitClassDeps(ClassNode cls, List<ClassNode> deps) {
|
||||
data.getClassData(cls).setClsDeps(clsNodesRef(deps));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitClassUsage(ClassNode cls, List<ClassNode> usage) {
|
||||
data.getClassData(cls).setClsUsage(clsNodesRef(usage));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitClassUseInMethods(ClassNode cls, List<MethodNode> methods) {
|
||||
data.getClassData(cls).setClsUseInMth(mthNodesRef(methods));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitFieldsUsage(FieldNode fld, List<MethodNode> methods) {
|
||||
data.getFieldData(fld).setUsage(mthNodesRef(methods));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitMethodsUsage(MethodNode mth, List<MethodNode> methods) {
|
||||
data.getMethodData(mth).setUsage(mthNodesRef(methods));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitComplete() {
|
||||
data.collectClassesWithoutData();
|
||||
}
|
||||
|
||||
private List<String> clsNodesRef(List<ClassNode> usage) {
|
||||
return Utils.collectionMap(usage, ClassNode::getRawName);
|
||||
}
|
||||
|
||||
private List<MthRef> mthNodesRef(List<MethodNode> methods) {
|
||||
return Utils.collectionMap(methods, m -> data.getMethodData(m).getMthRef());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package jadx.gui.cache.usage;
|
||||
|
||||
final class FldRef {
|
||||
private final String cls;
|
||||
private final String shortId;
|
||||
|
||||
FldRef(String cls, String shortId) {
|
||||
this.cls = cls;
|
||||
this.shortId = shortId;
|
||||
}
|
||||
|
||||
public String getCls() {
|
||||
return cls;
|
||||
}
|
||||
|
||||
public String getShortId() {
|
||||
return shortId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package jadx.gui.cache.usage;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
final class FldUsageData {
|
||||
private final FldRef fldRef;
|
||||
private List<MthRef> usage;
|
||||
|
||||
public FldUsageData(FldRef fldRef) {
|
||||
this.fldRef = fldRef;
|
||||
}
|
||||
|
||||
public FldRef getFldRef() {
|
||||
return fldRef;
|
||||
}
|
||||
|
||||
public List<MthRef> getUsage() {
|
||||
return usage;
|
||||
}
|
||||
|
||||
public void setUsage(List<MthRef> usage) {
|
||||
this.usage = usage;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package jadx.gui.cache.usage;
|
||||
|
||||
final class MthRef {
|
||||
private final String cls;
|
||||
private final String shortId;
|
||||
|
||||
MthRef(String cls, String shortId) {
|
||||
this.cls = cls;
|
||||
this.shortId = shortId;
|
||||
}
|
||||
|
||||
public String getCls() {
|
||||
return cls;
|
||||
}
|
||||
|
||||
public String getShortId() {
|
||||
return shortId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof MthRef)) {
|
||||
return false;
|
||||
}
|
||||
MthRef other = (MthRef) o;
|
||||
return cls.equals(other.cls)
|
||||
&& shortId.equals(other.shortId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return 31 * cls.hashCode() + shortId.hashCode();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package jadx.gui.cache.usage;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
final class MthUsageData {
|
||||
private final MthRef mthRef;
|
||||
private List<MthRef> usage;
|
||||
|
||||
public MthUsageData(MthRef mthRef) {
|
||||
this.mthRef = mthRef;
|
||||
}
|
||||
|
||||
public MthRef getMthRef() {
|
||||
return mthRef;
|
||||
}
|
||||
|
||||
public List<MthRef> getUsage() {
|
||||
return usage;
|
||||
}
|
||||
|
||||
public void setUsage(List<MthRef> usage) {
|
||||
this.usage = usage;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package jadx.gui.cache.usage;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
|
||||
class RawUsageData {
|
||||
|
||||
private final Map<String, ClsUsageData> clsMap = new HashMap<>();
|
||||
private List<String> classesWithoutData = Collections.emptyList();
|
||||
|
||||
public Map<String, ClsUsageData> getClsMap() {
|
||||
return clsMap;
|
||||
}
|
||||
|
||||
public List<String> getClassesWithoutData() {
|
||||
return classesWithoutData;
|
||||
}
|
||||
|
||||
public ClsUsageData getClassData(ClassNode cls) {
|
||||
return getClassData(cls.getRawName());
|
||||
}
|
||||
|
||||
public ClsUsageData getClassData(String clsRawName) {
|
||||
return clsMap.computeIfAbsent(clsRawName, ClsUsageData::new);
|
||||
}
|
||||
|
||||
public MthUsageData getMethodData(MethodNode mth) {
|
||||
ClassNode parentClass = mth.getParentClass();
|
||||
String shortId = mth.getMethodInfo().getShortId();
|
||||
return getClassData(parentClass).getMthUsage().computeIfAbsent(shortId,
|
||||
m -> new MthUsageData(new MthRef(parentClass.getRawName(), shortId)));
|
||||
}
|
||||
|
||||
public FldUsageData getFieldData(FieldNode fld) {
|
||||
ClassNode parentClass = fld.getParentClass();
|
||||
String shortId = fld.getFieldInfo().getShortId();
|
||||
return getClassData(parentClass).getFldUsage().computeIfAbsent(shortId,
|
||||
m -> new FldUsageData(new FldRef(parentClass.getRawName(), shortId)));
|
||||
}
|
||||
|
||||
public void collectClassesWithoutData() {
|
||||
Set<String> allClasses = new HashSet<>(clsMap.size() * 2);
|
||||
for (ClsUsageData usageData : clsMap.values()) {
|
||||
List<String> deps = usageData.getClsDeps();
|
||||
if (deps != null) {
|
||||
allClasses.addAll(deps);
|
||||
}
|
||||
List<String> usage = usageData.getClsUsage();
|
||||
if (usage != null) {
|
||||
allClasses.addAll(usage);
|
||||
}
|
||||
}
|
||||
allClasses.removeAll(clsMap.keySet());
|
||||
classesWithoutData = new ArrayList<>(allClasses);
|
||||
Collections.sort(classesWithoutData);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package jadx.gui.cache.usage;
|
||||
|
||||
public enum UsageCacheMode {
|
||||
NONE,
|
||||
MEMORY,
|
||||
DISK
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package jadx.gui.cache.usage;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.usage.IUsageInfoData;
|
||||
import jadx.api.usage.IUsageInfoVisitor;
|
||||
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.Utils;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
class UsageData implements IUsageInfoData {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(UsageData.class);
|
||||
|
||||
private final RootNode root;
|
||||
private final RawUsageData rawUsageData;
|
||||
|
||||
public UsageData(RootNode root, RawUsageData rawUsageData) {
|
||||
this.root = root;
|
||||
this.rawUsageData = rawUsageData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply() {
|
||||
for (ClsUsageData clsUsageData : rawUsageData.getClsMap().values()) {
|
||||
String clsRawName = clsUsageData.getRawName();
|
||||
ClassNode cls = root.resolveClass(clsRawName);
|
||||
if (cls == null) {
|
||||
throw new JadxRuntimeException("Failed to resolve class: " + clsRawName);
|
||||
}
|
||||
applyForClass(clsUsageData, clsRawName, cls);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyForClass(ClassNode cls) {
|
||||
String clsRawName = cls.getRawName();
|
||||
ClsUsageData clsUsageData = rawUsageData.getClsMap().get(clsRawName);
|
||||
if (clsUsageData == null) {
|
||||
LOG.debug("No usage data for class: {}", clsRawName);
|
||||
return;
|
||||
}
|
||||
applyForClass(clsUsageData, clsRawName, cls);
|
||||
}
|
||||
|
||||
private void applyForClass(ClsUsageData clsUsageData, String clsRawName, ClassNode cls) {
|
||||
cls.setDependencies(resolveClsList(clsUsageData.getClsDeps()));
|
||||
cls.setUseIn(resolveClsList(clsUsageData.getClsUsage()));
|
||||
cls.setUseInMth(resolveMthList(clsUsageData.getClsUseInMth()));
|
||||
for (Map.Entry<String, MthUsageData> entry : clsUsageData.getMthUsage().entrySet()) {
|
||||
MethodNode mth = cls.searchMethodByShortId(entry.getKey());
|
||||
if (mth == null) {
|
||||
throw new JadxRuntimeException("Method not found: " + clsRawName + "." + entry.getKey());
|
||||
}
|
||||
mth.setUseIn(resolveMthList(entry.getValue().getUsage()));
|
||||
}
|
||||
for (Map.Entry<String, FldUsageData> entry : clsUsageData.getFldUsage().entrySet()) {
|
||||
FieldNode fld = cls.searchFieldByShortId(entry.getKey());
|
||||
if (fld == null) {
|
||||
throw new JadxRuntimeException("Field not found: " + clsRawName + "." + entry.getKey());
|
||||
}
|
||||
fld.setUseIn(resolveMthList(entry.getValue().getUsage()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitUsageData(IUsageInfoVisitor visitor) {
|
||||
throw new JadxRuntimeException("Not implemented");
|
||||
}
|
||||
|
||||
private List<ClassNode> resolveClsList(List<String> clsList) {
|
||||
return Utils.collectionMap(clsList, root::resolveClass);
|
||||
}
|
||||
|
||||
private List<MethodNode> resolveMthList(List<MthRef> mthRefList) {
|
||||
return Utils.collectionMap(mthRefList, m -> root.resolveDirectMethod(m.getCls(), m.getShortId()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
package jadx.gui.cache.usage;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.usage.IUsageInfoData;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
import jadx.gui.cache.code.disk.adapters.DataAdapterHelper;
|
||||
|
||||
import static java.nio.file.StandardOpenOption.CREATE;
|
||||
import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
|
||||
import static java.nio.file.StandardOpenOption.WRITE;
|
||||
|
||||
public class UsageFileAdapter extends DataAdapterHelper {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(UsageFileAdapter.class);
|
||||
|
||||
private static final int USAGE_DATA_VERSION = 1;
|
||||
private static final byte[] JADX_USAGE_HEADER = "jadx.usage".getBytes(StandardCharsets.US_ASCII);
|
||||
|
||||
public static synchronized @Nullable RawUsageData load(Path usageFile, List<File> inputs) {
|
||||
if (!Files.isRegularFile(usageFile)) {
|
||||
return null;
|
||||
}
|
||||
long start = System.currentTimeMillis();
|
||||
try (DataInputStream in = new DataInputStream(new BufferedInputStream(Files.newInputStream(usageFile)))) {
|
||||
in.skipBytes(JADX_USAGE_HEADER.length);
|
||||
int dataVersion = in.readInt();
|
||||
if (dataVersion != USAGE_DATA_VERSION) {
|
||||
LOG.debug("Found old usage data format");
|
||||
FileUtils.deleteFileIfExists(usageFile);
|
||||
return null;
|
||||
}
|
||||
String inputsHash = buildInputsHash(inputs);
|
||||
String fileInputsHash = in.readUTF();
|
||||
if (!inputsHash.equals(fileInputsHash)) {
|
||||
LOG.debug("Found usage data with different inputs hash");
|
||||
FileUtils.deleteFileIfExists(usageFile);
|
||||
return null;
|
||||
}
|
||||
RawUsageData data = readData(in);
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Loaded usage data from disk cache, classes count: {}, time: {}ms, file: {}",
|
||||
data.getClsMap().size(), System.currentTimeMillis() - start, usageFile);
|
||||
}
|
||||
return data;
|
||||
} catch (Exception e) {
|
||||
try {
|
||||
FileUtils.deleteFileIfExists(usageFile);
|
||||
} catch (IOException ex) {
|
||||
// ignore
|
||||
}
|
||||
LOG.error("Failed to load usage data file", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static synchronized void save(IUsageInfoData data, Path usageFile, List<File> inputs) {
|
||||
long start = System.currentTimeMillis();
|
||||
FileUtils.makeDirsForFile(usageFile);
|
||||
String inputsHash = buildInputsHash(inputs);
|
||||
RawUsageData usageData = new RawUsageData();
|
||||
data.visitUsageData(new CollectUsageData(usageData));
|
||||
try (OutputStream fileOutput = Files.newOutputStream(usageFile, WRITE, CREATE, TRUNCATE_EXISTING);
|
||||
DataOutputStream out = new DataOutputStream(new BufferedOutputStream(fileOutput))) {
|
||||
out.write(JADX_USAGE_HEADER);
|
||||
out.writeInt(USAGE_DATA_VERSION);
|
||||
out.writeUTF(inputsHash);
|
||||
writeData(out, usageData);
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to save usage data file", e);
|
||||
try {
|
||||
FileUtils.deleteFileIfExists(usageFile);
|
||||
} catch (IOException ex) {
|
||||
LOG.error("Failed to delete usage data file: {}", usageFile, ex);
|
||||
}
|
||||
}
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Usage data saved, time: {}ms, file: {}", System.currentTimeMillis() - start, usageFile);
|
||||
}
|
||||
}
|
||||
|
||||
private static RawUsageData readData(DataInputStream in) throws IOException {
|
||||
RawUsageData data = new RawUsageData();
|
||||
int clsCount = readUVInt(in);
|
||||
int clsWithoutDataCount = readUVInt(in);
|
||||
|
||||
String[] clsNames = new String[clsCount + clsWithoutDataCount];
|
||||
ClsUsageData[] classes = new ClsUsageData[clsCount];
|
||||
int c = 0;
|
||||
for (int i = 0; i < clsCount; i++) {
|
||||
String clsRawName = in.readUTF();
|
||||
classes[i] = data.getClassData(clsRawName);
|
||||
clsNames[c++] = clsRawName;
|
||||
}
|
||||
for (int i = 0; i < clsWithoutDataCount; i++) {
|
||||
clsNames[c++] = in.readUTF();
|
||||
}
|
||||
int mthCount = readUVInt(in);
|
||||
MthRef[] methods = new MthRef[mthCount];
|
||||
for (int i = 0; i < mthCount; i++) {
|
||||
int clsId = readUVInt(in);
|
||||
String mthShortId = in.readUTF();
|
||||
ClsUsageData cls = classes[clsId];
|
||||
MthRef mthRef = new MthRef(cls.getRawName(), mthShortId);
|
||||
cls.getMthUsage().put(mthShortId, new MthUsageData(mthRef));
|
||||
methods[i] = mthRef;
|
||||
}
|
||||
for (int i = 0; i < clsCount; i++) {
|
||||
ClsUsageData cls = data.getClassData(clsNames[i]);
|
||||
cls.setClsDeps(readClsList(in, clsNames));
|
||||
cls.setClsUsage(readClsList(in, clsNames));
|
||||
cls.setClsUseInMth(readMthList(in, methods));
|
||||
|
||||
int mCount = readUVInt(in);
|
||||
for (int m = 0; m < mCount; m++) {
|
||||
MthRef mthRef = methods[readUVInt(in)];
|
||||
cls.getMthUsage().get(mthRef.getShortId())
|
||||
.setUsage(readMthList(in, methods));
|
||||
}
|
||||
int fCount = readUVInt(in);
|
||||
for (int f = 0; f < fCount; f++) {
|
||||
String fldShortId = in.readUTF();
|
||||
cls.getFldUsage().computeIfAbsent(fldShortId,
|
||||
fldId -> new FldUsageData(new FldRef(cls.getRawName(), fldId)))
|
||||
.setUsage(readMthList(in, methods));
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
private static void writeData(DataOutputStream out, RawUsageData usageData) throws IOException {
|
||||
Map<String, Integer> clsMap = new HashMap<>();
|
||||
Map<MthRef, Integer> mthMap = new HashMap<>();
|
||||
Map<String, ClsUsageData> clsDataMap = usageData.getClsMap();
|
||||
List<String> classes = new ArrayList<>(clsDataMap.keySet());
|
||||
Collections.sort(classes);
|
||||
List<String> classesWithoutData = usageData.getClassesWithoutData();
|
||||
|
||||
writeUVInt(out, classes.size());
|
||||
writeUVInt(out, classesWithoutData.size());
|
||||
int i = 0;
|
||||
for (String cls : classes) {
|
||||
out.writeUTF(cls);
|
||||
clsMap.put(cls, i++);
|
||||
}
|
||||
for (String cls : classesWithoutData) {
|
||||
out.writeUTF(cls);
|
||||
clsMap.put(cls, i++);
|
||||
}
|
||||
List<MthRef> methods = clsDataMap.values().stream()
|
||||
.flatMap(c -> c.getMthUsage().values().stream())
|
||||
.map(MthUsageData::getMthRef)
|
||||
.collect(Collectors.toList());
|
||||
writeUVInt(out, methods.size());
|
||||
int j = 0;
|
||||
for (MthRef mth : methods) {
|
||||
writeUVInt(out, clsMap.get(mth.getCls()));
|
||||
out.writeUTF(mth.getShortId());
|
||||
mthMap.put(mth, j++);
|
||||
}
|
||||
for (String cls : classes) {
|
||||
ClsUsageData clsData = clsDataMap.get(cls);
|
||||
writeClsList(out, clsMap, clsData.getClsDeps());
|
||||
writeClsList(out, clsMap, clsData.getClsUsage());
|
||||
writeMthList(out, mthMap, clsData.getClsUseInMth());
|
||||
|
||||
writeUVInt(out, clsData.getMthUsage().size());
|
||||
for (MthUsageData mthData : clsData.getMthUsage().values()) {
|
||||
writeUVInt(out, mthMap.get(mthData.getMthRef()));
|
||||
writeMthList(out, mthMap, mthData.getUsage());
|
||||
}
|
||||
|
||||
writeUVInt(out, clsData.getFldUsage().size());
|
||||
for (FldUsageData fldData : clsData.getFldUsage().values()) {
|
||||
out.writeUTF(fldData.getFldRef().getShortId());
|
||||
writeMthList(out, mthMap, fldData.getUsage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static List<String> readClsList(DataInputStream in, String[] classes) throws IOException {
|
||||
int count = readUVInt(in);
|
||||
if (count == 0) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<String> list = new ArrayList<>(count);
|
||||
for (int i = 0; i < count; i++) {
|
||||
list.add(classes[readUVInt(in)]);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private static void writeClsList(DataOutputStream out, Map<String, Integer> clsMap, List<String> clsList) throws IOException {
|
||||
if (Utils.isEmpty(clsList)) {
|
||||
writeUVInt(out, 0);
|
||||
return;
|
||||
}
|
||||
writeUVInt(out, clsList.size());
|
||||
for (String cls : clsList) {
|
||||
Integer clsId = clsMap.get(cls);
|
||||
if (clsId == null) {
|
||||
throw new JadxRuntimeException("Unknown class in usage: " + cls);
|
||||
}
|
||||
writeUVInt(out, clsId);
|
||||
}
|
||||
}
|
||||
|
||||
private static List<MthRef> readMthList(DataInputStream in, MthRef[] methods) throws IOException {
|
||||
int count = readUVInt(in);
|
||||
if (count == 0) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<MthRef> list = new ArrayList<>(count);
|
||||
for (int i = 0; i < count; i++) {
|
||||
list.add(methods[readUVInt(in)]);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private static void writeMthList(DataOutputStream out, Map<MthRef, Integer> mthMap, List<MthRef> mthList) throws IOException {
|
||||
if (Utils.isEmpty(mthList)) {
|
||||
writeUVInt(out, 0);
|
||||
return;
|
||||
}
|
||||
writeUVInt(out, mthList.size());
|
||||
for (MthRef mth : mthList) {
|
||||
writeUVInt(out, mthMap.get(mth));
|
||||
}
|
||||
}
|
||||
|
||||
private static String buildInputsHash(List<File> inputs) {
|
||||
List<Path> paths = inputs.stream()
|
||||
.filter(f -> !f.getName().endsWith(".jadx.kts"))
|
||||
.map(File::toPath)
|
||||
.collect(Collectors.toList());
|
||||
return FileUtils.buildInputsHash(paths);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package jadx.gui.cache.usage;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.usage.IUsageInfoCache;
|
||||
import jadx.api.usage.IUsageInfoData;
|
||||
import jadx.api.usage.impl.InMemoryUsageInfoCache;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
|
||||
public class UsageInfoCache implements IUsageInfoCache {
|
||||
|
||||
private static final Object LOAD_DATA_SYNC = new Object();
|
||||
|
||||
private final Path usageFile;
|
||||
private final List<File> inputs;
|
||||
private final InMemoryUsageInfoCache memCache = new InMemoryUsageInfoCache();
|
||||
private @Nullable RawUsageData rawUsageData;
|
||||
|
||||
public UsageInfoCache(Path cacheDir, List<File> inputFiles) {
|
||||
usageFile = cacheDir.resolve("usage");
|
||||
inputs = inputFiles;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable IUsageInfoData get(RootNode root) {
|
||||
IUsageInfoData memData = memCache.get(root);
|
||||
if (memData != null) {
|
||||
return memData;
|
||||
}
|
||||
synchronized (LOAD_DATA_SYNC) {
|
||||
if (rawUsageData == null) {
|
||||
rawUsageData = UsageFileAdapter.load(usageFile, inputs);
|
||||
}
|
||||
if (rawUsageData != null) {
|
||||
UsageData data = new UsageData(root, rawUsageData);
|
||||
memCache.set(root, data);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(RootNode root, IUsageInfoData data) {
|
||||
memCache.set(root, data);
|
||||
UsageFileAdapter.save(data, usageFile, inputs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
rawUsageData = null;
|
||||
memCache.close();
|
||||
}
|
||||
}
|
||||
@@ -7,9 +7,9 @@ import javax.swing.JOptionPane;
|
||||
|
||||
import jadx.api.ICodeCache;
|
||||
import jadx.gui.JadxWrapper;
|
||||
import jadx.gui.cache.code.FixedCodeCache;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.codecache.FixedCodeCache;
|
||||
|
||||
public class ExportTask extends CancelableBackgroundTask {
|
||||
|
||||
|
||||
@@ -34,13 +34,14 @@ import jadx.api.args.ResourceNameSource;
|
||||
import jadx.api.args.UserRenamesMappingsMode;
|
||||
import jadx.cli.JadxCLIArgs;
|
||||
import jadx.cli.LogHelper;
|
||||
import jadx.gui.cache.code.CodeCacheMode;
|
||||
import jadx.gui.cache.usage.UsageCacheMode;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
import jadx.gui.ui.codearea.EditorTheme;
|
||||
import jadx.gui.utils.FontUtils;
|
||||
import jadx.gui.utils.LafManager;
|
||||
import jadx.gui.utils.LangLocale;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.codecache.CodeCacheMode;
|
||||
|
||||
public class JadxSettings extends JadxCLIArgs {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JadxSettings.class);
|
||||
@@ -96,6 +97,7 @@ public class JadxSettings extends JadxCLIArgs {
|
||||
private String adbDialogPort = "5037";
|
||||
|
||||
private CodeCacheMode codeCacheMode = CodeCacheMode.DISK_WITH_CACHE;
|
||||
private UsageCacheMode usageCacheMode = UsageCacheMode.DISK;
|
||||
private boolean jumpOnDoubleClick = true;
|
||||
|
||||
/**
|
||||
@@ -668,6 +670,14 @@ public class JadxSettings extends JadxCLIArgs {
|
||||
this.codeCacheMode = codeCacheMode;
|
||||
}
|
||||
|
||||
public UsageCacheMode getUsageCacheMode() {
|
||||
return usageCacheMode;
|
||||
}
|
||||
|
||||
public void setUsageCacheMode(UsageCacheMode usageCacheMode) {
|
||||
this.usageCacheMode = usageCacheMode;
|
||||
}
|
||||
|
||||
public boolean isJumpOnDoubleClick() {
|
||||
return jumpOnDoubleClick;
|
||||
}
|
||||
|
||||
@@ -66,6 +66,8 @@ import jadx.api.impl.plugins.PluginsContext;
|
||||
import jadx.api.plugins.JadxPlugin;
|
||||
import jadx.api.plugins.options.JadxPluginOptions;
|
||||
import jadx.api.plugins.options.OptionDescription;
|
||||
import jadx.gui.cache.code.CodeCacheMode;
|
||||
import jadx.gui.cache.usage.UsageCacheMode;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
import jadx.gui.ui.codearea.EditorTheme;
|
||||
import jadx.gui.utils.FontUtils;
|
||||
@@ -73,7 +75,6 @@ import jadx.gui.utils.LafManager;
|
||||
import jadx.gui.utils.LangLocale;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
import jadx.gui.utils.codecache.CodeCacheMode;
|
||||
import jadx.gui.utils.ui.DocumentUpdateListener;
|
||||
|
||||
public class JadxSettingsWindow extends JDialog {
|
||||
@@ -450,6 +451,13 @@ public class JadxSettingsWindow extends JDialog {
|
||||
});
|
||||
String codeCacheModeToolTip = CodeCacheMode.buildToolTip();
|
||||
|
||||
JComboBox<UsageCacheMode> usageCacheModeComboBox = new JComboBox<>(UsageCacheMode.values());
|
||||
usageCacheModeComboBox.setSelectedItem(settings.getUsageCacheMode());
|
||||
usageCacheModeComboBox.addActionListener(e -> {
|
||||
settings.setUsageCacheMode((UsageCacheMode) usageCacheModeComboBox.getSelectedItem());
|
||||
needReload();
|
||||
});
|
||||
|
||||
JCheckBox showInconsistentCode = new JCheckBox();
|
||||
showInconsistentCode.setSelected(settings.isShowInconsistentCode());
|
||||
showInconsistentCode.addItemListener(e -> {
|
||||
@@ -583,6 +591,7 @@ public class JadxSettingsWindow extends JDialog {
|
||||
other.addRow(NLS.str("preferences.start_jobs"), autoStartJobs);
|
||||
other.addRow(NLS.str("preferences.decompilationMode"), decompilationModeComboBox);
|
||||
other.addRow(NLS.str("preferences.codeCacheMode"), codeCacheModeToolTip, codeCacheModeComboBox);
|
||||
other.addRow(NLS.str("preferences.usageCacheMode"), usageCacheModeComboBox);
|
||||
other.addRow(NLS.str("preferences.showInconsistentCode"), showInconsistentCode);
|
||||
other.addRow(NLS.str("preferences.escapeUnicode"), escapeUnicode);
|
||||
other.addRow(NLS.str("preferences.replaceConsts"), replaceConsts);
|
||||
|
||||
@@ -161,6 +161,7 @@ preferences.check_for_updates=Nach Updates beim Start suchen
|
||||
preferences.useDx=dx/d8 zur Konvertierung von Java Bytecode verwenden
|
||||
preferences.decompilationMode=Dekompilierungsmodus
|
||||
preferences.codeCacheMode=Cache-Code-Modus
|
||||
#preferences.usageCacheMode=Usage data cache mode
|
||||
preferences.showInconsistentCode=Inkonsistenten Code anzeigen
|
||||
preferences.escapeUnicode=Unicodezeichen escapen
|
||||
preferences.replaceConsts=Konstanten ersetzen
|
||||
|
||||
@@ -161,6 +161,7 @@ preferences.check_for_updates=Check for updates on startup
|
||||
preferences.useDx=Use dx/d8 to convert java bytecode
|
||||
preferences.decompilationMode=Decompilation mode
|
||||
preferences.codeCacheMode=Code cache mode
|
||||
preferences.usageCacheMode=Usage data cache mode
|
||||
preferences.showInconsistentCode=Show inconsistent code
|
||||
preferences.escapeUnicode=Escape unicode
|
||||
preferences.replaceConsts=Replace constants
|
||||
|
||||
@@ -161,6 +161,7 @@ preferences.check_for_updates=Buscar actualizaciones al iniciar
|
||||
#preferences.useDx=Use dx/d8 to convert java bytecode
|
||||
#preferences.decompilationMode=Decompilation mode
|
||||
#preferences.codeCacheMode=Code cache mode
|
||||
#preferences.usageCacheMode=Usage data cache mode
|
||||
preferences.showInconsistentCode=Mostrar código inconsistente
|
||||
preferences.escapeUnicode=Escape unicode
|
||||
preferences.replaceConsts=Reemplazar constantes
|
||||
|
||||
@@ -161,6 +161,7 @@ preferences.check_for_updates=시작시 업데이트 확인
|
||||
preferences.useDx=dx/d8을 사용하여 Java 바이트 코드 변환
|
||||
preferences.decompilationMode=디컴파일 모드
|
||||
preferences.codeCacheMode=코드 캐시 모드
|
||||
#preferences.usageCacheMode=Usage data cache mode
|
||||
preferences.showInconsistentCode=디컴파일 안된 코드 표시
|
||||
preferences.escapeUnicode=유니코드 이스케이프
|
||||
preferences.replaceConsts=상수 바꾸기
|
||||
|
||||
@@ -161,6 +161,7 @@ preferences.check_for_updates=Verificar por atualizações ao inicializar
|
||||
preferences.useDx=Usar dx/d8 para converter bytecode Java
|
||||
preferences.decompilationMode=Modo de descompilação
|
||||
preferences.codeCacheMode=Modo de cachê do código
|
||||
#preferences.usageCacheMode=Usage data cache mode
|
||||
preferences.showInconsistentCode=Mostrar código inconsistent
|
||||
preferences.escapeUnicode=Escapar unicode
|
||||
preferences.replaceConsts=Substituir constantes
|
||||
|
||||
@@ -161,6 +161,7 @@ preferences.check_for_updates=启动时检查更新
|
||||
preferences.useDx=使用 dx/d8 来转换java字节码
|
||||
preferences.decompilationMode=反编译模式
|
||||
preferences.codeCacheMode=代码缓存模式
|
||||
#preferences.usageCacheMode=Usage data cache mode
|
||||
preferences.showInconsistentCode=显示不一致的代码
|
||||
preferences.escapeUnicode=Unicode 字符转义
|
||||
preferences.replaceConsts=替换常量
|
||||
|
||||
@@ -161,6 +161,7 @@ preferences.check_for_updates=啟動時檢查更新
|
||||
preferences.useDx=使用 dx/d8 來轉換 Java 位元組碼
|
||||
preferences.decompilationMode=反編譯模式
|
||||
preferences.codeCacheMode=程式碼快取模式
|
||||
#preferences.usageCacheMode=Usage data cache mode
|
||||
preferences.showInconsistentCode=顯示不一致的程式碼
|
||||
preferences.escapeUnicode=Unicode 逸出
|
||||
preferences.replaceConsts=替換常數
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
package jadx.gui.utils.codecache;
|
||||
package jadx.gui.utils.cache.code;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
@@ -11,7 +11,7 @@ import org.slf4j.LoggerFactory;
|
||||
import jadx.api.ICodeInfo;
|
||||
import jadx.api.impl.NoOpCodeCache;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.gui.utils.codecache.disk.DiskCodeCache;
|
||||
import jadx.gui.cache.code.disk.DiskCodeCache;
|
||||
import jadx.tests.api.IntegrationTest;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
+3
-1
@@ -1,4 +1,4 @@
|
||||
package jadx.gui.utils.codecache.disk.adapters;
|
||||
package jadx.gui.utils.cache.code.disk.adapters;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
@@ -9,6 +9,8 @@ import java.io.IOException;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.gui.cache.code.disk.adapters.DataAdapterHelper;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class DataAdapterHelperTest {
|
||||
Reference in New Issue
Block a user