Commit c705f8cb authored by Skylot's avatar Skylot

Merge branch 'master' into type-inference-wip

parents d8b39c26 f8c0449d
......@@ -48,6 +48,7 @@ allprojects {
mavenLocal()
mavenCentral()
jcenter()
google()
}
jacoco {
......
......@@ -35,6 +35,13 @@ public class ResourceFile {
private final ResourceType type;
private ZipRef zipRef;
public static ResourceFile createResourceFile(JadxDecompiler decompiler, String name, ResourceType type) {
if (!ZipSecurity.isValidZipEntryName(name)) {
return null;
}
return new ResourceFile(decompiler, name, type);
}
protected ResourceFile(JadxDecompiler decompiler, String name, ResourceType type) {
this.decompiler = decompiler;
this.name = name;
......@@ -65,11 +72,4 @@ public class ResourceFile {
public String toString() {
return "ResourceFile{name='" + name + '\'' + ", type=" + type + "}";
}
public static ResourceFile createResourceFileInstance(JadxDecompiler decompiler, String name, ResourceType type) {
if (!ZipSecurity.isValidZipEntryName(name)) {
return null;
}
return new ResourceFile(decompiler, name, type);
}
}
package jadx.api;
import jadx.core.codegen.CodeWriter;
import jadx.core.utils.files.ZipSecurity;
import jadx.core.xmlgen.ResContainer;
public class ResourceFileContent extends ResourceFile {
private final CodeWriter content;
private ResourceFileContent(String name, ResourceType type, CodeWriter content) {
public ResourceFileContent(String name, ResourceType type, CodeWriter content) {
super(null, name, type);
this.content = content;
}
@Override
public ResContainer loadContent() {
return ResContainer.singleFile(getName(), content);
}
public static ResourceFileContent createResourceFileContentInstance(String name, ResourceType type, CodeWriter content) {
if (!ZipSecurity.isValidZipEntryName(name)) {
return null;
}
return new ResourceFileContent(name, type, content);
return ResContainer.textResource(getName(), content);
}
}
......@@ -18,6 +18,7 @@ import org.slf4j.LoggerFactory;
import jadx.api.ResourceFile.ZipRef;
import jadx.core.codegen.CodeWriter;
import jadx.core.utils.Utils;
import jadx.core.utils.android.Res9patchStreamDecoder;
import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.files.InputFile;
import jadx.core.utils.files.ZipSecurity;
......@@ -31,8 +32,6 @@ import static jadx.core.utils.files.FileUtils.copyStream;
public final class ResourcesLoader {
private static final Logger LOG = LoggerFactory.getLogger(ResourcesLoader.class);
private static final int LOAD_SIZE_LIMIT = 10 * 1024 * 1024;
private final JadxDecompiler jadxRef;
ResourcesLoader(JadxDecompiler jadxRef) {
......@@ -47,11 +46,11 @@ public final class ResourcesLoader {
return list;
}
public interface ResourceDecoder {
ResContainer decode(long size, InputStream is) throws IOException;
public interface ResourceDecoder<T> {
T decode(long size, InputStream is) throws IOException;
}
public static ResContainer decodeStream(ResourceFile rf, ResourceDecoder decoder) throws JadxException {
public static <T> T decodeStream(ResourceFile rf, ResourceDecoder<T> decoder) throws JadxException {
try {
ZipRef zipRef = rf.getZipRef();
if (zipRef == null) {
......@@ -80,44 +79,48 @@ public final class ResourcesLoader {
static ResContainer loadContent(JadxDecompiler jadxRef, ResourceFile rf) {
try {
return decodeStream(rf, (size, is) -> loadContent(jadxRef, rf, is, size));
return decodeStream(rf, (size, is) -> loadContent(jadxRef, rf, is));
} catch (JadxException e) {
LOG.error("Decode error", e);
CodeWriter cw = new CodeWriter();
cw.add("Error decode ").add(rf.getType().toString().toLowerCase());
cw.startLine(Utils.getStackTrace(e.getCause()));
return ResContainer.singleFile(rf.getName(), cw);
return ResContainer.textResource(rf.getName(), cw);
}
}
private static ResContainer loadContent(JadxDecompiler jadxRef, ResourceFile rf,
InputStream inputStream, long size) throws IOException {
InputStream inputStream) throws IOException {
switch (rf.getType()) {
case MANIFEST:
case XML:
return ResContainer.singleFile(rf.getName(),
jadxRef.getXmlParser().parse(inputStream));
CodeWriter content = jadxRef.getXmlParser().parse(inputStream);
return ResContainer.textResource(rf.getName(), content);
case ARSC:
return new ResTableParser()
.decodeFiles(inputStream);
return new ResTableParser().decodeFiles(inputStream);
case IMG:
return ResContainer.singleImageFile(rf.getName(), inputStream);
case CODE:
case LIB:
case FONT:
case UNKNOWN:
return ResContainer.singleBinaryFile(rf.getName(), inputStream);
return decodeImage(rf, inputStream);
default:
if (size > LOAD_SIZE_LIMIT) {
return ResContainer.singleFile(rf.getName(),
new CodeWriter().add("File too big, size: " + String.format("%.2f KB", size / 1024.)));
}
return ResContainer.singleFile(rf.getName(), loadToCodeWriter(inputStream));
return ResContainer.resourceFileLink(rf);
}
}
private static ResContainer decodeImage(ResourceFile rf, InputStream inputStream) {
String name = rf.getName();
if (name.endsWith(".9.png")) {
Res9patchStreamDecoder decoder = new Res9patchStreamDecoder();
ByteArrayOutputStream os = new ByteArrayOutputStream();
try {
decoder.decode(inputStream, os);
return ResContainer.decodedData(rf.getName(), os.toByteArray());
} catch (Exception e) {
LOG.error("Failed to decode 9-patch png image, path: {}", name, e);
}
}
return ResContainer.resourceFileLink(rf);
}
private void loadFile(List<ResourceFile> list, File file) {
......@@ -141,7 +144,7 @@ public final class ResourcesLoader {
private void addResourceFile(List<ResourceFile> list, File file) {
String name = file.getAbsolutePath();
ResourceType type = ResourceType.getFileType(name);
ResourceFile rf = ResourceFile.createResourceFileInstance(jadxRef, name, type);
ResourceFile rf = ResourceFile.createResourceFile(jadxRef, name, type);
if (rf != null) {
list.add(rf);
}
......@@ -153,7 +156,7 @@ public final class ResourcesLoader {
}
String name = entry.getName();
ResourceType type = ResourceType.getFileType(name);
ResourceFile rf = ResourceFile.createResourceFileInstance(jadxRef, name, type);
ResourceFile rf = ResourceFile.createResourceFile(jadxRef, name, type);
if (rf != null) {
rf.setZipRef(new ZipRef(zipFile, name));
list.add(rf);
......
package jadx.core.codegen;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
......@@ -21,6 +20,7 @@ import jadx.core.dex.instructions.mods.ConstructorInsn;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.StringUtils;
import jadx.core.utils.Utils;
public class NameGen {
......@@ -31,21 +31,22 @@ public class NameGen {
private final boolean fallback;
static {
OBJ_ALIAS = new HashMap<>();
OBJ_ALIAS.put(Consts.CLASS_STRING, "str");
OBJ_ALIAS.put(Consts.CLASS_CLASS, "cls");
OBJ_ALIAS.put(Consts.CLASS_THROWABLE, "th");
OBJ_ALIAS.put(Consts.CLASS_OBJECT, "obj");
OBJ_ALIAS.put("java.util.Iterator", "it");
OBJ_ALIAS.put("java.lang.Boolean", "bool");
OBJ_ALIAS.put("java.lang.Short", "sh");
OBJ_ALIAS.put("java.lang.Integer", "num");
OBJ_ALIAS.put("java.lang.Character", "ch");
OBJ_ALIAS.put("java.lang.Byte", "b");
OBJ_ALIAS.put("java.lang.Float", "f");
OBJ_ALIAS.put("java.lang.Long", "l");
OBJ_ALIAS.put("java.lang.Double", "d");
OBJ_ALIAS.put("java.lang.StringBuilder", "sb");
OBJ_ALIAS = Utils.newConstStringMap(
Consts.CLASS_STRING, "str",
Consts.CLASS_CLASS, "cls",
Consts.CLASS_THROWABLE, "th",
Consts.CLASS_OBJECT, "obj",
"java.util.Iterator", "it",
"java.lang.Boolean", "bool",
"java.lang.Short", "sh",
"java.lang.Integer", "num",
"java.lang.Character", "ch",
"java.lang.Byte", "b",
"java.lang.Float", "f",
"java.lang.Long", "l",
"java.lang.Double", "d",
"java.lang.StringBuilder", "sb"
);
}
public NameGen(MethodNode mth, boolean fallback) {
......
package jadx.core.codegen;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
......@@ -11,6 +12,7 @@ import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
import jadx.core.dex.attributes.nodes.ForceReturnAttr;
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.instructions.SwitchNode;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.NamedArg;
......@@ -306,16 +308,23 @@ public class RegionGen extends InsnGen {
return;
}
code.startLine("} catch (");
if (handler.isCatchAll()) {
code.add("Throwable");
} else {
Iterator<ClassInfo> it = handler.getCatchTypes().iterator();
if (it.hasNext()) {
useClass(code, it.next());
}
while (it.hasNext()) {
code.add(" | ");
useClass(code, it.next());
}
}
code.add(' ');
InsnArg arg = handler.getArg();
if (arg instanceof RegisterArg) {
declareVar(code, (RegisterArg) arg);
code.add(mgen.getNameGen().assignArg((RegisterArg) arg));
} else if (arg instanceof NamedArg) {
if (handler.isCatchAll()) {
code.add("Throwable");
} else {
useClass(code, handler.getCatchType());
}
code.add(' ');
code.add(mgen.getNameGen().assignNamedArg((NamedArg) arg));
}
code.add(") {");
......
......@@ -243,6 +243,10 @@ public class Deobfuscator {
}
}
public void forceRenameField(FieldNode field) {
field.getFieldInfo().setAlias(makeFieldAlias(field));
}
public void renameMethod(MethodNode mth) {
String alias = getMethodAlias(mth);
if (alias != null) {
......@@ -253,6 +257,13 @@ public class Deobfuscator {
}
}
public void forceRenameMethod(MethodNode mth) {
mth.getMethodInfo().setAlias(makeMethodAlias(mth));
if (mth.isVirtual()) {
resolveOverriding(mth);
}
}
public void addPackagePreset(String origPkgName, String pkgAlias) {
PackageNode pkg = getPackageNode(origPkgName, true);
pkg.setAlias(pkgAlias);
......
......@@ -2,12 +2,14 @@ package jadx.core.dex.info;
import java.io.File;
import org.jetbrains.annotations.NotNull;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.exceptions.JadxRuntimeException;
public final class ClassInfo {
public final class ClassInfo implements Comparable<ClassInfo> {
private final ArgType type;
private String pkg;
......@@ -194,4 +196,9 @@ public final class ClassInfo {
}
return false;
}
@Override
public int compareTo(@NotNull ClassInfo o) {
return fullName.compareTo(o.fullName);
}
}
......@@ -65,6 +65,10 @@ public final class FieldInfo {
return !name.equals(alias);
}
public boolean equalsNameAndType(FieldInfo other) {
return name.equals(other.name) && type.equals(other.type);
}
@Override
public boolean equals(Object o) {
if (this == o) {
......
......@@ -321,6 +321,15 @@ public class ClassNode extends LineAttrNode implements ILoadable, IDexNode {
return null;
}
public FieldNode searchFieldByNameAndType(FieldInfo field) {
for (FieldNode f : fields) {
if (f.getFieldInfo().equalsNameAndType(field)) {
return f;
}
}
return null;
}
public FieldNode searchFieldByName(String name) {
for (FieldNode f : fields) {
if (f.getName().equals(name)) {
......
......@@ -152,16 +152,15 @@ public class DexNode implements IDexNode {
@Nullable
FieldNode deepResolveField(@NotNull ClassNode cls, FieldInfo fieldInfo) {
FieldNode field = cls.searchFieldByName(fieldInfo.getName());
FieldNode field = cls.searchFieldByNameAndType(fieldInfo);
if (field != null) {
return field;
}
FieldNode found;
ArgType superClass = cls.getSuperClass();
if (superClass != null) {
ClassNode superNode = resolveClass(superClass);
if (superNode != null) {
found = deepResolveField(superNode, fieldInfo);
FieldNode found = deepResolveField(superNode, fieldInfo);
if (found != null) {
return found;
}
......@@ -170,7 +169,7 @@ public class DexNode implements IDexNode {
for (ArgType iFaceType : cls.getInterfaces()) {
ClassNode iFaceNode = resolveClass(iFaceType);
if (iFaceNode != null) {
found = deepResolveField(iFaceNode, fieldInfo);
FieldNode found = deepResolveField(iFaceNode, fieldInfo);
if (found != null) {
return found;
}
......
......@@ -117,16 +117,16 @@ public class MethodNode extends LineAttrNode implements ILoadable, IDexNode {
DexNode dex = parentClass.dex();
Code mthCode = dex.readCode(methodData);
regsCount = mthCode.getRegistersSize();
this.regsCount = mthCode.getRegistersSize();
initMethodTypes();
InsnDecoder decoder = new InsnDecoder(this);
decoder.decodeInsns(mthCode);
instructions = decoder.process();
codeSize = instructions.length;
this.instructions = decoder.process();
this.codeSize = instructions.length;
initTryCatches(mthCode);
initJumps();
initTryCatches(this, mthCode, instructions);
initJumps(instructions);
this.debugInfoOffset = mthCode.getDebugInfoOffset();
} catch (Exception e) {
......@@ -263,37 +263,37 @@ public class MethodNode extends LineAttrNode implements ILoadable, IDexNode {
return genericMap;
}
private void initTryCatches(Code mthCode) {
InsnNode[] insnByOffset = instructions;
private static void initTryCatches(MethodNode mth, Code mthCode, InsnNode[] insnByOffset) {
CatchHandler[] catchBlocks = mthCode.getCatchHandlers();
Try[] tries = mthCode.getTries();
if (catchBlocks.length == 0 && tries.length == 0) {
return;
}
int hc = 0;
int handlersCount = 0;
Set<Integer> addrs = new HashSet<>();
List<TryCatchBlock> catches = new ArrayList<>(catchBlocks.length);
for (CatchHandler handler : catchBlocks) {
TryCatchBlock tcBlock = new TryCatchBlock();
catches.add(tcBlock);
for (int i = 0; i < handler.getAddresses().length; i++) {
int addr = handler.getAddresses()[i];
ClassInfo type = ClassInfo.fromDex(parentClass.dex(), handler.getTypeIndexes()[i]);
tcBlock.addHandler(this, addr, type);
int[] handlerAddrArr = handler.getAddresses();
for (int i = 0; i < handlerAddrArr.length; i++) {
int addr = handlerAddrArr[i];
ClassInfo type = ClassInfo.fromDex(mth.dex(), handler.getTypeIndexes()[i]);
tcBlock.addHandler(mth, addr, type);
addrs.add(addr);
hc++;
handlersCount++;
}
int addr = handler.getCatchAllAddress();
if (addr >= 0) {
tcBlock.addHandler(this, addr, null);
tcBlock.addHandler(mth, addr, null);
addrs.add(addr);
hc++;
handlersCount++;
}
}
if (hc > 0 && hc != addrs.size()) {
if (handlersCount > 0 && handlersCount != addrs.size()) {
// resolve nested try blocks:
// inner block contains all handlers from outer block => remove these handlers from inner block
// each handler must be only in one try/catch block
......@@ -301,7 +301,7 @@ public class MethodNode extends LineAttrNode implements ILoadable, IDexNode {
for (TryCatchBlock ct2 : catches) {
if (ct1 != ct2 && ct2.containsAllHandlers(ct1)) {
for (ExceptionHandler h : ct1.getHandlers()) {
ct2.removeHandler(this, h);
ct2.removeHandler(mth, h);
h.setTryBlock(ct1);
}
}
......@@ -315,6 +315,7 @@ public class MethodNode extends LineAttrNode implements ILoadable, IDexNode {
for (ExceptionHandler eh : ct.getHandlers()) {
int addr = eh.getHandleOffset();
ExcHandlerAttr ehAttr = new ExcHandlerAttr(ct, eh);
// TODO: don't override existing attribute
insnByOffset[addr].addAttr(ehAttr);
}
}
......@@ -341,8 +342,7 @@ public class MethodNode extends LineAttrNode implements ILoadable, IDexNode {
}
}
private void initJumps() {
InsnNode[] insnByOffset = instructions;
private static void initJumps(InsnNode[] insnByOffset) {
for (int offset = 0; offset < insnByOffset.length; offset++) {
InsnNode insn = insnByOffset[offset];
if (insn == null) {
......@@ -490,7 +490,18 @@ public class MethodNode extends LineAttrNode implements ILoadable, IDexNode {
exceptionHandlers = new ArrayList<>(2);
} else {
for (ExceptionHandler h : exceptionHandlers) {
if (h == handler || h.getHandleOffset() == handler.getHandleOffset()) {
if (h.equals(handler)) {
return h;
}
if (h.getHandleOffset() == handler.getHandleOffset()) {
if (h.getTryBlock() == handler.getTryBlock()) {
for (ClassInfo catchType : handler.getCatchTypes()) {
h.addCatchType(catchType);
}
} else {
// same handlers from different try blocks
// will merge later
}
return h;
}
}
......
......@@ -22,7 +22,6 @@ import jadx.core.dex.visitors.typeinference.TypeUpdate;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.StringUtils;
import jadx.core.utils.android.AndroidResourcesUtils;
import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.DexFile;
import jadx.core.utils.files.InputFile;
......@@ -84,17 +83,16 @@ public class RootNode {
LOG.debug("'.arsc' file not found");
return;
}
ResTableParser parser = new ResTableParser();
try {
ResourcesLoader.decodeStream(arsc, (size, is) -> {
ResourceStorage resStorage = ResourcesLoader.decodeStream(arsc, (size, is) -> {
ResTableParser parser = new ResTableParser();
parser.decode(is);
return null;
return parser.getResStorage();
});
} catch (JadxException e) {
processResources(resStorage);
} catch (Exception e) {
LOG.error("Failed to parse '.arsc' file", e);
return;
}
processResources(parser.getResStorage());
}
public void processResources(ResourceStorage resStorage) {
......
......@@ -30,6 +30,6 @@ public class ExcHandlerAttr implements IAttribute {
public String toString() {
return "ExcHandler: " + (handler.isFinally()
? " FINALLY"
: (handler.isCatchAll() ? "all" : handler.getCatchType()) + " " + handler.getArg());
: handler.catchTypeStr() + " " + handler.getArg());
}
}
package jadx.core.dex.trycatch;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;
import org.jetbrains.annotations.Nullable;
import jadx.core.Consts;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.IContainer;
import jadx.core.utils.InsnUtils;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException;
public class ExceptionHandler {
private final ClassInfo catchType;
private final Set<ClassInfo> catchTypes = new TreeSet<>();
private final int handleOffset;
private BlockNode handlerBlock;
......@@ -23,17 +32,57 @@ public class ExceptionHandler {
private TryCatchBlock tryBlock;
private boolean isFinally;
public ExceptionHandler(int addr, ClassInfo type) {
public ExceptionHandler(int addr, @Nullable ClassInfo type) {
this.handleOffset = addr;
this.catchType = type;
addCatchType(type);
}
/**
* Add exception type to catch block
* @param type - null for 'all' or 'Throwable' handler
*/
public void addCatchType(@Nullable ClassInfo type) {
if (type != null) {
this.catchTypes.add(type);
} else {
if (!this.catchTypes.isEmpty()) {
throw new JadxRuntimeException("Null type added to not empty exception handler: " + this);
}
}
}
public void addCatchTypes(Collection<ClassInfo> types) {
for (ClassInfo type : types) {
addCatchType(type);
}
}
public ClassInfo getCatchType() {
return catchType;
public Set<ClassInfo> getCatchTypes() {
return catchTypes;
}
public ArgType getArgType() {
if (isCatchAll()) {
return ArgType.THROWABLE;
}
Set<ClassInfo> types = getCatchTypes();
if (types.size() == 1) {
return types.iterator().next().getType();
} else {
return ArgType.THROWABLE;
}
}
public boolean isCatchAll() {
return catchType == null || catchType.getFullName().equals(Consts.CLASS_THROWABLE);
if (catchTypes.isEmpty()) {
return true;
}
for (ClassInfo classInfo : catchTypes) {
if (classInfo.getFullName().equals(Consts.CLASS_THROWABLE)) {
return true;
}
}
return false;
}
public int getHandleOffset() {
......@@ -89,35 +138,30 @@ public class ExceptionHandler {
}
@Override
public int hashCode() {
return (catchType == null ? 0 : 31 * catchType.hashCode()) + handleOffset;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
ExceptionHandler other = (ExceptionHandler) obj;
if (catchType == null) {
if (other.catchType != null) {
return false;
}
} else if (!catchType.equals(other.catchType)) {
if (o == null || getClass() != o.getClass()) {
return false;
}
return handleOffset == other.handleOffset;
ExceptionHandler that = (ExceptionHandler) o;
return handleOffset == that.handleOffset &&
catchTypes.equals(that.catchTypes) &&
Objects.equals(tryBlock, that.tryBlock);
}
@Override
public int hashCode() {
return Objects.hash(catchTypes, handleOffset /*, tryBlock*/);
}
public String catchTypeStr() {
return catchTypes.isEmpty() ? "all" : Utils.listToString(catchTypes, " | ", ClassInfo::getShortName);
}
@Override
public String toString() {
return (catchType == null ? "all"
: catchType.getShortName()) + " -> " + InsnUtils.formatOffset(handleOffset);
return catchTypeStr() + " -> " + InsnUtils.formatOffset(handleOffset);
}
}
......@@ -5,6 +5,8 @@ import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import org.jetbrains.annotations.Nullable;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.info.ClassInfo;
......@@ -40,12 +42,14 @@ public class TryCatchBlock {
return handlers.containsAll(tb.handlers);
}
public ExceptionHandler addHandler(MethodNode mth, int addr, ClassInfo type) {
public ExceptionHandler addHandler(MethodNode mth, int addr, @Nullable ClassInfo type) {
ExceptionHandler handler = new ExceptionHandler(addr, type);
handler = mth.addExceptionHandler(handler);
handlers.add(handler);
handler.setTryBlock(this);
return handler;
ExceptionHandler addedHandler = mth.addExceptionHandler(handler);
if (addedHandler == handler || addedHandler.getTryBlock() != this) {
handlers.add(addedHandler);
}
return addedHandler;
}
public void removeHandler(MethodNode mth, ExceptionHandler handler) {
......
......@@ -343,7 +343,7 @@ public class ModVisitor extends AbstractVisitor {
// result arg used both in this insn and exception handler,
RegisterArg resArg = insn.getResult();
ArgType type = excHandler.isCatchAll() ? ArgType.THROWABLE : excHandler.getCatchType().getType();
ArgType type = excHandler.getArgType();
String name = excHandler.isCatchAll() ? "th" : "e";
if (resArg.getName() == null) {
resArg.setName(name);
......
......@@ -12,6 +12,7 @@ import jadx.core.Consts;
import jadx.core.deobf.Deobfuscator;
import jadx.core.deobf.NameMapper;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.nodes.ClassNode;
......@@ -19,7 +20,6 @@ import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.files.FileUtils;
import jadx.core.utils.files.InputFile;
......@@ -49,20 +49,12 @@ public class RenameVisitor extends AbstractVisitor {
checkClasses(root, isCaseSensitive);
}
@Override
public boolean visit(ClassNode cls) throws JadxException {
checkFields(cls);
checkMethods(cls);
for (ClassNode inner : cls.getInnerClasses()) {
visit(inner);
}
return false;
}
private void checkClasses(RootNode root, boolean caseSensitive) {
Set<String> clsNames = new HashSet<>();
for (ClassNode cls : root.getClasses(true)) {
checkClassName(cls);
checkFields(cls);
checkMethods(cls);
if (!caseSensitive) {
ClassInfo classInfo = cls.getClassInfo();
String clsFileName = classInfo.getAlias().getFullPath();
......@@ -103,7 +95,7 @@ public class RenameVisitor extends AbstractVisitor {
FieldInfo fieldInfo = field.getFieldInfo();
String fieldName = fieldInfo.getAlias();
if (!names.add(fieldName) || !NameMapper.isValidIdentifier(fieldName)) {
deobfuscator.renameField(field);
deobfuscator.forceRenameField(field);
}
}
}
......@@ -111,12 +103,16 @@ public class RenameVisitor extends AbstractVisitor {
private void checkMethods(ClassNode cls) {
Set<String> names = new HashSet<>();
for (MethodNode mth : cls.getMethods()) {
if (mth.contains(AFlag.DONT_GENERATE) || mth.getAccessFlags().isConstructor()) {
AccessInfo accessFlags = mth.getAccessFlags();
if (accessFlags.isConstructor()
|| accessFlags.isBridge()
|| accessFlags.isSynthetic()
|| mth.contains(AFlag.DONT_GENERATE)) {
continue;
}
String signature = mth.getMethodInfo().makeSignature(false);
if (!names.add(signature) || !NameMapper.isValidIdentifier(mth.getAlias())) {
deobfuscator.renameMethod(mth);
deobfuscator.forceRenameMethod(mth);
}
}
}
......
......@@ -48,7 +48,7 @@ public class BlockExceptionHandler extends AbstractVisitor {
return;
}
ExceptionHandler excHandler = handlerAttr.getHandler();
ArgType argType = excHandler.isCatchAll() ? ArgType.THROWABLE : excHandler.getCatchType().getType();
ArgType argType = excHandler.getArgType();
if (!block.getInstructions().isEmpty()) {
InsnNode me = block.getInstructions().get(0);
if (me.getType() == InsnType.MOVE_EXCEPTION) {
......
......@@ -2,6 +2,7 @@ package jadx.core.dex.visitors.blocksmaker;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
......@@ -20,6 +21,9 @@ import jadx.core.dex.nodes.Edge;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.trycatch.CatchAttr;
import jadx.core.dex.trycatch.ExcHandlerAttr;
import jadx.core.dex.trycatch.ExceptionHandler;
import jadx.core.dex.trycatch.TryCatchBlock;
import jadx.core.dex.visitors.AbstractVisitor;
import jadx.core.utils.BlockUtils;
import jadx.core.utils.exceptions.JadxOverflowException;
......@@ -372,7 +376,17 @@ public class BlockProcessor extends AbstractVisitor {
}
private static boolean modifyBlocksTree(MethodNode mth) {
for (BlockNode block : mth.getBasicBlocks()) {
List<BlockNode> basicBlocks = mth.getBasicBlocks();
for (BlockNode block : basicBlocks) {
if (block.getPredecessors().isEmpty() && block != mth.getEnterBlock()) {
throw new JadxRuntimeException("Unreachable block: " + block);
}
}
if (mergeExceptionHandlers(mth)) {
removeMarkedBlocks(mth);
return true;
}
for (BlockNode block : basicBlocks) {
if (checkLoops(mth, block)) {
return true;
}
......@@ -493,6 +507,85 @@ public class BlockProcessor extends AbstractVisitor {
}
/**
* Merge handlers for multi-exception catch
*/
private static boolean mergeExceptionHandlers(MethodNode mth) {
for (BlockNode block : mth.getBasicBlocks()) {
ExcHandlerAttr excHandlerAttr = block.get(AType.EXC_HANDLER);
if (excHandlerAttr != null) {
List<BlockNode> blocksForMerge = collectExcHandlerBlocks(block, excHandlerAttr);
if (mergeHandlers(mth, blocksForMerge, excHandlerAttr)) {
return true;
}
}
}
return false;
}
private static List<BlockNode> collectExcHandlerBlocks(BlockNode block, ExcHandlerAttr excHandlerAttr) {
List<BlockNode> successors = block.getSuccessors();
if (successors.size() != 1) {
return Collections.emptyList();
}
RegisterArg reg = getMoveExceptionRegister(block);
if (reg == null) {
return Collections.emptyList();
}
TryCatchBlock tryBlock = excHandlerAttr.getTryBlock();
List<BlockNode> blocksForMerge = new ArrayList<>();
BlockNode nextBlock = successors.get(0);
for (BlockNode predBlock : nextBlock.getPredecessors()) {
if (predBlock != block
&& checkOtherExcHandler(predBlock, tryBlock, reg)) {
blocksForMerge.add(predBlock);
}
}
return blocksForMerge;
}
private static boolean checkOtherExcHandler(BlockNode predBlock, TryCatchBlock tryBlock, RegisterArg reg) {
ExcHandlerAttr otherExcHandlerAttr = predBlock.get(AType.EXC_HANDLER);
if (otherExcHandlerAttr == null) {
return false;
}
TryCatchBlock otherTryBlock = otherExcHandlerAttr.getTryBlock();
if (tryBlock != otherTryBlock) {
return false;
}
RegisterArg otherReg = getMoveExceptionRegister(predBlock);
if (otherReg == null || reg.getRegNum() != otherReg.getRegNum()) {
return false;
}
return true;
}
private static RegisterArg getMoveExceptionRegister(BlockNode block) {
if (block.getInstructions().isEmpty()) {
return null;
}
InsnNode insn = block.getInstructions().get(0);
if (insn.getType() != InsnType.MOVE_EXCEPTION) {
return null;
}
return insn.getResult();
}
private static boolean mergeHandlers(MethodNode mth, List<BlockNode> blocksForMerge, ExcHandlerAttr excHandlerAttr) {
if (blocksForMerge.isEmpty()) {
return false;
}
TryCatchBlock tryBlock = excHandlerAttr.getTryBlock();
for (BlockNode block : blocksForMerge) {
ExcHandlerAttr otherExcHandlerAttr = block.get(AType.EXC_HANDLER);
ExceptionHandler excHandler = otherExcHandlerAttr.getHandler();
excHandlerAttr.getHandler().addCatchTypes(excHandler.getCatchTypes());
tryBlock.removeHandler(mth, excHandler);
detachBlock(block);
}
return true;
}
/**
* Splice return block if several predecessors presents
*/
private static boolean splitReturn(MethodNode mth) {
......@@ -590,6 +683,20 @@ public class BlockProcessor extends AbstractVisitor {
});
}
private static void detachBlock(BlockNode block) {
for (BlockNode pred : block.getPredecessors()) {
pred.getSuccessors().remove(block);
pred.updateCleanSuccessors();
}
for (BlockNode successor : block.getSuccessors()) {
successor.getPredecessors().remove(block);
}
block.add(AFlag.REMOVE);
block.getInstructions().clear();
block.getPredecessors().clear();
block.getSuccessors().clear();
}
private static void clearBlocksState(MethodNode mth) {
mth.getBasicBlocks().forEach(block -> {
block.remove(AType.LOOP);
......
......@@ -44,6 +44,7 @@ public class BlockSplitter extends AbstractVisitor {
mth.initBasicBlocks();
splitBasicBlocks(mth);
removeJumpAttr(mth);
removeInsns(mth);
removeEmptyDetachedBlocks(mth);
initBlocksInTargetNodes(mth);
......@@ -299,6 +300,14 @@ public class BlockSplitter extends AbstractVisitor {
return block;
}
private void removeJumpAttr(MethodNode mth) {
for (BlockNode block : mth.getBasicBlocks()) {
for (InsnNode insn : block.getInstructions()) {
insn.remove(AType.JUMP);
}
}
}
private static void removeInsns(MethodNode mth) {
for (BlockNode block : mth.getBasicBlocks()) {
block.getInstructions().removeIf(insn -> {
......
......@@ -5,8 +5,10 @@ import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import jadx.api.JadxDecompiler;
......@@ -39,8 +41,16 @@ public class Utils {
if (objects == null) {
return "";
}
return listToString(objects, joiner, Object::toString);
}
public static <T> String listToString(Iterable<T> objects, Function<T, String> toStr) {
return listToString(objects, ", ", toStr);
}
public static <T> String listToString(Iterable<T> objects, String joiner, Function<T, String> toStr) {
StringBuilder sb = new StringBuilder();
listToString(sb, objects, joiner, Object::toString);
listToString(sb, objects, joiner, toStr);
return sb.toString();
}
......@@ -152,4 +162,16 @@ public class Utils {
}
return new ImmutableList<>(list);
}
public static Map<String, String> newConstStringMap(String... parameters) {
int len = parameters.length;
if (len == 0) {
return Collections.emptyMap();
}
Map<String, String> result = new HashMap<>(len / 2);
for (int i = 0; i < len; i += 2) {
result.put(parameters[i], parameters[i + 1]);
}
return Collections.unmodifiableMap(result);
}
}
package jadx.core.xmlgen;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import org.apache.commons.io.IOUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.ResourceFile;
import jadx.core.codegen.CodeWriter;
import jadx.core.utils.android.Res9patchStreamDecoder;
import jadx.core.utils.exceptions.JadxRuntimeException;
public class ResContainer implements Comparable<ResContainer> {
private static final Logger LOG = LoggerFactory.getLogger(ResContainer.class);
public enum DataType {
TEXT, DECODED_DATA, RES_LINK, RES_TABLE
}
private final DataType dataType;
private final String name;
private final Object data;
private final List<ResContainer> subFiles;
@Nullable
private CodeWriter content;
@Nullable
private BufferedImage image;
@Nullable
private InputStream binary;
private ResContainer(String name, List<ResContainer> subFiles) {
this.name = name;
this.subFiles = subFiles;
}
public static ResContainer singleFile(String name, CodeWriter content) {
ResContainer resContainer = new ResContainer(name, Collections.emptyList());
resContainer.content = content;
return resContainer;
}
public static ResContainer singleImageFile(String name, InputStream content) {
ResContainer resContainer = new ResContainer(name, Collections.emptyList());
InputStream newContent = content;
if (name.endsWith(".9.png")) {
Res9patchStreamDecoder decoder = new Res9patchStreamDecoder();
ByteArrayOutputStream os = new ByteArrayOutputStream();
try {
decoder.decode(content, os);
} catch (Exception e) {
LOG.error("Failed to decode 9-patch png image, path: {}", name, e);
}
newContent = new ByteArrayInputStream(os.toByteArray());
}
try {
resContainer.image = ImageIO.read(newContent);
} catch (Exception e) {
throw new JadxRuntimeException("Image load error", e);
}
return resContainer;
public static ResContainer textResource(String name, CodeWriter content) {
return new ResContainer(name, Collections.emptyList(), content, DataType.TEXT);
}
public static ResContainer singleBinaryFile(String name, InputStream content) {
ResContainer resContainer = new ResContainer(name, Collections.emptyList());
try {
// TODO: don't store binary files in memory
resContainer.binary = new ByteArrayInputStream(IOUtils.toByteArray(content));
} catch (Exception e) {
LOG.warn("Contents of the binary resource '{}' not saved, got exception", name, e);
}
return resContainer;
public static ResContainer decodedData(String name, byte[] data) {
return new ResContainer(name, Collections.emptyList(), data, DataType.DECODED_DATA);
}
public static ResContainer resourceFileLink(ResourceFile resFile) {
return new ResContainer(resFile.getName(), Collections.emptyList(), resFile, DataType.RES_LINK);
}
public static ResContainer multiFile(String name) {
return new ResContainer(name, new ArrayList<>());
public static ResContainer resourceTable(String name, List<ResContainer> subFiles, CodeWriter rootContent) {
return new ResContainer(name, subFiles, rootContent, DataType.RES_TABLE);
}
private ResContainer(String name, List<ResContainer> subFiles, Object data, DataType dataType) {
this.name = Objects.requireNonNull(name);
this.subFiles = Objects.requireNonNull(subFiles);
this.data = Objects.requireNonNull(data);
this.dataType = Objects.requireNonNull(dataType);
}
public String getName() {
......@@ -89,27 +52,24 @@ public class ResContainer implements Comparable<ResContainer> {
return name.replace("/", File.separator);
}
@Nullable
public CodeWriter getContent() {
return content;
public List<ResContainer> getSubFiles() {
return subFiles;
}
@Nullable
public InputStream getBinary() {
return binary;
public DataType getDataType() {
return dataType;
}
public void setContent(@Nullable CodeWriter content) {
this.content = content;
public CodeWriter getText() {
return (CodeWriter) data;
}
@Nullable
public BufferedImage getImage() {
return image;
public byte[] getDecodedData() {
return (byte[]) data;
}
public List<ResContainer> getSubFiles() {
return subFiles;
public ResourceFile getResLink() {
return (ResourceFile) data;
}
@Override
......@@ -136,6 +96,6 @@ public class ResContainer implements Comparable<ResContainer> {
@Override
public String toString() {
return "Res{" + name + ", subFiles=" + subFiles + "}";
return "Res{" + name + ", type=" + dataType + ", subFiles=" + subFiles + "}";
}
}
......@@ -66,23 +66,9 @@ public class ResTableParser extends CommonBinaryParser {
ValuesParser vp = new ValuesParser(strings, resStorage.getResourcesNames());
ResXmlGen resGen = new ResXmlGen(resStorage, vp);
ResContainer res = ResContainer.multiFile("res");
res.setContent(makeXmlDump());
res.getSubFiles().addAll(resGen.makeResourcesXml());
return res;
}
public CodeWriter makeDump() {
CodeWriter writer = new CodeWriter();
writer.add("app package: ").add(resStorage.getAppPackage());
writer.startLine();
ValuesParser vp = new ValuesParser(strings, resStorage.getResourcesNames());
for (ResourceEntry ri : resStorage.getResources()) {
writer.startLine(ri + ": " + vp.getValueString(ri));
}
writer.finish();
return writer;
CodeWriter content = makeXmlDump();
List<ResContainer> xmlFiles = resGen.makeResourcesXml();
return ResContainer.resourceTable("res", xmlFiles, content);
}
public CodeWriter makeXmlDump() {
......
......@@ -57,7 +57,7 @@ public class ResXmlGen {
content.decIndent();
content.startLine("</resources>");
content.finish();
files.add(ResContainer.singleFile(fileName, content));
files.add(ResContainer.textResource(fileName, content));
}
Collections.sort(files);
return files;
......
package jadx.core.xmlgen;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.nio.file.Files;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.ResourceFile;
import jadx.api.ResourcesLoader;
import jadx.core.codegen.CodeWriter;
import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.FileUtils;
import jadx.core.utils.files.ZipSecurity;
import static jadx.core.utils.files.FileUtils.prepareFile;
public class ResourcesSaver implements Runnable {
private static final Logger LOG = LoggerFactory.getLogger(ResourcesSaver.class);
......@@ -33,76 +29,74 @@ public class ResourcesSaver implements Runnable {
@Override
public void run() {
ResContainer rc = resourceFile.loadContent();
if (rc != null) {
saveResources(rc);
}
saveResources(resourceFile.loadContent());
}
private void saveResources(ResContainer rc) {
if (rc == null) {
return;
}
List<ResContainer> subFiles = rc.getSubFiles();
if (subFiles.isEmpty()) {
save(rc, outDir);
} else {
if (rc.getDataType() == ResContainer.DataType.RES_TABLE) {
saveToFile(rc, new File(outDir, "res/values/public.xml"));
for (ResContainer subFile : subFiles) {
for (ResContainer subFile : rc.getSubFiles()) {
saveResources(subFile);
}
} else {
save(rc, outDir);
}
}
private void save(ResContainer rc, File outDir) {
File outFile = new File(outDir, rc.getFileName());
BufferedImage image = rc.getImage();
if (image != null) {
String ext = FilenameUtils.getExtension(outFile.getName());
try {
outFile = prepareFile(outFile);
if (!ZipSecurity.isInSubDirectory(outDir, outFile)) {
LOG.error("Path traversal attack detected, invalid resource name: {}",
outFile.getPath());
return;
}
ImageIO.write(image, ext, outFile);
} catch (IOException e) {
LOG.error("Failed to save image: {}", rc.getName(), e);
}
return;
}
if (!ZipSecurity.isInSubDirectory(outDir, outFile)) {
LOG.error("Path traversal attack detected, invalid resource name: {}",
rc.getFileName());
LOG.error("Path traversal attack detected, invalid resource name: {}", outFile.getPath());
return;
}
saveToFile(rc, outFile);
}
private void saveToFile(ResContainer rc, File outFile) {
CodeWriter cw = rc.getContent();
if (cw != null) {
cw.save(outFile);
return;
}
InputStream binary = rc.getBinary();
if (binary != null) {
try {
switch (rc.getDataType()) {
case TEXT:
case RES_TABLE:
CodeWriter cw = rc.getText();
cw.save(outFile);
return;
case DECODED_DATA:
byte[] data = rc.getDecodedData();
FileUtils.makeDirsForFile(outFile);
try {
Files.write(outFile.toPath(), data);
} catch (Exception e) {
LOG.warn("Resource '{}' not saved, got exception", rc.getName(), e);
}
return;
case RES_LINK:
ResourceFile resFile = rc.getResLink();
FileUtils.makeDirsForFile(outFile);
try (FileOutputStream binaryFileStream = new FileOutputStream(outFile)) {
IOUtils.copy(binary, binaryFileStream);
} finally {
binary.close();
try {
saveResourceFile(resFile, outFile);
} catch (Exception e) {
LOG.warn("Resource '{}' not saved, got exception", rc.getName(), e);
}
return;
default:
LOG.warn("Resource '{}' not saved, unknown type", rc.getName());
break;
}
}
private void saveResourceFile(ResourceFile resFile, File outFile) throws JadxException {
ResourcesLoader.decodeStream(resFile, (size, is) -> {
try (FileOutputStream fileStream = new FileOutputStream(outFile)) {
IOUtils.copy(is, fileStream);
} catch (Exception e) {
LOG.warn("Resource '{}' not saved, got exception", rc.getName(), e);
throw new JadxRuntimeException("Resource file save error", e);
}
return;
}
LOG.warn("Resource '{}' not saved, unknown type", rc.getName());
return null;
});
}
}
package jadx.tests.integration.names;
import org.junit.Test;
import jadx.core.dex.nodes.ClassNode;
import jadx.tests.api.SmaliTest;
import static jadx.tests.api.utils.JadxMatchers.containsOne;
import static org.junit.Assert.assertThat;
public class TestDuplicatedNames extends SmaliTest {
/*
public static class TestCls {
public Object fieldName;
public String fieldName;
public Object run() {
return this.fieldName;
}
public String run() {
return this.fieldName;
}
}
*/
@Test
public void test() {
commonChecks();
}
@Test
public void testWithDeobf() {
enableDeobfuscation();
commonChecks();
}
private void commonChecks() {
ClassNode cls = getClassNodeFromSmaliWithPath("names", "TestDuplicatedNames");
String code = cls.getCode().toString();
assertThat(code, containsOne("Object fieldName;"));
assertThat(code, containsOne("String f0fieldName"));
assertThat(code, containsOne("this.fieldName"));
assertThat(code, containsOne("this.f0fieldName"));
assertThat(code, containsOne("public Object run() {"));
assertThat(code, containsOne("public String m0run() {"));
}
}
package jadx.tests.integration.trycatch;
import java.security.ProviderException;
import java.time.DateTimeException;
import org.junit.Test;
import jadx.core.dex.nodes.ClassNode;
import jadx.tests.api.IntegrationTest;
import static jadx.tests.api.utils.JadxMatchers.containsOne;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertThat;
public class TestMultiExceptionCatch extends IntegrationTest {
public static class TestCls {
public void test() {
try {
System.out.println("Test");
} catch (ProviderException | DateTimeException e) {
throw new RuntimeException(e);
}
}
}
@Test
public void test() {
ClassNode cls = getClassNode(TestCls.class);
String code = cls.getCode().toString();
assertThat(code, containsOne("try {"));
assertThat(code, containsOne("} catch (ProviderException | DateTimeException e) {"));
assertThat(code, containsOne("throw new RuntimeException(e);"));
assertThat(code, not(containsString("RuntimeException e;")));
}
}
package jadx.tests.integration.trycatch;
import org.junit.Test;
import jadx.core.dex.nodes.ClassNode;
import jadx.tests.api.SmaliTest;
import static jadx.tests.api.utils.JadxMatchers.containsOne;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertThat;
public class TestMultiExceptionCatchSameJump extends SmaliTest {
/*
public static class TestCls {
public void test() {
try {
System.out.println("Test");
} catch (ProviderException | DateTimeException e) {
throw new RuntimeException(e);
}
}
}
*/
@Test
public void test() {
ClassNode cls = getClassNodeFromSmaliWithPkg("trycatch", "TestMultiExceptionCatchSameJump");
String code = cls.getCode().toString();
assertThat(code, containsOne("try {"));
assertThat(code, containsOne("} catch (ProviderException | DateTimeException e) {"));
assertThat(code, containsOne("throw new RuntimeException(e);"));
assertThat(code, not(containsString("RuntimeException e;")));
}
}
......@@ -37,7 +37,6 @@ public class TestPrimitivesInIf extends IntegrationTest {
@Test
public void test2() {
setOutputCFG();
noDebugInfo();
ClassNode cls = getClassNode(TestCls.class);
String code = cls.getCode().toString();
......
.class public LTestDuplicatedNames;
.super Ljava/lang/Object;
.source "TestDuplicatedNames.java"
# instance fields
.field public fieldName:Ljava/lang/String;
.field public fieldName:Ljava/lang/Object;
# direct methods
.method public constructor <init>()V
.registers 1
.prologue
.line 3
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
return-void
.end method
# virtual methods
.method public run()Ljava/lang/String;
.registers 2
.prologue
iget-object v0, p0, LTestDuplicatedNames;->fieldName:Ljava/lang/String;
return-object v0
.end method
.method public run()Ljava/lang/Object;
.registers 2
.prologue
iget-object v0, p0, LTestDuplicatedNames;->fieldName:Ljava/lang/Object;
return-object v0
.end method
.class public Ltrycatch/TestMultiExceptionCatchSameJump;
.super Ljava/lang/Object;
.source "TestMultiExceptionCatchSameJump.java"
.method public test()V
.locals 2
.line 17
:try_start_0
sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;
const-string v1, "Test"
invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
:try_end_0
.catch Ljava/security/ProviderException; {:try_start_0 .. :try_end_0} :catch_0
.catch Ljava/time/DateTimeException; {:try_start_0 .. :try_end_0} :catch_0
.line 20
nop
.line 22
return-void
.line 18
:catch_0
move-exception v0
.line 19
.local v0, "e":Ljava/lang/RuntimeException;
new-instance v1, Ljava/lang/RuntimeException;
invoke-direct {v1, v0}, Ljava/lang/RuntimeException;-><init>(Ljava/lang/Throwable;)V
throw v1
.end method
......@@ -16,9 +16,11 @@ dependencies {
compile 'hu.kazocsaba:image-viewer:1.2.3'
compile 'org.apache.commons:commons-lang3:3.8.1'
compile 'org.apache.commons:commons-text:1.6'
compile 'io.reactivex.rxjava2:rxjava:2.2.5'
compile "com.github.akarnokd:rxjava2-swing:0.3.3"
compile 'com.android.tools.build:apksig:3.3.0'
}
applicationDistribution.with {
......@@ -54,7 +56,7 @@ launch4j {
mainClassName = 'jadx.gui.JadxGUI'
copyConfigurable = project.tasks.shadowJar.outputs.files
jar = "lib/${project.tasks.shadowJar.archiveName}"
// icon = "${projectDir}/icons/myApp.ico"
icon = "${projectDir}/src/main/resources/logos/jadx-logo.ico"
outfile = "jadx-gui-${version}.exe"
copyright = 'Skylot'
windowTitle = 'jadx'
......
......@@ -10,6 +10,7 @@ import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.JadxArgs;
import jadx.api.JadxDecompiler;
import jadx.api.JavaClass;
import jadx.api.JavaPackage;
......@@ -67,7 +68,6 @@ public class JadxWrapper {
/**
* Get the complete list of classes
* @return
*/
public List<JavaClass> getClasses() {
return decompiler.getClasses();
......@@ -75,19 +75,20 @@ public class JadxWrapper {
/**
* Get all classes that are not excluded by the excluded packages settings
* @return
*/
public List<JavaClass> getIncludedClasses() {
List<JavaClass> classList = decompiler.getClasses();
String excludedPackages = settings.getExcludedPackages().trim();
if (excludedPackages.length() == 0)
if (excludedPackages.length() == 0) {
return classList;
}
String[] excluded = excludedPackages.split("[ ]+");
return classList.stream().filter(cls -> {
for (String exclude : excluded) {
if (cls.getFullName().startsWith(exclude))
if (cls.getFullName().startsWith(exclude)) {
return false;
}
}
return true;
}).collect(Collectors.toList());
......@@ -105,7 +106,7 @@ public class JadxWrapper {
return openFile;
}
public JadxSettings getSettings() {
return settings;
public JadxArgs getArgs() {
return decompiler.getArgs();
}
}
......@@ -3,12 +3,12 @@ package jadx.gui.jobs;
import javax.swing.*;
import java.util.concurrent.Future;
import jadx.gui.utils.NLS;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.gui.ui.ProgressPanel;
import jadx.gui.utils.CacheObject;
import jadx.gui.utils.NLS;
import jadx.gui.utils.Utils;
import jadx.gui.utils.search.TextSearchIndex;
......@@ -27,12 +27,7 @@ public class BackgroundWorker extends SwingWorker<Void, Void> {
if (isDone()) {
return;
}
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
progressPane.setVisible(true);
}
});
SwingUtilities.invokeLater(() -> progressPane.setVisible(true));
addPropertyChangeListener(progressPane);
execute();
}
......@@ -46,7 +41,7 @@ public class BackgroundWorker extends SwingWorker<Void, Void> {
}
@Override
protected Void doInBackground() throws Exception {
protected Void doInBackground() {
try {
System.gc();
LOG.debug("Memory usage: Before decompile: {}", Utils.memoryInfo());
......
......@@ -11,12 +11,7 @@ public class DecompileJob extends BackgroundJob {
protected void runJob() {
for (final JavaClass cls : wrapper.getIncludedClasses()) {
addTask(new Runnable() {
@Override
public void run() {
cls.decompile();
}
});
addTask(cls::decompile);
}
}
......
......@@ -150,7 +150,6 @@ public class JadxSettings extends JadxCLIArgs {
return true;
}
public boolean isShowHeapUsageBar() {
return showHeapUsageBar;
}
......
......@@ -21,7 +21,7 @@ public class JadxSettingsAdapter {
private static final Preferences PREFS = Preferences.userNodeForPackage(JadxGUI.class);
private static ExclusionStrategy EXCLUDE_FIELDS = new ExclusionStrategy() {
private static final ExclusionStrategy EXCLUDE_FIELDS = new ExclusionStrategy() {
@Override
public boolean shouldSkipField(FieldAttributes f) {
return JadxSettings.SKIP_FIELDS.contains(f.getName())
......
package jadx.gui.settings;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import java.awt.*;
import java.awt.event.ItemEvent;
import java.awt.event.MouseAdapter;
......@@ -19,8 +17,6 @@ import jadx.gui.ui.codearea.EditorTheme;
import jadx.gui.utils.LangLocale;
import jadx.gui.utils.NLS;
import static jadx.gui.utils.Utils.FONT_HACK;
public class JadxSettingsWindow extends JDialog {
private static final long serialVersionUID = -1804570470377354148L;
......@@ -40,7 +36,6 @@ public class JadxSettingsWindow extends JDialog {
this.prevLang = settings.getLangLocale();
initUI();
registerBundledFonts();
setTitle(NLS.str("preferences.title"));
setSize(400, 550);
......@@ -50,13 +45,6 @@ public class JadxSettingsWindow extends JDialog {
setLocationRelativeTo(null);
}
public static void registerBundledFonts() {
GraphicsEnvironment grEnv = GraphicsEnvironment.getLocalGraphicsEnvironment();
if (FONT_HACK != null) {
grEnv.registerFont(FONT_HACK);
}
}
private void initUI() {
JPanel panel = new JPanel();
panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS));
......@@ -204,7 +192,6 @@ public class JadxSettingsWindow extends JDialog {
int i = themesCbx.getSelectedIndex();
EditorTheme editorTheme = editorThemes[i];
settings.setEditorThemePath(editorTheme.getPath());
mainWindow.setEditorTheme(editorTheme.getPath());
mainWindow.loadSettings();
});
......@@ -245,11 +232,11 @@ public class JadxSettingsWindow extends JDialog {
});
JButton editExcludedPackages = new JButton(NLS.str("preferences.excludedPackages.button"));
editExcludedPackages.addActionListener( event -> {
editExcludedPackages.addActionListener(event -> {
String result = JOptionPane.showInputDialog(this, NLS.str("preferences.excludedPackages.editDialog"),
settings.getExcludedPackages());
if (result !=null) {
if (result != null) {
settings.setExcludedPackages(result);
}
});
......
package jadx.gui.treemodel;
import javax.swing.*;
import java.io.File;
import java.security.cert.Certificate;
import java.util.List;
import java.util.stream.Collectors;
import com.android.apksig.ApkVerifier;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.commons.text.StringEscapeUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.gui.JadxWrapper;
import jadx.gui.utils.CertificateManager;
import jadx.gui.utils.NLS;
import jadx.gui.utils.Utils;
public class ApkSignature extends JNode {
private static final long serialVersionUID = -9121321926113143407L;
private static final Logger LOG = LoggerFactory.getLogger(ApkSignature.class);
private static final ImageIcon CERTIFICATE_ICON = Utils.openIcon("certificate_obj");
private final transient File openFile;
private String content;
public static ApkSignature getApkSignature(JadxWrapper wrapper) {
// Only show the ApkSignature node if an AndroidManifest.xml is present.
// Without a manifest the Google ApkVerifier refuses to work.
if (wrapper.getResources().stream().noneMatch(r -> "AndroidManifest.xml".equals(r.getName()))) {
return null;
}
File openFile = wrapper.getOpenFile();
return new ApkSignature(openFile);
}
public ApkSignature(File openFile) {
this.openFile = openFile;
}
@Override
public JClass getJParent() {
return null;
}
@Override
public Icon getIcon() {
return CERTIFICATE_ICON;
}
@Override
public String makeString() {
return "APK signature";
}
@Override
public String getContent() {
if (content != null) {
return this.content;
}
ApkVerifier verifier = new ApkVerifier.Builder(openFile).build();
try {
ApkVerifier.Result result = verifier.verify();
StringEscapeUtils.Builder builder = StringEscapeUtils.builder(StringEscapeUtils.ESCAPE_HTML4);
builder.append("<h1>APK signature verification result:</h1>");
builder.append("<p><b>");
if (result.isVerified()) {
builder.escape(NLS.str("apkSignature.verificationSuccess"));
} else {
builder.escape(NLS.str("apkSignature.verificationFailed"));
}
builder.append("</b></p>");
final String err = NLS.str("apkSignature.errors");
final String warn = NLS.str("apkSignature.warnings");
final String sigSucc = NLS.str("apkSignature.signatureSuccess");
final String sigFail = NLS.str("apkSignature.signatureFailed");
writeIssues(builder, err, result.getErrors());
writeIssues(builder, warn, result.getWarnings());
if (!result.getV1SchemeSigners().isEmpty()) {
builder.append("<h2>");
builder.escape(String.format(result.isVerifiedUsingV1Scheme() ? sigSucc : sigFail, 1));
builder.append("</h2>\n");
builder.append("<blockquote>");
for (ApkVerifier.Result.V1SchemeSignerInfo signer : result.getV1SchemeSigners()) {
builder.append("<h3>");
builder.escape(NLS.str("apkSignature.signer"));
builder.append(" ");
builder.escape(signer.getName());
builder.append(" (");
builder.escape(signer.getSignatureFileName());
builder.append(")");
builder.append("</h3>");
writeCertificate(builder, signer.getCertificate());
writeIssues(builder, err, signer.getErrors());
writeIssues(builder, warn, signer.getWarnings());
}
builder.append("</blockquote>");
}
if (!result.getV2SchemeSigners().isEmpty()) {
builder.append("<h2>");
builder.escape(String.format(result.isVerifiedUsingV2Scheme() ? sigSucc : sigFail, 2));
builder.append("</h2>\n");
builder.append("<blockquote>");
for (ApkVerifier.Result.V2SchemeSignerInfo signer : result.getV2SchemeSigners()) {
builder.append("<h3>");
builder.escape(NLS.str("apkSignature.signer"));
builder.append(" ");
builder.append(Integer.toString(signer.getIndex() + 1));
builder.append("</h3>");
writeCertificate(builder, signer.getCertificate());
writeIssues(builder, err, signer.getErrors());
writeIssues(builder, warn, signer.getWarnings());
}
builder.append("</blockquote>");
}
this.content = builder.toString();
} catch (Exception e) {
LOG.error(e.getMessage(), e);
StringEscapeUtils.Builder builder = StringEscapeUtils.builder(StringEscapeUtils.ESCAPE_HTML4);
builder.append("<h1>");
builder.escape(NLS.str("apkSignature.exception"));
builder.append("</h1><pre>");
builder.escape(ExceptionUtils.getStackTrace(e));
builder.append("</pre>");
return builder.toString();
}
return this.content;
}
private void writeCertificate(StringEscapeUtils.Builder builder, Certificate cert) {
CertificateManager certMgr = new CertificateManager(cert);
builder.append("<blockquote><pre>");
builder.escape(certMgr.generateHeader());
builder.append("</pre><pre>");
builder.escape(certMgr.generatePublicKey());
builder.append("</pre><pre>");
builder.escape(certMgr.generateSignature());
builder.append("</pre><pre>");
builder.append(certMgr.generateFingerprint());
builder.append("</pre></blockquote>");
}
private void writeIssues(StringEscapeUtils.Builder builder, String issueType, List<ApkVerifier.IssueWithParams> issueList) {
if (!issueList.isEmpty()) {
builder.append("<h3>");
builder.escape(issueType);
builder.append("</h3>");
builder.append("<blockquote>");
// Unprotected Zip entry issues are very common, handle them separately
List<ApkVerifier.IssueWithParams> unprotIssues = issueList.stream().filter(i ->
i.getIssue() == ApkVerifier.Issue.JAR_SIG_UNPROTECTED_ZIP_ENTRY).collect(Collectors.toList());
if (!unprotIssues.isEmpty()) {
builder.append("<h4>");
builder.escape(NLS.str("apkSignature.unprotectedEntry"));
builder.append("</h4><blockquote>");
for (ApkVerifier.IssueWithParams issue : unprotIssues) {
builder.escape((String) issue.getParams()[0]);
builder.append("<br>");
}
builder.append("</blockquote>");
}
List<ApkVerifier.IssueWithParams> remainingIssues = issueList.stream().filter(i ->
i.getIssue() != ApkVerifier.Issue.JAR_SIG_UNPROTECTED_ZIP_ENTRY).collect(Collectors.toList());
if (!remainingIssues.isEmpty()) {
builder.append("<pre>\n");
for (ApkVerifier.IssueWithParams issue : remainingIssues) {
builder.escape(issue.toString());
builder.append("\n");
}
builder.append("</pre>\n");
}
builder.append("</blockquote>");
}
}
}
......@@ -2,6 +2,7 @@ package jadx.gui.treemodel;
import javax.swing.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
......@@ -11,14 +12,13 @@ import org.jetbrains.annotations.NotNull;
import jadx.api.ResourceFile;
import jadx.api.ResourceFileContent;
import jadx.api.ResourceType;
import jadx.api.ResourcesLoader;
import jadx.core.codegen.CodeWriter;
import jadx.core.xmlgen.ResContainer;
import jadx.gui.utils.NLS;
import jadx.gui.utils.OverlayIcon;
import jadx.gui.utils.Utils;
import static jadx.api.ResourceFileContent.createResourceFileContentInstance;
public class JResource extends JLoadableNode implements Comparable<JResource> {
private static final long serialVersionUID = -201018424302612434L;
......@@ -43,7 +43,7 @@ public class JResource extends JLoadableNode implements Comparable<JResource> {
private transient boolean loaded;
private transient String content;
private transient Map<Integer, Integer> lineMapping;
private transient Map<Integer, Integer> lineMapping = Collections.emptyMap();
public JResource(ResourceFile resFile, String name, JResType type) {
this(resFile, name, name, type);
......@@ -58,15 +58,16 @@ public class JResource extends JLoadableNode implements Comparable<JResource> {
}
public final void update() {
removeAllChildren();
if (!loaded) {
if (files.isEmpty()) {
if (type == JResType.DIR
|| type == JResType.ROOT
|| resFile.getType() == ResourceType.ARSC) {
// fake leaf to force show expand button
// real sub nodes will load on expand in loadNode() method
add(new TextNode(NLS.str("tree.loading")));
}
} else {
loadContent();
removeAllChildren();
for (JResource res : files) {
res.update();
add(res);
......@@ -76,13 +77,8 @@ public class JResource extends JLoadableNode implements Comparable<JResource> {
@Override
public void loadNode() {
loadContent();
loaded = true;
update();
}
private void loadContent() {
getContent();
update();
}
@Override
......@@ -95,40 +91,68 @@ public class JResource extends JLoadableNode implements Comparable<JResource> {
}
@Override
public String getContent() {
if (!loaded && resFile != null && type == JResType.FILE) {
loaded = true;
if (isSupportedForView(resFile.getType())) {
ResContainer rc = resFile.loadContent();
if (rc != null) {
addSubFiles(rc, this, 0);
}
public synchronized String getContent() {
if (loaded) {
return content;
}
if (resFile == null || type != JResType.FILE) {
return null;
}
if (!isSupportedForView(resFile.getType())) {
return null;
}
ResContainer rc = resFile.loadContent();
if (rc == null) {
return null;
}
if (rc.getDataType() == ResContainer.DataType.RES_TABLE) {
content = loadCurrentSingleRes(rc);
for (ResContainer subFile : rc.getSubFiles()) {
loadSubNodes(this, subFile, 1);
}
loaded = true;
return content;
}
return content;
// single node
return loadCurrentSingleRes(rc);
}
private void addSubFiles(ResContainer rc, JResource root, int depth) {
CodeWriter cw = rc.getContent();
if (cw != null) {
if (depth == 0) {
root.lineMapping = cw.getLineMapping();
root.content = cw.toString();
} else {
String resName = rc.getName();
String[] path = resName.split("/");
String resShortName = path.length == 0 ? resName : path[path.length - 1];
ResourceFileContent fileContent = createResourceFileContentInstance(resShortName, ResourceType.XML, cw);
if (fileContent != null) {
addPath(path, root, new JResource(fileContent, resName, resShortName, JResType.FILE));
private String loadCurrentSingleRes(ResContainer rc) {
switch (rc.getDataType()) {
case TEXT:
case RES_TABLE:
CodeWriter cw = rc.getText();
lineMapping = cw.getLineMapping();
return cw.toString();
case RES_LINK:
try {
return ResourcesLoader.decodeStream(rc.getResLink(), (size, is) -> {
if (size > 10 * 1024 * 1024L) {
return "File too large for view";
}
return ResourcesLoader.loadToCodeWriter(is).toString();
});
} catch (Exception e) {
return "Failed to load resource file: \n" + jadx.core.utils.Utils.getStackTrace(e);
}
}
case DECODED_DATA:
default:
return "Unexpected resource type: " + rc;
}
List<ResContainer> subFiles = rc.getSubFiles();
if (!subFiles.isEmpty()) {
for (ResContainer subFile : subFiles) {
addSubFiles(subFile, root, depth + 1);
}
}
private void loadSubNodes(JResource root, ResContainer rc, int depth) {
String resName = rc.getName();
String[] path = resName.split("/");
String resShortName = path.length == 0 ? resName : path[path.length - 1];
CodeWriter cw = rc.getText();
ResourceFileContent fileContent = new ResourceFileContent(resShortName, ResourceType.XML, cw);
addPath(path, root, new JResource(fileContent, resName, resShortName, JResType.FILE));
for (ResContainer subFile : rc.getSubFiles()) {
loadSubNodes(root, subFile, depth + 1);
}
}
......@@ -190,25 +214,29 @@ public class JResource extends JLoadableNode implements Comparable<JResource> {
}
}
private static final Map<String, String> EXTENSION_TO_FILE_SYNTAX = jadx.core.utils.Utils.newConstStringMap(
"java", SyntaxConstants.SYNTAX_STYLE_JAVA,
"js", SyntaxConstants.SYNTAX_STYLE_JAVASCRIPT,
"ts", SyntaxConstants.SYNTAX_STYLE_TYPESCRIPT,
"json", SyntaxConstants.SYNTAX_STYLE_JSON,
"css", SyntaxConstants.SYNTAX_STYLE_CSS,
"less", SyntaxConstants.SYNTAX_STYLE_LESS,
"html", SyntaxConstants.SYNTAX_STYLE_HTML,
"xml", SyntaxConstants.SYNTAX_STYLE_XML,
"yaml", SyntaxConstants.SYNTAX_STYLE_YAML,
"properties", SyntaxConstants.SYNTAX_STYLE_PROPERTIES_FILE,
"ini", SyntaxConstants.SYNTAX_STYLE_INI,
"sql", SyntaxConstants.SYNTAX_STYLE_SQL,
"arsc", SyntaxConstants.SYNTAX_STYLE_XML
);
private String getSyntaxByExtension(String name) {
int dot = name.lastIndexOf('.');
if (dot == -1) {
return null;
}
String ext = name.substring(dot + 1);
if (ext.equals("js")) {
return SyntaxConstants.SYNTAX_STYLE_JAVASCRIPT;
}
if (ext.equals("css")) {
return SyntaxConstants.SYNTAX_STYLE_CSS;
}
if (ext.equals("html")) {
return SyntaxConstants.SYNTAX_STYLE_HTML;
}
if (ext.equals("arsc")) {
return SyntaxConstants.SYNTAX_STYLE_XML;
}
return null;
return EXTENSION_TO_FILE_SYNTAX.get(ext);
}
@Override
......@@ -256,6 +284,10 @@ public class JResource extends JLoadableNode implements Comparable<JResource> {
return resFile;
}
public Map<Integer, Integer> getLineMapping() {
return lineMapping;
}
@Override
public JClass getJParent() {
return null;
......
......@@ -37,6 +37,11 @@ public class JRoot extends JNode {
add(jRes);
}
ApkSignature signature = ApkSignature.getApkSignature(wrapper);
if (signature != null) {
add(signature);
}
JCertificate certificate = getCertificate(wrapper.getResources());
if (certificate != null) {
add(certificate);
......
......@@ -85,7 +85,7 @@ public class JSources extends JNode {
}
}
// use identity set for collect inner packages
Set<JPackage> innerPackages = Collections.newSetFromMap(new IdentityHashMap<JPackage, Boolean>());
Set<JPackage> innerPackages = Collections.newSetFromMap(new IdentityHashMap<>());
for (JPackage pkg : pkgMap.values()) {
innerPackages.addAll(pkg.getInnerPackages());
}
......
......@@ -2,8 +2,6 @@ package jadx.gui.ui;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import jadx.api.JadxDecompiler;
import jadx.gui.utils.NLS;
......@@ -42,11 +40,7 @@ class AboutDialog extends JDialog {
textPane.add(Box.createRigidArea(new Dimension(0, 20)));
JButton close = new JButton(NLS.str("tabs.close"));
close.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
dispose();
}
});
close.addActionListener(event -> dispose());
close.setAlignmentX(0.5f);
Container contentPane = getContentPane();
......
......@@ -33,14 +33,14 @@ import jadx.gui.jobs.DecompileJob;
import jadx.gui.treemodel.JNode;
import jadx.gui.ui.codearea.CodeArea;
import jadx.gui.utils.CacheObject;
import jadx.gui.utils.NLS;
import jadx.gui.utils.JumpPosition;
import jadx.gui.utils.NLS;
import jadx.gui.utils.search.TextSearchIndex;
public abstract class CommonSearchDialog extends JDialog {
private static final long serialVersionUID = 8939332306115370276L;
private static final Logger LOG = LoggerFactory.getLogger(CommonSearchDialog.class);
private static final long serialVersionUID = 8939332306115370276L;
public static final int RESULTS_PER_PAGE = 100;
......@@ -397,12 +397,14 @@ public abstract class CommonSearchDialog extends JDialog {
protected class ResultsTableCellRenderer implements TableCellRenderer {
private final JLabel emptyLabel = new JLabel();
private final Font font;
private final Color codeSelectedColor;
private final Color codeBackground;
private Map<Integer, Component> componentCache = new HashMap<>();
private final Map<Integer, Component> componentCache = new HashMap<>();
public ResultsTableCellRenderer() {
RSyntaxTextArea area = CodeArea.getDefaultArea(mainWindow);
this.font = area.getFont();
this.codeSelectedColor = area.getSelectionColor();
this.codeBackground = area.getBackground();
}
......@@ -414,7 +416,7 @@ public abstract class CommonSearchDialog extends JDialog {
Component comp = componentCache.get(id);
if (comp == null) {
if (obj instanceof JNode) {
comp = makeCell(table, (JNode) obj, column);
comp = makeCell((JNode) obj, column);
componentCache.put(id, comp);
} else {
comp = emptyLabel;
......@@ -442,10 +444,10 @@ public abstract class CommonSearchDialog extends JDialog {
}
}
private Component makeCell(JTable table, JNode node, int column) {
private Component makeCell(JNode node, int column) {
if (column == 0) {
JLabel label = new JLabel(node.makeLongString() + " ", node.getIcon(), SwingConstants.LEFT);
label.setFont(table.getFont());
label.setFont(font);
label.setOpaque(true);
label.setToolTipText(label.getText());
return label;
......
package jadx.gui.ui;
import jadx.gui.utils.NLS;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
public class HeapUsageBar extends JProgressBar implements ActionListener {
private static final double TWO_TO_20 = 1048576d; // 1024 * 1024
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private final Color GREEN = new Color(0, 180, 0);
private final Color RED = new Color(200, 0, 0);
import jadx.gui.utils.NLS;
import jadx.gui.utils.Utils;
private final Runtime r;
public class HeapUsageBar extends JProgressBar implements ActionListener {
private static final Logger LOG = LoggerFactory.getLogger(HeapUsageBar.class);
private static final long serialVersionUID = -8739563124249884967L;
private String maxHeapStr;
private static final double TWO_TO_20 = 1048576d;
private final Timer timer;
private static final Color GREEN = new Color(0, 180, 0);
private static final Color RED = new Color(200, 0, 0);
private final double maxGB;
private final transient Runtime runtime = Runtime.getRuntime();
private final transient Timer timer;
private final String textFormat;
private final double maxGB;
public HeapUsageBar() {
super();
textFormat = NLS.str("heapUsage.text");
r = Runtime.getRuntime();
this.textFormat = NLS.str("heapUsage.text");
setBorderPainted(false);
setStringPainted(true);
setValue(10);
int maxKB = (int) (r.maxMemory() / 1024);
int maxKB = (int) (runtime.maxMemory() / 1024);
setMaximum(maxKB);
maxGB = maxKB / TWO_TO_20;
update();
timer = new Timer(1000, this);
timer = new Timer(2000, this);
addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
Runtime.getRuntime().gc();
update();
if (LOG.isDebugEnabled()) {
LOG.debug("Memory used: {}", Utils.memoryInfo());
}
}
});
}
public void update() {
long used = r.totalMemory() - r.freeMemory();
long used = runtime.totalMemory() - runtime.freeMemory();
int usedKB = (int) (used / 1024);
setValue(usedKB);
setString(String.format(textFormat, (usedKB / TWO_TO_20), maxGB));
if (used > r.totalMemory() * 0.8) {
if ((used + Utils.MIN_FREE_MEMORY) > runtime.maxMemory()) {
setForeground(RED);
} else {
setForeground(GREEN);
......
package jadx.gui.ui;
import javax.swing.*;
import java.awt.*;
import jadx.gui.settings.JadxSettings;
import jadx.gui.treemodel.JNode;
public final class HtmlPanel extends ContentPanel {
private static final long serialVersionUID = -6251262855835426245L;
private final JHtmlPane textArea;
public HtmlPanel(TabbedPane panel, JNode jnode) {
super(panel, jnode);
setLayout(new BorderLayout());
textArea = new JHtmlPane();
loadSettings();
textArea.setText(jnode.getContent());
textArea.setCaretPosition(0); // otherwise the start view will be the last line
textArea.setEditable(false);
JScrollPane sp = new JScrollPane(textArea);
add(sp);
}
@Override
public void loadSettings() {
JadxSettings settings = getTabbedPane().getMainWindow().getSettings();
textArea.setFont(settings.getFont());
}
private static final class JHtmlPane extends JEditorPane {
private static final long serialVersionUID = 6886040384052136157L;
public JHtmlPane() {
setContentType("text/html");
}
@Override
public void paint(Graphics g) {
Graphics2D g2d = (Graphics2D) g.create();
try {
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
super.paint(g2d);
} finally {
g2d.dispose();
}
}
}
}
package jadx.gui.ui;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import hu.kazocsaba.imageviewer.ImageViewer;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
import jadx.api.ResourceFile;
import jadx.api.ResourcesLoader;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.xmlgen.ResContainer;
import jadx.gui.treemodel.JResource;
import jadx.gui.ui.codearea.CodeArea;
public class ImagePanel extends ContentPanel {
private static final long serialVersionUID = 4071356367073142688L;
ImagePanel(TabbedPane panel, JResource res) {
super(panel, res);
setLayout(new BorderLayout());
try {
BufferedImage img = loadImage(res);
ImageViewer imageViewer = new ImageViewer(img);
add(imageViewer.getComponent());
} catch (Exception e) {
RSyntaxTextArea textArea = CodeArea.getDefaultArea(panel.getMainWindow());
textArea.setText("Image load error: \n" + Utils.getStackTrace(e));
add(textArea);
}
}
private BufferedImage loadImage(JResource res) {
ResourceFile resFile = res.getResFile();
BufferedImage img = resFile.loadContent().getImage();
ImageViewer imageViewer = new ImageViewer(img);
imageViewer.setZoomFactor(2.);
setLayout(new BorderLayout());
add(imageViewer.getComponent());
ResContainer resContainer = resFile.loadContent();
ResContainer.DataType dataType = resContainer.getDataType();
if (dataType == ResContainer.DataType.DECODED_DATA) {
try {
return ImageIO.read(new ByteArrayInputStream(resContainer.getDecodedData()));
} catch (Exception e) {
throw new JadxRuntimeException("Failed to load image", e);
}
} else if (dataType == ResContainer.DataType.RES_LINK) {
try {
return ResourcesLoader.decodeStream(resFile, (size, is) -> ImageIO.read(is));
} catch (Exception e) {
throw new JadxRuntimeException("Failed to load image", e);
}
} else {
throw new JadxRuntimeException("Unsupported resource image data type: " + resFile);
}
}
@Override
......
......@@ -59,7 +59,7 @@ public class MainDropTarget implements DropTargetListener {
try {
Transferable transferable = dtde.getTransferable();
List<File> transferData = (List<File>) transferable.getTransferData(DataFlavor.javaFileListFlavor);
if (transferData != null && transferData.size() > 0) {
if (!transferData.isEmpty()) {
dtde.dropComplete(true);
// load first file
mainWindow.openFile(transferData.get(0));
......
......@@ -9,7 +9,6 @@ import javax.swing.filechooser.FileNameExtensionFilter;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.ExpandVetoException;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
......@@ -24,7 +23,9 @@ import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.File;
import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
......@@ -32,6 +33,7 @@ import org.fife.ui.rsyntaxtextarea.Theme;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.JadxArgs;
import jadx.api.ResourceFile;
import jadx.gui.JadxWrapper;
import jadx.gui.jobs.BackgroundWorker;
......@@ -39,6 +41,7 @@ import jadx.gui.jobs.DecompileJob;
import jadx.gui.jobs.IndexJob;
import jadx.gui.settings.JadxSettings;
import jadx.gui.settings.JadxSettingsWindow;
import jadx.gui.treemodel.ApkSignature;
import jadx.gui.treemodel.JCertificate;
import jadx.gui.treemodel.JClass;
import jadx.gui.treemodel.JLoadableNode;
......@@ -49,9 +52,9 @@ import jadx.gui.update.JadxUpdate;
import jadx.gui.update.JadxUpdate.IUpdateCallback;
import jadx.gui.update.data.Release;
import jadx.gui.utils.CacheObject;
import jadx.gui.utils.JumpPosition;
import jadx.gui.utils.Link;
import jadx.gui.utils.NLS;
import jadx.gui.utils.JumpPosition;
import jadx.gui.utils.Utils;
import static javax.swing.KeyStroke.getKeyStroke;
......@@ -95,7 +98,6 @@ public class MainWindow extends JFrame {
private boolean isFlattenPackage;
private JToggleButton flatPkgButton;
private JCheckBoxMenuItem flatPkgMenuItem;
private JCheckBoxMenuItem heapUsageBarMenuItem;
private JToggleButton deobfToggleBtn;
private JCheckBoxMenuItem deobfMenuItem;
......@@ -111,16 +113,21 @@ public class MainWindow extends JFrame {
this.cacheObject = new CacheObject();
resetCache();
registerBundledFonts();
initUI();
initMenuAndToolbar();
applySettings();
checkForUpdate();
setWindowIcons();
}
private void applySettings() {
setFont(settings.getFont());
setEditorTheme(settings.getEditorThemePath());
private void setWindowIcons() {
List<Image> icons = new ArrayList<>();
icons.add(Utils.openImage("/logos/jadx-logo-16px.png"));
icons.add(Utils.openImage("/logos/jadx-logo-32px.png"));
icons.add(Utils.openImage("/logos/jadx-logo-48px.png"));
icons.add(Utils.openImage("/logos/jadx-logo.png"));
setIconImages(icons);
loadSettings();
checkForUpdate();
}
public void open() {
......@@ -222,12 +229,6 @@ public class MainWindow extends JFrame {
}
private void saveAll(boolean export) {
settings.setExportAsGradleProject(export);
if (export) {
settings.setSkipSources(false);
settings.setSkipResources(false);
}
JFileChooser fileChooser = new JFileChooser();
fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
fileChooser.setToolTipText(NLS.str("file.save_all_msg"));
......@@ -239,6 +240,15 @@ public class MainWindow extends JFrame {
int ret = fileChooser.showDialog(mainPanel, NLS.str("file.select"));
if (ret == JFileChooser.APPROVE_OPTION) {
JadxArgs decompilerArgs = wrapper.getArgs();
decompilerArgs.setExportAsGradleProject(export);
if (export) {
decompilerArgs.setSkipSources(false);
decompilerArgs.setSkipResources(false);
} else {
decompilerArgs.setSkipSources(settings.isSkipSources());
decompilerArgs.setSkipResources(settings.isSkipResources());
}
settings.setLastSaveFilePath(fileChooser.getCurrentDirectory().getPath());
ProgressMonitor progressMonitor = new ProgressMonitor(mainPanel, NLS.str("msg.saving_sources"), "", 0, 100);
progressMonitor.setMillisToPopup(0);
......@@ -290,15 +300,17 @@ public class MainWindow extends JFrame {
private void treeClickAction() {
try {
Object obj = tree.getLastSelectedPathComponent();
if (obj == null) {
return;
}
if (obj instanceof JResource) {
JResource res = (JResource) obj;
ResourceFile resFile = res.getResFile();
if (resFile != null && JResource.isSupportedForView(resFile.getType())) {
tabbedPane.showResource(res);
}
} else if (obj instanceof JCertificate) {
JCertificate cert = (JCertificate) obj;
tabbedPane.showCertificate(cert);
} else if ((obj instanceof JCertificate) || (obj instanceof ApkSignature)) {
tabbedPane.showSimpleNode((JNode) obj);
} else if (obj instanceof JNode) {
JNode node = (JNode) obj;
JClass cls = node.getRootClass();
......@@ -388,7 +400,7 @@ public class MainWindow extends JFrame {
flatPkgMenuItem = new JCheckBoxMenuItem(NLS.str("menu.flatten"), ICON_FLAT_PKG);
flatPkgMenuItem.setState(isFlattenPackage);
heapUsageBarMenuItem = new JCheckBoxMenuItem(NLS.str("menu.heapUsageBar"));
JCheckBoxMenuItem heapUsageBarMenuItem = new JCheckBoxMenuItem(NLS.str("menu.heapUsageBar"));
heapUsageBarMenuItem.setState(settings.isShowHeapUsageBar());
heapUsageBarMenuItem.addActionListener(event -> {
settings.setShowHeapUsageBar(!settings.isShowHeapUsageBar());
......@@ -595,7 +607,7 @@ public class MainWindow extends JFrame {
});
tree.addTreeWillExpandListener(new TreeWillExpandListener() {
@Override
public void treeWillExpand(TreeExpansionEvent event) throws ExpandVetoException {
public void treeWillExpand(TreeExpansionEvent event) {
TreePath path = event.getPath();
Object node = path.getLastPathComponent();
if (node instanceof JLoadableNode) {
......@@ -604,7 +616,8 @@ public class MainWindow extends JFrame {
}
@Override
public void treeWillCollapse(TreeExpansionEvent event) throws ExpandVetoException {
public void treeWillCollapse(TreeExpansionEvent event) {
// ignore
}
});
......@@ -643,7 +656,14 @@ public class MainWindow extends JFrame {
setFont(font);
}
public void setEditorTheme(String editorThemePath) {
public static void registerBundledFonts() {
GraphicsEnvironment grEnv = GraphicsEnvironment.getLocalGraphicsEnvironment();
if (Utils.FONT_HACK != null) {
grEnv.registerFont(Utils.FONT_HACK);
}
}
private void setEditorTheme(String editorThemePath) {
try {
editorTheme = Theme.load(getClass().getResourceAsStream(editorThemePath));
} catch (Exception e) {
......@@ -661,6 +681,14 @@ public class MainWindow extends JFrame {
}
public void loadSettings() {
Font font = settings.getFont();
Font largerFont = font.deriveFont(font.getSize() + 2.f);
setFont(largerFont);
setEditorTheme(settings.getEditorThemePath());
tree.setFont(largerFont);
tree.setRowHeight(-1);
tabbedPane.loadSettings();
}
......
......@@ -176,6 +176,7 @@ public class SearchDialog extends CommonSearchDialog {
.subscribeOn(Schedulers.single())
.doOnNext(r -> LOG.debug("search event: {}", r))
.switchMap(text -> prepareSearch(text)
.doOnError(e -> LOG.error("Error prepare search: {}", e.getMessage(), e))
.subscribeOn(Schedulers.single())
.toList()
.toFlowable(), 1)
......
......@@ -17,6 +17,7 @@ import org.slf4j.LoggerFactory;
import jadx.api.ResourceFile;
import jadx.api.ResourceType;
import jadx.gui.treemodel.ApkSignature;
import jadx.gui.treemodel.JCertificate;
import jadx.gui.treemodel.JClass;
import jadx.gui.treemodel.JNode;
......@@ -93,8 +94,8 @@ public class TabbedPane extends JTabbedPane {
SwingUtilities.invokeLater(() -> setSelectedComponent(contentPanel));
}
public void showCertificate(JCertificate cert) {
final ContentPanel contentPanel = getContentPanel(cert);
public void showSimpleNode(JNode node) {
final ContentPanel contentPanel = getContentPanel(node);
if (contentPanel == null) {
return;
}
......@@ -170,6 +171,9 @@ public class TabbedPane extends JTabbedPane {
return null;
}
}
if (node instanceof ApkSignature) {
return new HtmlPanel(this, node);
}
if (node instanceof JCertificate) {
return new CertificatePanel(this, node);
}
......
......@@ -6,13 +6,14 @@ import java.awt.event.ActionEvent;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import jadx.gui.treemodel.JClass;
import jadx.gui.treemodel.JNode;
import jadx.gui.treemodel.JResource;
import jadx.gui.ui.ContentPanel;
import jadx.gui.ui.TabbedPane;
import jadx.gui.utils.Utils;
public final class CodePanel extends ContentPanel {
private static final long serialVersionUID = 5310536092010045565L;
private final SearchBar searchBar;
......@@ -24,7 +25,6 @@ public final class CodePanel extends ContentPanel {
codeArea = new CodeArea(this);
searchBar = new SearchBar(codeArea);
scrollPane = new JScrollPane(codeArea);
initLineNumbers();
......@@ -37,7 +37,23 @@ public final class CodePanel extends ContentPanel {
}
private void initLineNumbers() {
scrollPane.setRowHeaderView(new LineNumbers(codeArea));
// TODO: fix slow line rendering on big files
if (codeArea.getDocument().getLength() <= 100_000) {
LineNumbers numbers = new LineNumbers(codeArea);
numbers.setUseSourceLines(isUseSourceLines());
scrollPane.setRowHeaderView(numbers);
}
}
private boolean isUseSourceLines() {
if (node instanceof JClass) {
return true;
}
if (node instanceof JResource) {
JResource resNode = (JResource) node;
return !resNode.getLineMapping().isEmpty();
}
return false;
}
private class SearchAction extends AbstractAction {
......
......@@ -32,16 +32,16 @@ public class LineNumbers extends JPanel implements CaretListener {
private static final int NUM_HEIGHT = Integer.MAX_VALUE - 1000000;
private static final Map<?, ?> DESKTOP_HINTS = (Map<?, ?>) Toolkit.getDefaultToolkit().getDesktopProperty("awt.font.desktophints");
private CodeArea codeArea;
private final CodeArea codeArea;
private boolean useSourceLines = true;
private int lastDigits;
private int lastLine;
private Map<String, FontMetrics> fonts;
private transient final Color numberColor;
private transient final Color currentColor;
private transient final Border border;
private final transient Color numberColor;
private final transient Color currentColor;
private final transient Border border;
public LineNumbers(CodeArea component) {
this.codeArea = component;
......@@ -199,12 +199,10 @@ public class LineNumbers extends JPanel implements CaretListener {
String fontFamily = (String) as.getAttribute(StyleConstants.FontFamily);
Integer fontSize = (Integer) as.getAttribute(StyleConstants.FontSize);
String key = fontFamily + fontSize;
FontMetrics fm = fonts.get(key);
if (fm == null) {
FontMetrics fm = fonts.computeIfAbsent(key, k -> {
Font font = new Font(fontFamily, Font.PLAIN, fontSize);
fm = codeArea.getFontMetrics(font);
fonts.put(key, fm);
}
return codeArea.getFontMetrics(font);
});
descent = Math.max(descent, fm.getDescent());
}
}
......@@ -221,4 +219,8 @@ public class LineNumbers extends JPanel implements CaretListener {
lastLine = currentLine;
}
}
public void setUseSourceLines(boolean useSourceLines) {
this.useSourceLines = useSourceLines;
}
}
......@@ -6,9 +6,8 @@ import java.io.Reader;
import java.lang.reflect.Type;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Collections;
import java.nio.charset.StandardCharsets;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import com.google.gson.Gson;
......@@ -32,12 +31,8 @@ public class JadxUpdate {
private static final Type RELEASES_LIST_TYPE = new TypeToken<List<Release>>() {
}.getType();
private static final Comparator<Release> RELEASE_COMPARATOR = new Comparator<Release>() {
@Override
public int compare(Release o1, Release o2) {
return VersionComparator.checkAndCompare(o1.getName(), o2.getName());
}
};
private static final Comparator<Release> RELEASE_COMPARATOR = (o1, o2) ->
VersionComparator.checkAndCompare(o1.getName(), o2.getName());
public interface IUpdateCallback {
void onUpdate(Release r);
......@@ -47,17 +42,14 @@ public class JadxUpdate {
}
public static void check(final IUpdateCallback callback) {
Runnable run = new Runnable() {
@Override
public void run() {
try {
Release release = checkForNewRelease();
if (release != null) {
callback.onUpdate(release);
}
} catch (Exception e) {
LOG.debug("Jadx update error", e);
Runnable run = () -> {
try {
Release release = checkForNewRelease();
if (release != null) {
callback.onUpdate(release);
}
} catch (Exception e) {
LOG.debug("Jadx update error", e);
}
};
Thread thread = new Thread(run);
......@@ -77,17 +69,11 @@ public class JadxUpdate {
if (list == null) {
return null;
}
for (Iterator<Release> it = list.iterator(); it.hasNext(); ) {
Release release = it.next();
if (release.getName().equalsIgnoreCase(version)
|| release.isPreRelease()) {
it.remove();
}
}
list.removeIf(release -> release.getName().equalsIgnoreCase(version) || release.isPreRelease());
if (list.isEmpty()) {
return null;
}
Collections.sort(list, RELEASE_COMPARATOR);
list.sort(RELEASE_COMPARATOR);
Release latest = list.get(list.size() - 1);
if (VersionComparator.checkAndCompare(version, latest.getName()) >= 0) {
return null;
......@@ -101,7 +87,7 @@ public class JadxUpdate {
HttpURLConnection con = (HttpURLConnection) obj.openConnection();
con.setRequestMethod("GET");
if (con.getResponseCode() == 200) {
Reader reader = new InputStreamReader(con.getInputStream(), "UTF-8");
Reader reader = new InputStreamReader(con.getInputStream(), StandardCharsets.UTF_8);
return GSON.fromJson(reader, type);
}
return null;
......
......@@ -21,8 +21,8 @@ public class CertificateManager {
private static final Logger LOG = LoggerFactory.getLogger(CertificateManager.class);
private static final String CERTIFICATE_TYPE_NAME = "X.509";
private final Certificate cert;
private X509Certificate x509cert;
private Certificate cert;
public static String decode(InputStream in) {
StringBuilder strBuild = new StringBuilder();
......@@ -54,7 +54,7 @@ public class CertificateManager {
}
}
String generateHeader() {
public String generateHeader() {
StringBuilder builder = new StringBuilder();
append(builder, NLS.str("certificate.cert_type"), x509cert.getType());
append(builder, NLS.str("certificate.serialSigVer"), ((Integer) x509cert.getVersion()).toString());
......@@ -70,14 +70,14 @@ public class CertificateManager {
return builder.toString();
}
String generateSignature() {
public String generateSignature() {
StringBuilder builder = new StringBuilder();
append(builder, NLS.str("certificate.serialSigType"), x509cert.getSigAlgName());
append(builder, NLS.str("certificate.serialSigOID"), x509cert.getSigAlgOID());
return builder.toString();
}
String generateFingerprint() {
public String generateFingerprint() {
StringBuilder builder = new StringBuilder();
try {
append(builder, NLS.str("certificate.serialMD5"), getThumbPrint(x509cert, "MD5"));
......@@ -89,7 +89,7 @@ public class CertificateManager {
return builder.toString();
}
String generatePublicKey() {
public String generatePublicKey() {
PublicKey publicKey = x509cert.getPublicKey();
if (publicKey instanceof RSAPublicKey) {
return generateRSAPublicKey();
......@@ -106,6 +106,8 @@ public class CertificateManager {
append(builder, NLS.str("certificate.serialPubKeyType"), pub.getAlgorithm());
append(builder, NLS.str("certificate.serialPubKeyExponent"), pub.getPublicExponent().toString(10));
append(builder, NLS.str("certificate.serialPubKeyModulusSize"), Integer.toString(
pub.getModulus().toString(2).length()));
append(builder, NLS.str("certificate.serialPubKeyModulus"), pub.getModulus().toString(10));
return builder.toString();
......@@ -120,7 +122,7 @@ public class CertificateManager {
return builder.toString();
}
String generateTextForX509() {
public String generateTextForX509() {
StringBuilder builder = new StringBuilder();
if (x509cert != null) {
builder.append(generateHeader());
......@@ -136,7 +138,7 @@ public class CertificateManager {
return builder.toString();
}
private String generateText() {
public String generateText() {
StringBuilder str = new StringBuilder();
String type = cert.getType();
if (type.equals(CERTIFICATE_TYPE_NAME)) {
......
......@@ -9,7 +9,7 @@ import jadx.api.JavaMethod;
import jadx.api.JavaNode;
public class CodeLinesInfo {
private NavigableMap<Integer, JavaNode> map = new TreeMap<>();
private final NavigableMap<Integer, JavaNode> map = new TreeMap<>();
public CodeLinesInfo(JavaClass cls) {
addClass(cls);
......
......@@ -2,9 +2,9 @@ package jadx.gui.utils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import jadx.api.CodePosition;
import jadx.api.JavaClass;
......@@ -21,6 +21,10 @@ public class CodeUsageInfo {
public List<CodeNode> getUsageList() {
return usageList;
}
public synchronized void addUsage(CodeNode codeNode) {
usageList.add(codeNode);
}
}
private final JNodeCache nodeCache;
......@@ -29,7 +33,7 @@ public class CodeUsageInfo {
this.nodeCache = nodeCache;
}
private final Map<JNode, UsageInfo> usageMap = new HashMap<>();
private final Map<JNode, UsageInfo> usageMap = new ConcurrentHashMap<>();
public void processClass(JavaClass javaClass, CodeLinesInfo linesInfo, List<StringRef> lines) {
Map<CodePosition, JavaNode> usage = javaClass.getUsageMap();
......@@ -42,17 +46,13 @@ public class CodeUsageInfo {
private void addUsage(JNode jNode, JavaClass javaClass,
CodeLinesInfo linesInfo, CodePosition codePosition, List<StringRef> lines) {
UsageInfo usageInfo = usageMap.get(jNode);
if (usageInfo == null) {
usageInfo = new UsageInfo();
usageMap.put(jNode, usageInfo);
}
UsageInfo usageInfo = usageMap.computeIfAbsent(jNode, key -> new UsageInfo());
int line = codePosition.getLine();
JavaNode javaNodeByLine = linesInfo.getJavaNodeByLine(line);
StringRef codeLine = lines.get(line - 1);
JNode node = nodeCache.makeFrom(javaNodeByLine == null ? javaClass : javaNodeByLine);
CodeNode codeNode = new CodeNode(node, line, codeLine);
usageInfo.getUsageList().add(codeNode);
usageInfo.addUsage(codeNode);
}
public List<CodeNode> getUsageList(JNode node) {
......
......@@ -5,7 +5,7 @@ import java.util.List;
public class JumpManager {
private List<JumpPosition> list = new ArrayList<>();
private final List<JumpPosition> list = new ArrayList<>();
private int currentPos = 0;
public void addPosition(JumpPosition pos) {
......
......@@ -3,7 +3,7 @@ package jadx.gui.utils;
import java.util.Locale;
public class LangLocale {
private Locale locale;
private final Locale locale;
public LangLocale(Locale locale) {
this.locale = locale;
......
......@@ -16,7 +16,7 @@ public class Link extends JLabel implements MouseListener {
private static final Logger LOG = LoggerFactory.getLogger(Link.class);
private String url;
private final String url;
public Link(String text, String url) {
super(text);
......
......@@ -4,24 +4,29 @@ import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.PropertyResourceBundle;
import java.util.ResourceBundle;
import java.util.Vector;
import org.jetbrains.annotations.NotNull;
import jadx.core.utils.exceptions.JadxRuntimeException;
public class NLS {
private static Vector<LangLocale> i18nLocales = new Vector<>();
private static final Vector<LangLocale> i18nLocales = new Vector<>();
private static Map<LangLocale, ResourceBundle> i18nMessagesMap = new HashMap<>();
private static final Map<LangLocale, ResourceBundle> i18nMessagesMap = new HashMap<>();
private static final ResourceBundle fallbackMessagesMap;
private static final LangLocale localLocale;
// Use these two fields to avoid invoking Map.get() method twice.
private static ResourceBundle localizedMessagesMap;
private static ResourceBundle fallbackMessagesMap;
private static LangLocale currentLocale;
private static LangLocale localLocale;
static {
localLocale = new LangLocale(Locale.getDefault());
......@@ -45,10 +50,13 @@ public class NLS {
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
String resName = String.format("i18n/Messages_%s.properties", locale.get());
URL bundleUrl = classLoader.getResource(resName);
if (bundleUrl == null) {
throw new JadxRuntimeException("Locale resource not found: " + resName);
}
try (Reader reader = new InputStreamReader(bundleUrl.openStream(), StandardCharsets.UTF_8)) {
bundle = new PropertyResourceBundle(reader);
} catch (IOException e) {
throw new RuntimeException("Failed to load " + resName, e);
throw new JadxRuntimeException("Failed to load " + resName, e);
}
i18nMessagesMap.put(locale, bundle);
}
......@@ -66,7 +74,8 @@ public class NLS {
if (bundle != null) {
try {
return bundle.getString(key);
} catch (MissingResourceException e) {
} catch (MissingResourceException ignored) {
// use fallback string
}
}
return fallbackMessagesMap.getString(key); // definitely exists
......
......@@ -26,6 +26,16 @@ public class Utils {
public static final Font FONT_HACK = openFontTTF("Hack-Regular");
/**
* The minimum about of memory in bytes we are trying to keep free, otherwise the application may run out of heap
* which ends up in a Java garbage collector running "amok" (CPU utilization 100% for each core and the UI is
* not responsive).
* <p>
* We can calculate and store this value here as the maximum heap is fixed for each JVM instance
* and can't be changed at runtime.
*/
public static final long MIN_FREE_MEMORY = calculateMinFreeMemory();
private Utils() {
}
......@@ -38,6 +48,14 @@ public class Utils {
return new ImageIcon(resource);
}
public static Image openImage(String path) {
URL resource = Utils.class.getResource(path);
if (resource == null) {
throw new JadxRuntimeException("Image not found: " + path);
}
return Toolkit.getDefaultToolkit().createImage(resource);
}
@Nullable
public static Font openFontTTF(String name) {
String fontPath = "/fonts/" + name + ".ttf";
......@@ -107,31 +125,37 @@ public class Utils {
return overIcon;
}
/**
* @return 20% of the maximum heap size limited to 512 MB (bytes)
*/
public static long calculateMinFreeMemory() {
Runtime runtime = Runtime.getRuntime();
long minFree = (long) (runtime.maxMemory() * 0.2);
return Math.min(minFree, 512 * 1024L * 1024L);
}
public static boolean isFreeMemoryAvailable() {
Runtime runtime = Runtime.getRuntime();
long maxMemory = runtime.maxMemory();
long totalFree = runtime.freeMemory() + maxMemory - runtime.totalMemory();
return totalFree > maxMemory * 0.2;
long totalFree = runtime.freeMemory() + (maxMemory - runtime.totalMemory());
return totalFree > MIN_FREE_MEMORY;
}
public static String memoryInfo() {
Runtime runtime = Runtime.getRuntime();
StringBuilder sb = new StringBuilder();
long maxMemory = runtime.maxMemory();
long allocatedMemory = runtime.totalMemory();
long freeMemory = runtime.freeMemory();
sb.append("heap: ").append(format(allocatedMemory - freeMemory));
sb.append(", allocated: ").append(format(allocatedMemory));
sb.append(", free: ").append(format(freeMemory));
sb.append(", total free: ").append(format(freeMemory + maxMemory - allocatedMemory));
sb.append(", max: ").append(format(maxMemory));
return sb.toString();
return "heap: " + format(allocatedMemory - freeMemory) +
", allocated: " + format(allocatedMemory) +
", free: " + format(freeMemory) +
", total free: " + format(freeMemory + maxMemory - allocatedMemory) +
", max: " + format(maxMemory);
}
private static String format(long mem) {
return Long.toString((long) (mem / 1024. / 1024.)) + "MB";
return (long) (mem / (double) (1024L * 1024L)) + "MB";
}
/**
......
......@@ -12,10 +12,14 @@ public class SimpleIndex<T> implements SearchIndex<T> {
private final List<String> keys = new ArrayList<>();
private final List<T> values = new ArrayList<>();
private final Object syncData = new Object();
@Override
public void put(String str, T value) {
keys.add(str);
values.add(value);
synchronized (syncData) {
keys.add(str);
values.add(value);
}
}
@Override
......@@ -39,13 +43,15 @@ public class SimpleIndex<T> implements SearchIndex<T> {
@Override
public Flowable<T> search(final String searchStr, final boolean caseInsensitive) {
return Flowable.create(emitter -> {
int size = size();
for (int i = 0; i < size; i++) {
if (isMatched(keys.get(i), searchStr, caseInsensitive)) {
emitter.onNext(values.get(i));
}
if (emitter.isCancelled()) {
return;
synchronized (syncData) {
int size = keys.size();
for (int i = 0; i < size; i++) {
if (isMatched(keys.get(i), searchStr, caseInsensitive)) {
emitter.onNext(values.get(i));
}
if (emitter.isCancelled()) {
return;
}
}
}
emitter.onComplete();
......@@ -54,6 +60,8 @@ public class SimpleIndex<T> implements SearchIndex<T> {
@Override
public int size() {
return keys.size();
synchronized (syncData) {
return keys.size();
}
}
}
......@@ -47,7 +47,7 @@ tabs.closeAll=Close All
nav.back=Back
nav.forward=Forward
message.indexingClassesSkipped=<html>Jadx is running low on memory. Therefore %d classes were not indexed.<br>If you want all classes to be indexed restart Jadx with increased maximum heap size.</html>
message.indexingClassesSkipped=<html>Jadx is running low on memory. Therefore %d classes were not indexed.<br>If you want all classes to be indexed restart Jadx with increased maximum heap size.</html>
heapUsage.text=JADX memory usage: %.2f GB of %.2f GB
......@@ -136,9 +136,20 @@ certificate.serialValidUntil=Valid until
certificate.serialPubKeyType=Public key type
certificate.serialPubKeyExponent=Exponent
certificate.serialPubKeyModulus=Modulus
certificate.serialPubKeyModulusSize=Modulus size (bits)
certificate.serialSigType=Signature type
certificate.serialSigOID=Signature OID
certificate.serialMD5=MD5 Fingerprint
certificate.serialSHA1=SHA-1 Fingerprint
certificate.serialSHA256=SHA-256 Fingerprint
certificate.serialPubKeyY=Y
apkSignature.signer=Signer
apkSignature.verificationSuccess=Signature verification succeeded
apkSignature.verificationFailed=Signature verification succeeded
apkSignature.signatureSuccess=Valid APK signature v%d found
apkSignature.signatureFailed=Invalid APK signature v%d found
apkSignature.errors=Errors
apkSignature.warnings=Warnings
apkSignature.exception=APK verification failed
apkSignature.unprotectedEntry=Files that are not protected by signature. Unauthorized modifications to this JAR entry will not be detected.
package jadx.gui.tests
import jadx.gui.utils.search.StringRef
import spock.lang.Specification
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment