feat(gui): save usage data into disk cache

This commit is contained in:
Skylot
2022-10-24 20:47:24 +01:00
parent a89dbc1152
commit 6912ed40b4
53 changed files with 1023 additions and 105 deletions
+24 -1
View File
@@ -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,4 +1,4 @@
package jadx.gui.utils.codecache;
package jadx.gui.cache.code;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -1,4 +1,4 @@
package jadx.gui.utils.codecache;
package jadx.gui.cache.code;
import java.io.IOException;
import java.util.Map;
@@ -1,4 +1,4 @@
package jadx.gui.utils.codecache;
package jadx.gui.cache.code;
import jadx.api.ICodeCache;
import jadx.api.ICodeInfo;
@@ -1,4 +1,4 @@
package jadx.gui.utils.codecache.disk;
package jadx.gui.cache.code.disk;
import java.io.IOException;
import java.util.Deque;
@@ -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);
@@ -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,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,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,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,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,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,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,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,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,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,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,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,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());
}
}
+19
View File
@@ -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;
}
}
+37
View File
@@ -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=替換常數
@@ -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;
@@ -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 {