Commit 16d8d41b authored by Skylot's avatar Skylot

Merge branch 'master' into type-inference-wip

# Conflicts:
#	jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockSplitter.java
#	jadx-core/src/main/java/jadx/core/dex/visitors/ssa/EliminatePhiNodes.java
parents 7bd17522 28d348b3
...@@ -53,13 +53,18 @@ public class JadxArgsValidator { ...@@ -53,13 +53,18 @@ public class JadxArgsValidator {
} else { } else {
outDir = makeDirFromInput(args); outDir = makeDirFromInput(args);
} }
}
args.setOutDir(outDir); args.setOutDir(outDir);
setFromOut(args); }
if (srcDir == null) {
args.setOutDirSrc(new File(args.getOutDir(), JadxArgs.DEFAULT_SRC_DIR));
}
if (resDir == null) {
args.setOutDirRes(new File(args.getOutDir(), JadxArgs.DEFAULT_RES_DIR));
}
checkDir(args.getOutDir()); checkDir(args.getOutDir(), "Output");
checkDir(args.getOutDirSrc()); checkDir(args.getOutDirSrc(), "Source output");
checkDir(args.getOutDirRes()); checkDir(args.getOutDirRes(), "Resources output");
} }
@NotNull @NotNull
...@@ -79,15 +84,6 @@ public class JadxArgsValidator { ...@@ -79,15 +84,6 @@ public class JadxArgsValidator {
return outDir; return outDir;
} }
private static void setFromOut(JadxArgs args) {
if (args.getOutDirSrc() == null) {
args.setOutDirSrc(new File(args.getOutDir(), JadxArgs.DEFAULT_SRC_DIR));
}
if (args.getOutDirRes() == null) {
args.setOutDirRes(new File(args.getOutDir(), JadxArgs.DEFAULT_RES_DIR));
}
}
private static void checkFile(File file) { private static void checkFile(File file) {
if (!file.exists()) { if (!file.exists()) {
throw new JadxArgsValidateException("File not found " + file.getAbsolutePath()); throw new JadxArgsValidateException("File not found " + file.getAbsolutePath());
...@@ -97,9 +93,9 @@ public class JadxArgsValidator { ...@@ -97,9 +93,9 @@ public class JadxArgsValidator {
} }
} }
private static void checkDir(File dir) { private static void checkDir(File dir, String desc) {
if (dir != null && dir.exists() && !dir.isDirectory()) { if (dir != null && dir.exists() && !dir.isDirectory()) {
throw new JadxArgsValidateException("Output directory exists as file " + dir); throw new JadxArgsValidateException(desc + " directory exists as file " + dir);
} }
} }
......
...@@ -96,10 +96,10 @@ public class Jadx { ...@@ -96,10 +96,10 @@ public class Jadx {
passes.add(new SimplifyVisitor()); passes.add(new SimplifyVisitor());
passes.add(new CheckRegions()); passes.add(new CheckRegions());
passes.add(new MethodInlineVisitor());
passes.add(new ExtractFieldInit()); passes.add(new ExtractFieldInit());
passes.add(new FixAccessModifiers()); passes.add(new FixAccessModifiers());
passes.add(new ClassModifier()); passes.add(new ClassModifier());
passes.add(new MethodInlineVisitor());
passes.add(new EnumVisitor()); passes.add(new EnumVisitor());
passes.add(new LoopRegionVisitor()); passes.add(new LoopRegionVisitor());
......
...@@ -103,11 +103,11 @@ public class ClsSet { ...@@ -103,11 +103,11 @@ public class ClsSet {
} }
private static NClass getCls(String fullName, Map<String, NClass> names) { private static NClass getCls(String fullName, Map<String, NClass> names) {
NClass id = names.get(fullName); NClass cls = names.get(fullName);
if (id == null && !names.containsKey(fullName)) { if (cls == null) {
LOG.debug("Class not found: {}", fullName); LOG.debug("Class not found: {}", fullName);
} }
return id; return cls;
} }
void save(File output) throws IOException { void save(File output) throws IOException {
......
...@@ -7,7 +7,7 @@ public class NClass { ...@@ -7,7 +7,7 @@ public class NClass {
private final String name; private final String name;
private NClass[] parents; private NClass[] parents;
private int id; private final int id;
public NClass(String name, int id) { public NClass(String name, int id) {
this.name = name; this.name = name;
...@@ -22,10 +22,6 @@ public class NClass { ...@@ -22,10 +22,6 @@ public class NClass {
return id; return id;
} }
public void setId(int id) {
this.id = id;
}
public NClass[] getParents() { public NClass[] getParents() {
return parents; return parents;
} }
......
...@@ -10,6 +10,7 @@ import org.jetbrains.annotations.Nullable; ...@@ -10,6 +10,7 @@ import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import jadx.core.Consts;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.FieldReplaceAttr; import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
...@@ -624,6 +625,11 @@ public class InsnGen { ...@@ -624,6 +625,11 @@ public class InsnGen {
break; break;
case SUPER: case SUPER:
ClassInfo superCallCls = getClassForSuperCall(code, callMth);
if (superCallCls != null) {
useClass(code, superCallCls);
code.add('.');
}
// use 'super' instead 'this' in 0 arg // use 'super' instead 'this' in 0 arg
code.add("super").add('.'); code.add("super").add('.');
k++; k++;
...@@ -647,6 +653,36 @@ public class InsnGen { ...@@ -647,6 +653,36 @@ public class InsnGen {
generateMethodArguments(code, insn, k, callMthNode); generateMethodArguments(code, insn, k, callMthNode);
} }
@Nullable
private ClassInfo getClassForSuperCall(CodeWriter code, MethodInfo callMth) {
ClassNode useCls = mth.getParentClass();
ClassInfo insnCls = useCls.getAlias();
ClassInfo declClass = callMth.getDeclClass();
if (insnCls.equals(declClass)) {
return null;
}
ClassNode topClass = useCls.getTopParentClass();
if (topClass.getClassInfo().equals(declClass)) {
return declClass;
}
// search call class
ClassNode nextParent = useCls;
do {
ClassInfo nextClsInfo = nextParent.getClassInfo();
if (nextClsInfo.equals(declClass)
|| ArgType.isInstanceOf(mth.root(), nextClsInfo.getType(), declClass.getType())) {
if (nextParent == useCls) {
return null;
}
return nextClsInfo;
}
nextParent = nextParent.getParentClass();
} while (nextParent != null && nextParent != topClass);
// search failed, just return parent class
return useCls.getParentClass().getClassInfo();
}
void generateMethodArguments(CodeWriter code, InsnNode insn, int startArgNum, void generateMethodArguments(CodeWriter code, InsnNode insn, int startArgNum,
@Nullable MethodNode callMth) throws CodegenException { @Nullable MethodNode callMth) throws CodegenException {
int k = startArgNum; int k = startArgNum;
...@@ -744,6 +780,9 @@ public class InsnGen { ...@@ -744,6 +780,9 @@ public class InsnGen {
return false; return false;
} }
InsnNode inl = mia.getInsn(); InsnNode inl = mia.getInsn();
if (Consts.DEBUG) {
code.add("/* inline method: ").add(callMthNode.toString()).add("*/").startLine();
}
if (callMthNode.getMethodInfo().getArgumentsTypes().isEmpty()) { if (callMthNode.getMethodInfo().getArgumentsTypes().isEmpty()) {
makeInsn(inl, code, Flags.BODY_ONLY); makeInsn(inl, code, Flags.BODY_ONLY);
} else { } else {
......
...@@ -483,21 +483,7 @@ public class Deobfuscator { ...@@ -483,21 +483,7 @@ public class Deobfuscator {
if (name.length() > maxLength) { if (name.length() > maxLength) {
return "x" + Integer.toHexString(name.hashCode()); return "x" + Integer.toHexString(name.hashCode());
} }
if (!NameMapper.isAllCharsPrintable(name)) { return NameMapper.removeInvalidCharsMiddle(name);
return removeInvalidChars(name);
}
return name;
}
private String removeInvalidChars(String name) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < name.length(); i++) {
int ch = name.charAt(i);
if (NameMapper.isPrintableChar(ch)) {
sb.append((char) ch);
}
}
return sb.toString();
} }
private void dumpClassAlias(ClassNode cls) { private void dumpClassAlias(ClassNode cls) {
......
...@@ -91,6 +91,14 @@ public class NameMapper { ...@@ -91,6 +91,14 @@ public class NameMapper {
&& isAllCharsPrintable(str); && isAllCharsPrintable(str);
} }
public static boolean isValidIdentifierStart(int codePoint) {
return Character.isJavaIdentifierStart(codePoint);
}
public static boolean isValidIdentifierPart(int codePoint) {
return Character.isJavaIdentifierPart(codePoint);
}
public static boolean isPrintableChar(int c) { public static boolean isPrintableChar(int c) {
return 32 <= c && c <= 126; return 32 <= c && c <= 126;
} }
...@@ -105,6 +113,49 @@ public class NameMapper { ...@@ -105,6 +113,49 @@ public class NameMapper {
return true; return true;
} }
/**
* Return modified string with removed:
* <p><ul>
* <li> not printable chars (including unicode)
* <li> chars not valid for java identifier part
* </ul><p>
* Note: this 'middle' method must be used with prefixed string:
* <p><ul>
* <li> can leave invalid chars for java identifier start (i.e numbers)
* <li> result not checked for reserved words
* </ul><p>
*/
public static String removeInvalidCharsMiddle(String name) {
if (isValidIdentifier(name) && isAllCharsPrintable(name)) {
return name;
}
int len = name.length();
StringBuilder sb = new StringBuilder(len);
for (int i = 0; i < len; i++) {
int codePoint = name.codePointAt(i);
if (isPrintableChar(codePoint) && isValidIdentifierPart(codePoint)) {
sb.append((char) codePoint);
}
}
return sb.toString();
}
/**
* Return string with removed invalid chars, see {@link #removeInvalidCharsMiddle}
* <p>
* Prepend prefix if first char is not valid as java identifier start char.
*/
public static String removeInvalidChars(String name, String prefix) {
String result = removeInvalidCharsMiddle(name);
if (!result.isEmpty()) {
int codePoint = result.codePointAt(0);
if (!isValidIdentifierStart(codePoint)) {
return prefix + result;
}
}
return result;
}
private NameMapper() { private NameMapper() {
} }
} }
...@@ -43,8 +43,12 @@ public final class MethodInfo { ...@@ -43,8 +43,12 @@ public final class MethodInfo {
} }
public String makeSignature(boolean includeRetType) { public String makeSignature(boolean includeRetType) {
return makeSignature(false, includeRetType);
}
public String makeSignature(boolean useAlias, boolean includeRetType) {
StringBuilder signature = new StringBuilder(); StringBuilder signature = new StringBuilder();
signature.append(name); signature.append(useAlias ? alias : name);
signature.append('('); signature.append('(');
for (ArgType arg : args) { for (ArgType arg : args) {
signature.append(TypeGen.signature(arg)); signature.append(TypeGen.signature(arg));
......
...@@ -38,12 +38,12 @@ import jadx.core.utils.exceptions.JadxRuntimeException; ...@@ -38,12 +38,12 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
import static jadx.core.dex.nodes.ProcessState.UNLOADED; import static jadx.core.dex.nodes.ProcessState.UNLOADED;
public class ClassNode extends LineAttrNode implements ILoadable, IDexNode { public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
private static final Logger LOG = LoggerFactory.getLogger(ClassNode.class); private static final Logger LOG = LoggerFactory.getLogger(ClassNode.class);
private final DexNode dex; private final DexNode dex;
private final ClassInfo clsInfo; private final ClassInfo clsInfo;
private final AccessInfo accessFlags; private AccessInfo accessFlags;
private ArgType superClass; private ArgType superClass;
private List<ArgType> interfaces; private List<ArgType> interfaces;
private Map<ArgType, List<ArgType>> genericMap; private Map<ArgType, List<ArgType>> genericMap;
...@@ -409,11 +409,17 @@ public class ClassNode extends LineAttrNode implements ILoadable, IDexNode { ...@@ -409,11 +409,17 @@ public class ClassNode extends LineAttrNode implements ILoadable, IDexNode {
return null; return null;
} }
@Override
public AccessInfo getAccessFlags() { public AccessInfo getAccessFlags() {
return accessFlags; return accessFlags;
} }
@Override @Override
public void setAccessFlags(AccessInfo accessFlags) {
this.accessFlags = accessFlags;
}
@Override
public DexNode dex() { public DexNode dex() {
return dex; return dex;
} }
......
...@@ -8,11 +8,11 @@ import jadx.core.dex.info.AccessInfo.AFType; ...@@ -8,11 +8,11 @@ import jadx.core.dex.info.AccessInfo.AFType;
import jadx.core.dex.info.FieldInfo; import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.ArgType;
public class FieldNode extends LineAttrNode { public class FieldNode extends LineAttrNode implements ICodeNode {
private final ClassNode parent; private final ClassNode parent;
private final FieldInfo fieldInfo; private final FieldInfo fieldInfo;
private final AccessInfo accFlags; private AccessInfo accFlags;
private ArgType type; private ArgType type;
...@@ -32,10 +32,16 @@ public class FieldNode extends LineAttrNode { ...@@ -32,10 +32,16 @@ public class FieldNode extends LineAttrNode {
return fieldInfo; return fieldInfo;
} }
@Override
public AccessInfo getAccessFlags() { public AccessInfo getAccessFlags() {
return accFlags; return accFlags;
} }
@Override
public void setAccessFlags(AccessInfo accFlags) {
this.accFlags = accFlags;
}
public String getName() { public String getName() {
return fieldInfo.getName(); return fieldInfo.getName();
} }
...@@ -57,6 +63,21 @@ public class FieldNode extends LineAttrNode { ...@@ -57,6 +63,21 @@ public class FieldNode extends LineAttrNode {
} }
@Override @Override
public String typeName() {
return "field";
}
@Override
public DexNode dex() {
return parent.dex();
}
@Override
public RootNode root() {
return parent.root();
}
@Override
public int hashCode() { public int hashCode() {
return fieldInfo.hashCode(); return fieldInfo.hashCode();
} }
......
package jadx.core.dex.nodes;
import jadx.core.dex.attributes.IAttributeNode;
import jadx.core.dex.info.AccessInfo;
public interface ICodeNode extends IDexNode, IAttributeNode {
AccessInfo getAccessFlags();
void setAccessFlags(AccessInfo newAccessFlags);
}
...@@ -46,7 +46,7 @@ import jadx.core.utils.exceptions.JadxRuntimeException; ...@@ -46,7 +46,7 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
import static jadx.core.utils.Utils.lockList; import static jadx.core.utils.Utils.lockList;
public class MethodNode extends LineAttrNode implements ILoadable, IDexNode { public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
private static final Logger LOG = LoggerFactory.getLogger(MethodNode.class); private static final Logger LOG = LoggerFactory.getLogger(MethodNode.class);
private final MethodInfo mthInfo; private final MethodInfo mthInfo;
...@@ -607,12 +607,14 @@ public class MethodNode extends LineAttrNode implements ILoadable, IDexNode { ...@@ -607,12 +607,14 @@ public class MethodNode extends LineAttrNode implements ILoadable, IDexNode {
return sVars; return sVars;
} }
@Override
public AccessInfo getAccessFlags() { public AccessInfo getAccessFlags() {
return accFlags; return accFlags;
} }
public void setAccFlags(AccessInfo accFlags) { @Override
this.accFlags = accFlags; public void setAccessFlags(AccessInfo newAccessFlags) {
this.accFlags = newAccessFlags;
} }
public Region getRegion() { public Region getRegion() {
......
...@@ -5,6 +5,7 @@ import java.util.Objects; ...@@ -5,6 +5,7 @@ import java.util.Objects;
import com.android.dx.rop.code.AccessFlags; import com.android.dx.rop.code.AccessFlags;
import jadx.core.Consts;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.FieldReplaceAttr; import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
...@@ -15,6 +16,7 @@ import jadx.core.dex.info.MethodInfo; ...@@ -15,6 +16,7 @@ import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.IndexInsnNode; import jadx.core.dex.instructions.IndexInsnNode;
import jadx.core.dex.instructions.InsnType; import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.InvokeNode; import jadx.core.dex.instructions.InvokeNode;
import jadx.core.dex.instructions.InvokeType;
import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg; import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.InsnWrapArg; import jadx.core.dex.instructions.args.InsnWrapArg;
...@@ -140,7 +142,11 @@ public class ClassModifier extends AbstractVisitor { ...@@ -140,7 +142,11 @@ public class ClassModifier extends AbstractVisitor {
return; return;
} }
if (removeBridgeMethod(cls, mth)) { if (removeBridgeMethod(cls, mth)) {
if (Consts.DEBUG) {
mth.addAttr(AType.COMMENTS, "Removed as synthetic bridge method");
} else {
mth.add(AFlag.DONT_GENERATE); mth.add(AFlag.DONT_GENERATE);
}
return; return;
} }
// remove synthetic constructor for inner classes // remove synthetic constructor for inner classes
...@@ -219,10 +225,18 @@ public class ClassModifier extends AbstractVisitor { ...@@ -219,10 +225,18 @@ public class ClassModifier extends AbstractVisitor {
private static boolean checkSyntheticWrapper(MethodNode mth, InsnNode insn) { private static boolean checkSyntheticWrapper(MethodNode mth, InsnNode insn) {
InsnType insnType = insn.getType(); InsnType insnType = insn.getType();
if (insnType == InsnType.INVOKE) { if (insnType != InsnType.INVOKE) {
MethodInfo callMth = ((InvokeNode) insn).getCallMth(); return false;
}
InvokeNode invokeInsn = (InvokeNode) insn;
if (invokeInsn.getInvokeType() == InvokeType.SUPER) {
return false;
}
MethodInfo callMth = invokeInsn.getCallMth();
MethodNode wrappedMth = mth.root().deepResolveMethod(callMth); MethodNode wrappedMth = mth.root().deepResolveMethod(callMth);
if (wrappedMth != null) { if (wrappedMth == null) {
return false;
}
AccessInfo wrappedAccFlags = wrappedMth.getAccessFlags(); AccessInfo wrappedAccFlags = wrappedMth.getAccessFlags();
if (wrappedAccFlags.isStatic()) { if (wrappedAccFlags.isStatic()) {
return false; return false;
...@@ -240,19 +254,16 @@ public class ClassModifier extends AbstractVisitor { ...@@ -240,19 +254,16 @@ public class ClassModifier extends AbstractVisitor {
return false; return false;
} }
} }
String alias = mth.getAlias(); // remove confirmed, change visibility and name if needed
if (Objects.equals(wrappedMth.getAlias(), alias)) {
return true;
}
if (!wrappedAccFlags.isPublic()) { if (!wrappedAccFlags.isPublic()) {
// must be public // must be public
FixAccessModifiers.changeVisibility(wrappedMth, AccessFlags.ACC_PUBLIC); FixAccessModifiers.changeVisibility(wrappedMth, AccessFlags.ACC_PUBLIC);
} }
String alias = mth.getAlias();
if (!Objects.equals(wrappedMth.getAlias(), alias)) {
wrappedMth.getMethodInfo().setAlias(alias); wrappedMth.getMethodInfo().setAlias(alias);
return true;
}
} }
return false; return true;
} }
private static boolean registersAndCastsOnly(InsnArg arg) { private static boolean registersAndCastsOnly(InsnArg arg) {
...@@ -274,8 +285,7 @@ public class ClassModifier extends AbstractVisitor { ...@@ -274,8 +285,7 @@ public class ClassModifier extends AbstractVisitor {
if (otherMth != mth) { if (otherMth != mth) {
MethodInfo omi = otherMth.getMethodInfo(); MethodInfo omi = otherMth.getMethodInfo();
if (omi.getName().equals(mi.getName()) if (omi.getName().equals(mi.getName())
&& omi.getArgumentsTypes().size() == mi.getArgumentsTypes().size()) { && Objects.equals(omi.getArgumentsTypes(), mi.getArgumentsTypes())) {
// TODO: check objects types
return false; return false;
} }
} }
......
...@@ -4,6 +4,7 @@ import com.android.dx.rop.code.AccessFlags; ...@@ -4,6 +4,7 @@ import com.android.dx.rop.code.AccessFlags;
import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.AType;
import jadx.core.dex.info.AccessInfo; import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.nodes.ICodeNode;
import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode; import jadx.core.dex.nodes.RootNode;
...@@ -32,12 +33,12 @@ public class FixAccessModifiers extends AbstractVisitor { ...@@ -32,12 +33,12 @@ public class FixAccessModifiers extends AbstractVisitor {
} }
} }
public static void changeVisibility(MethodNode mth, int newVisFlag) { public static void changeVisibility(ICodeNode node, int newVisFlag) {
AccessInfo accessFlags = mth.getAccessFlags(); AccessInfo accessFlags = node.getAccessFlags();
AccessInfo newAccFlags = accessFlags.changeVisibility(newVisFlag); AccessInfo newAccFlags = accessFlags.changeVisibility(newVisFlag);
if (newAccFlags != accessFlags) { if (newAccFlags != accessFlags) {
mth.setAccFlags(newAccFlags); node.setAccessFlags(newAccFlags);
mth.addAttr(AType.COMMENTS, "access modifiers changed from: " + accessFlags.rawString()); node.addAttr(AType.COMMENTS, "access modifiers changed from: " + accessFlags.rawString());
} }
} }
......
...@@ -2,28 +2,44 @@ package jadx.core.dex.visitors; ...@@ -2,28 +2,44 @@ package jadx.core.dex.visitors;
import java.util.List; import java.util.List;
import com.android.dx.rop.code.AccessFlags;
import jadx.core.Consts;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.MethodInlineAttr; import jadx.core.dex.attributes.nodes.MethodInlineAttr;
import jadx.core.dex.info.AccessInfo; import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.instructions.IndexInsnNode;
import jadx.core.dex.instructions.InsnType; import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.InvokeNode;
import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg; import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.InsnWrapArg;
import jadx.core.dex.instructions.args.RegisterArg; import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.BlockNode; import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.exceptions.JadxException; import jadx.core.utils.exceptions.JadxException;
/** @JadxVisitor(
* Inline synthetic methods. name = "InlineMethods",
*/ desc = "Inline synthetic static methods",
runAfter = {
FixAccessModifiers.class,
ClassModifier.class
}
)
public class MethodInlineVisitor extends AbstractVisitor { public class MethodInlineVisitor extends AbstractVisitor {
@Override @Override
public void visit(MethodNode mth) throws JadxException { public void visit(MethodNode mth) throws JadxException {
if (mth.isNoCode() || mth.contains(AFlag.DONT_GENERATE)) {
return;
}
AccessInfo accessFlags = mth.getAccessFlags(); AccessInfo accessFlags = mth.getAccessFlags();
if (accessFlags.isSynthetic() if (accessFlags.isSynthetic() && accessFlags.isStatic()
&& accessFlags.isStatic()
&& mth.getBasicBlocks().size() == 2) { && mth.getBasicBlocks().size() == 2) {
BlockNode returnBlock = mth.getBasicBlocks().get(1); BlockNode returnBlock = mth.getBasicBlocks().get(1);
if (returnBlock.contains(AFlag.RETURN) || returnBlock.getInstructions().isEmpty()) { if (returnBlock.contains(AFlag.RETURN) || returnBlock.getInstructions().isEmpty()) {
...@@ -72,7 +88,47 @@ public class MethodInlineVisitor extends AbstractVisitor { ...@@ -72,7 +88,47 @@ public class MethodInlineVisitor extends AbstractVisitor {
} }
private static void addInlineAttr(MethodNode mth, InsnNode insn) { private static void addInlineAttr(MethodNode mth, InsnNode insn) {
if (fixVisibilityOfInlineCode(mth, insn)) {
if (Consts.DEBUG) {
mth.addAttr(AType.COMMENTS, "Removed for inline");
} else {
mth.addAttr(new MethodInlineAttr(insn)); mth.addAttr(new MethodInlineAttr(insn));
mth.add(AFlag.DONT_GENERATE); mth.add(AFlag.DONT_GENERATE);
} }
}
}
private static boolean fixVisibilityOfInlineCode(MethodNode mth, InsnNode insn) {
int newVisFlag = AccessFlags.ACC_PUBLIC; // TODO: calculate more precisely
InsnType insnType = insn.getType();
if (insnType == InsnType.INVOKE) {
InvokeNode invoke = (InvokeNode) insn;
MethodNode callMthNode = mth.root().deepResolveMethod(invoke.getCallMth());
if (callMthNode != null) {
FixAccessModifiers.changeVisibility(callMthNode, newVisFlag);
}
return true;
}
if (insnType == InsnType.ONE_ARG) {
InsnArg arg = insn.getArg(0);
if (!arg.isInsnWrap()) {
return false;
}
return fixVisibilityOfInlineCode(mth, ((InsnWrapArg) arg).getWrapInsn());
}
if (insn instanceof IndexInsnNode) {
Object indexObj = ((IndexInsnNode) insn).getIndex();
if (indexObj instanceof FieldInfo) {
FieldNode fieldNode = mth.root().deepResolveField(((FieldInfo) indexObj));
if (fieldNode != null) {
FixAccessModifiers.changeVisibility(fieldNode, newVisFlag);
}
return true;
}
}
if (Consts.DEBUG) {
mth.addAttr(AType.COMMENTS, "JADX DEBUG: can't inline method, not implemented redirect type: " + insn);
}
return false;
}
} }
...@@ -72,14 +72,9 @@ public class RenameVisitor extends AbstractVisitor { ...@@ -72,14 +72,9 @@ public class RenameVisitor extends AbstractVisitor {
ClassInfo classInfo = cls.getClassInfo(); ClassInfo classInfo = cls.getClassInfo();
ClassInfo alias = classInfo.getAlias(); ClassInfo alias = classInfo.getAlias();
String clsName = alias.getShortName(); String clsName = alias.getShortName();
String newShortName = null;
char firstChar = clsName.charAt(0); String newShortName = fixClsShortName(clsName);
if (Character.isDigit(firstChar)) { if (!newShortName.equals(clsName)) {
newShortName = Consts.ANONYMOUS_CLASS_PREFIX + clsName;
} else if (firstChar == '$') {
newShortName = "C" + clsName;
}
if (newShortName != null) {
classInfo.rename(cls.root(), alias.makeFullClsName(newShortName, true)); classInfo.rename(cls.root(), alias.makeFullClsName(newShortName, true));
} }
if (alias.getPackage().isEmpty()) { if (alias.getPackage().isEmpty()) {
...@@ -89,6 +84,17 @@ public class RenameVisitor extends AbstractVisitor { ...@@ -89,6 +84,17 @@ public class RenameVisitor extends AbstractVisitor {
} }
} }
private String fixClsShortName(String clsName) {
char firstChar = clsName.charAt(0);
if (Character.isDigit(firstChar)) {
return Consts.ANONYMOUS_CLASS_PREFIX + NameMapper.removeInvalidCharsMiddle(clsName);
}
if (firstChar == '$') {
return 'C' + NameMapper.removeInvalidCharsMiddle(clsName);
}
return NameMapper.removeInvalidChars(clsName, "C");
}
private void checkFields(ClassNode cls) { private void checkFields(ClassNode cls) {
Set<String> names = new HashSet<>(); Set<String> names = new HashSet<>();
for (FieldNode field : cls.getFields()) { for (FieldNode field : cls.getFields()) {
...@@ -101,17 +107,22 @@ public class RenameVisitor extends AbstractVisitor { ...@@ -101,17 +107,22 @@ public class RenameVisitor extends AbstractVisitor {
} }
private void checkMethods(ClassNode cls) { private void checkMethods(ClassNode cls) {
for (MethodNode mth : cls.getMethods()) {
if (!NameMapper.isValidIdentifier(mth.getAlias())) {
deobfuscator.forceRenameMethod(mth);
}
}
Set<String> names = new HashSet<>(); Set<String> names = new HashSet<>();
for (MethodNode mth : cls.getMethods()) { for (MethodNode mth : cls.getMethods()) {
AccessInfo accessFlags = mth.getAccessFlags(); AccessInfo accessFlags = mth.getAccessFlags();
if (accessFlags.isConstructor() if (accessFlags.isConstructor()
|| accessFlags.isBridge() || accessFlags.isBridge()
|| accessFlags.isSynthetic() || accessFlags.isSynthetic()
|| mth.contains(AFlag.DONT_GENERATE)) { || mth.contains(AFlag.DONT_GENERATE) /* this flag not set yet */) {
continue; continue;
} }
String signature = mth.getMethodInfo().makeSignature(false); String signature = mth.getMethodInfo().makeSignature(true, false);
if (!names.add(signature) || !NameMapper.isValidIdentifier(mth.getAlias())) { if (!names.add(signature)) {
deobfuscator.forceRenameMethod(mth); deobfuscator.forceRenameMethod(mth);
} }
} }
......
package jadx.core.dex.visitors.blocksmaker; package jadx.core.dex.visitors.blocksmaker;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
...@@ -47,6 +50,7 @@ public class BlockSplitter extends AbstractVisitor { ...@@ -47,6 +50,7 @@ public class BlockSplitter extends AbstractVisitor {
removeJumpAttr(mth); removeJumpAttr(mth);
removeInsns(mth); removeInsns(mth);
removeEmptyDetachedBlocks(mth); removeEmptyDetachedBlocks(mth);
removeUnreachableBlocks(mth);
initBlocksInTargetNodes(mth); initBlocksInTargetNodes(mth);
removeJumpAttributes(mth.getInstructions()); removeJumpAttributes(mth.getInstructions());
...@@ -335,4 +339,37 @@ public class BlockSplitter extends AbstractVisitor { ...@@ -335,4 +339,37 @@ public class BlockSplitter extends AbstractVisitor {
} }
} }
} }
private void removeUnreachableBlocks(MethodNode mth) {
Set<BlockNode> toRemove = new LinkedHashSet<>();
for (BlockNode block : mth.getBasicBlocks()) {
if (block.getPredecessors().isEmpty() && block != mth.getEnterBlock()) {
toRemove.add(block);
collectSuccessors(block, toRemove);
}
}
if (!toRemove.isEmpty()) {
mth.getBasicBlocks().removeIf(toRemove::contains);
int insnsCount = toRemove.stream().mapToInt(block -> block.getInstructions().size()).sum();
mth.addAttr(AType.COMMENTS, "JADX INFO: unreachable blocks removed: " + toRemove.size()
+ ", instructions: " + insnsCount);
}
}
private void collectSuccessors(BlockNode startBlock, Set<BlockNode> toRemove) {
Deque<BlockNode> stack = new ArrayDeque<>();
stack.add(startBlock);
while (!stack.isEmpty()) {
BlockNode block = stack.pop();
if (!toRemove.contains(block)) {
for (BlockNode successor : block.getSuccessors()) {
if (toRemove.containsAll(successor.getPredecessors())) {
stack.push(successor);
}
}
}
toRemove.add(block);
}
}
} }
...@@ -54,6 +54,10 @@ public class AndroidResourcesUtils { ...@@ -54,6 +54,10 @@ public class AndroidResourcesUtils {
} }
LOG.info("App 'R' class not found, put all resources ids to : '{}'", fullName); LOG.info("App 'R' class not found, put all resources ids to : '{}'", fullName);
resCls = makeClass(root, fullName, resStorage); resCls = makeClass(root, fullName, resStorage);
if (resCls == null) {
// We are in an APK without code therefore we don't have to update an 'R' class with the resources
return null;
}
addResourceFields(resCls, resStorage, false); addResourceFields(resCls, resStorage, false);
return resCls; return resCls;
} }
...@@ -81,18 +85,18 @@ public class AndroidResourcesUtils { ...@@ -81,18 +85,18 @@ public class AndroidResourcesUtils {
return rCls; return rCls;
} }
private static void addResourceFields(ClassNode cls, ResourceStorage resStorage, boolean rClsExists) { private static void addResourceFields(ClassNode resCls, ResourceStorage resStorage, boolean rClsExists) {
Map<String, ClassNode> innerClsMap = new TreeMap<>(); Map<String, ClassNode> innerClsMap = new TreeMap<>();
if (rClsExists) { if (rClsExists) {
for (ClassNode innerClass : cls.getInnerClasses()) { for (ClassNode innerClass : resCls.getInnerClasses()) {
innerClsMap.put(innerClass.getShortName(), innerClass); innerClsMap.put(innerClass.getShortName(), innerClass);
} }
} }
for (ResourceEntry resource : resStorage.getResources()) { for (ResourceEntry resource : resStorage.getResources()) {
ClassNode typeCls = innerClsMap.computeIfAbsent(resource.getTypeName(), name -> { ClassNode typeCls = innerClsMap.computeIfAbsent(resource.getTypeName(), name -> {
ClassNode newTypeCls = new ClassNode(cls.dex(), cls.getFullName() + "$" + name, ClassNode newTypeCls = new ClassNode(resCls.dex(), resCls.getFullName() + "$" + name,
AccessFlags.ACC_PUBLIC | AccessFlags.ACC_STATIC | AccessFlags.ACC_FINAL); AccessFlags.ACC_PUBLIC | AccessFlags.ACC_STATIC | AccessFlags.ACC_FINAL);
cls.addInnerClass(newTypeCls); resCls.addInnerClass(newTypeCls);
if (rClsExists) { if (rClsExists) {
newTypeCls.addAttr(AType.COMMENTS, "added by JADX"); newTypeCls.addAttr(AType.COMMENTS, "added by JADX");
} }
......
...@@ -77,8 +77,7 @@ public class InputFile { ...@@ -77,8 +77,7 @@ public class InputFile {
if (skipSources) { if (skipSources) {
return; return;
} }
LOG.warn("No dex files found in {}", file);
throw new DecodeException("Unsupported input file format: " + file);
} }
private void addDexFile(Dex dexBuf) { private void addDexFile(Dex dexBuf) {
......
package jadx.core.deobf;
import org.junit.Test;
import static jadx.core.deobf.NameMapper.isValidIdentifier;
import static jadx.core.deobf.NameMapper.removeInvalidChars;
import static jadx.core.deobf.NameMapper.removeInvalidCharsMiddle;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
public class NameMapperTest {
@Test
public void validIdentifiers() {
assertThat(isValidIdentifier("ACls"), is(true));
}
@Test
public void notValidIdentifiers() {
assertThat(isValidIdentifier("1cls"), is(false));
assertThat(isValidIdentifier("-cls"), is(false));
assertThat(isValidIdentifier("A-cls"), is(false));
}
@Test
public void testRemoveInvalidCharsMiddle() {
assertThat(removeInvalidCharsMiddle("1cls"), is("1cls"));
assertThat(removeInvalidCharsMiddle("-cls"), is("cls"));
assertThat(removeInvalidCharsMiddle("A-cls"), is("Acls"));
}
@Test
public void testRemoveInvalidChars() {
assertThat(removeInvalidChars("1cls", "C"), is("C1cls"));
assertThat(removeInvalidChars("-cls", "C"), is("cls"));
assertThat(removeInvalidChars("A-cls", "C"), is("Acls"));
}
}
...@@ -215,12 +215,14 @@ public abstract class IntegrationTest extends TestUtils { ...@@ -215,12 +215,14 @@ public abstract class IntegrationTest extends TestUtils {
rethrow("Original check failed", ie); rethrow("Original check failed", ie);
} }
// run 'check' method from decompiled class // run 'check' method from decompiled class
if (compile) {
try { try {
invoke("check"); invoke("check");
} catch (InvocationTargetException ie) { } catch (InvocationTargetException ie) {
rethrow("Decompiled check failed", ie); rethrow("Decompiled check failed", ie);
} }
System.out.println("Auto check: PASSED"); System.out.println("Auto check: PASSED");
}
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
fail("Auto check exception: " + e.getMessage()); fail("Auto check exception: " + e.getMessage());
......
package jadx.tests.integration.inline;
import org.junit.Test;
import jadx.core.dex.nodes.ClassNode;
import jadx.tests.api.IntegrationTest;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertThat;
public class TestSyntheticInline2 extends IntegrationTest {
public static class Base {
protected void call() {
System.out.println("base call");
}
}
public static class TestCls extends Base {
public class A {
public void invokeCall() {
TestCls.this.call();
}
public void invokeSuperCall() {
TestCls.super.call();
}
}
@Override
public void call() {
System.out.println("TestCls call");
}
public void check() {
A a = new A();
a.invokeSuperCall();
a.invokeCall();
}
}
@Test
public void test() {
disableCompilation(); // strange java compiler bug
ClassNode cls = getClassNode(TestCls.class); // Base class in unknown
String code = cls.getCode().toString();
assertThat(code, not(containsString("synthetic")));
assertThat(code, not(containsString("access$")));
assertThat(code, containsString("TestSyntheticInline2$TestCls.this.call();"));
assertThat(code, containsString("TestSyntheticInline2$TestCls.super.call();"));
}
@Test
public void testTopClass() {
ClassNode cls = getClassNode(TestSyntheticInline2.class);
String code = cls.getCode().toString();
assertThat(code, containsString(indent(1) + "TestCls.super.call();"));
}
}
...@@ -46,6 +46,6 @@ public class TestDuplicatedNames extends SmaliTest { ...@@ -46,6 +46,6 @@ public class TestDuplicatedNames extends SmaliTest {
assertThat(code, containsOne("this.f0fieldName")); assertThat(code, containsOne("this.f0fieldName"));
assertThat(code, containsOne("public Object run() {")); assertThat(code, containsOne("public Object run() {"));
assertThat(code, containsOne("public String m0run() {")); assertThat(code, containsOne("public String m1run() {"));
} }
} }
...@@ -35,7 +35,7 @@ public class JadxWrapper { ...@@ -35,7 +35,7 @@ public class JadxWrapper {
this.decompiler.getArgs().setInputFiles(Collections.singletonList(file)); this.decompiler.getArgs().setInputFiles(Collections.singletonList(file));
this.decompiler.load(); this.decompiler.load();
} catch (Exception e) { } catch (Exception e) {
LOG.error("Error load file: {}", file, e); LOG.error("Jadx init error", e);
} }
} }
......
...@@ -10,6 +10,7 @@ import java.util.Map; ...@@ -10,6 +10,7 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
...@@ -18,20 +19,19 @@ import jadx.cli.JadxCLIArgs; ...@@ -18,20 +19,19 @@ import jadx.cli.JadxCLIArgs;
import jadx.gui.ui.codearea.EditorTheme; import jadx.gui.ui.codearea.EditorTheme;
import jadx.gui.utils.LangLocale; import jadx.gui.utils.LangLocale;
import jadx.gui.utils.NLS; import jadx.gui.utils.NLS;
import jadx.gui.utils.Utils;
import static jadx.gui.utils.Utils.FONT_HACK;
public class JadxSettings extends JadxCLIArgs { public class JadxSettings extends JadxCLIArgs {
private static final Logger LOG = LoggerFactory.getLogger(JadxSettings.class); private static final Logger LOG = LoggerFactory.getLogger(JadxSettings.class);
private static final String USER_HOME = System.getProperty("user.home"); private static final String USER_HOME = System.getProperty("user.home");
private static final int RECENT_FILES_COUNT = 15; private static final int RECENT_FILES_COUNT = 15;
private static final int CURRENT_SETTINGS_VERSION = 6; private static final int CURRENT_SETTINGS_VERSION = 8;
private static final Font DEFAULT_FONT = FONT_HACK != null ? FONT_HACK : new RSyntaxTextArea().getFont(); private static final Font DEFAULT_FONT = new RSyntaxTextArea().getFont();
static final Set<String> SKIP_FIELDS = new HashSet<>(Arrays.asList( static final Set<String> SKIP_FIELDS = new HashSet<>(Arrays.asList(
"files", "input", "outputDir", "verbose", "printHelp" "files", "input", "outDir", "outDirSrc", "outDirRes", "verbose", "printVersion", "printHelp"
)); ));
private String lastOpenFilePath = USER_HOME; private String lastOpenFilePath = USER_HOME;
private String lastSaveFilePath = USER_HOME; private String lastSaveFilePath = USER_HOME;
...@@ -262,8 +262,19 @@ public class JadxSettings extends JadxCLIArgs { ...@@ -262,8 +262,19 @@ public class JadxSettings extends JadxCLIArgs {
return Font.decode(fontStr); return Font.decode(fontStr);
} }
public void setFont(Font font) { public void setFont(@Nullable Font font) {
this.fontStr = font.getFontName() + addStyleName(font.getStyle()) + "-" + font.getSize(); if (font == null) {
this.fontStr = "";
return;
}
StringBuilder sb = new StringBuilder();
sb.append(font.getFontName());
String fontStyleName = Utils.getFontStyleName(font.getStyle()).replaceAll(" ", "");
if (!fontStyleName.isEmpty()) {
sb.append('-').append(fontStyleName.toUpperCase());
}
sb.append('-').append(font.getSize());
this.fontStr = sb.toString();
} }
public String getEditorThemePath() { public String getEditorThemePath() {
...@@ -274,19 +285,6 @@ public class JadxSettings extends JadxCLIArgs { ...@@ -274,19 +285,6 @@ public class JadxSettings extends JadxCLIArgs {
this.editorThemePath = editorThemePath; this.editorThemePath = editorThemePath;
} }
private static String addStyleName(int style) {
switch (style) {
case Font.BOLD:
return "-BOLD";
case Font.PLAIN:
return "-PLAIN";
case Font.ITALIC:
return "-ITALIC";
default:
return "";
}
}
private void upgradeSettings(int fromVersion) { private void upgradeSettings(int fromVersion) {
LOG.debug("upgrade settings from version: {} to {}", fromVersion, CURRENT_SETTINGS_VERSION); LOG.debug("upgrade settings from version: {} to {}", fromVersion, CURRENT_SETTINGS_VERSION);
if (fromVersion == 0) { if (fromVersion == 0) {
...@@ -319,6 +317,18 @@ public class JadxSettings extends JadxCLIArgs { ...@@ -319,6 +317,18 @@ public class JadxSettings extends JadxCLIArgs {
} }
if (fromVersion == 5) { if (fromVersion == 5) {
setRespectBytecodeAccessModifiers(false); setRespectBytecodeAccessModifiers(false);
fromVersion++;
}
if (fromVersion == 6) {
if (getFont().getFontName().equals("Hack Regular")) {
setFont(null);
}
fromVersion++;
}
if (fromVersion == 7) {
outDir = null;
outDirSrc = null;
outDirRes = null;
} }
settingsVersion = CURRENT_SETTINGS_VERSION; settingsVersion = CURRENT_SETTINGS_VERSION;
sync(); sync();
......
...@@ -16,6 +16,7 @@ import jadx.gui.ui.MainWindow; ...@@ -16,6 +16,7 @@ import jadx.gui.ui.MainWindow;
import jadx.gui.ui.codearea.EditorTheme; import jadx.gui.ui.codearea.EditorTheme;
import jadx.gui.utils.LangLocale; import jadx.gui.utils.LangLocale;
import jadx.gui.utils.NLS; import jadx.gui.utils.NLS;
import jadx.gui.utils.Utils;
public class JadxSettingsWindow extends JDialog { public class JadxSettingsWindow extends JDialog {
private static final long serialVersionUID = -1804570470377354148L; private static final long serialVersionUID = -1804570470377354148L;
...@@ -73,6 +74,7 @@ public class JadxSettingsWindow extends JDialog { ...@@ -73,6 +74,7 @@ public class JadxSettingsWindow extends JDialog {
JButton cancelButton = new JButton(NLS.str("preferences.cancel")); JButton cancelButton = new JButton(NLS.str("preferences.cancel"));
cancelButton.addActionListener(event -> { cancelButton.addActionListener(event -> {
JadxSettingsAdapter.fill(settings, startSettings); JadxSettingsAdapter.fill(settings, startSettings);
mainWindow.loadSettings();
dispose(); dispose();
}); });
...@@ -86,6 +88,8 @@ public class JadxSettingsWindow extends JDialog { ...@@ -86,6 +88,8 @@ public class JadxSettingsWindow extends JDialog {
if (res == JOptionPane.YES_OPTION) { if (res == JOptionPane.YES_OPTION) {
String defaults = JadxSettingsAdapter.makeString(JadxSettings.makeDefault()); String defaults = JadxSettingsAdapter.makeString(JadxSettings.makeDefault());
JadxSettingsAdapter.fill(settings, defaults); JadxSettingsAdapter.fill(settings, defaults);
mainWindow.loadSettings();
needReload();
getContentPane().removeAll(); getContentPane().removeAll();
initUI(); initUI();
pack(); pack();
...@@ -103,7 +107,9 @@ public class JadxSettingsWindow extends JDialog { ...@@ -103,7 +107,9 @@ public class JadxSettingsWindow extends JDialog {
buttonPane.add(cancelButton); buttonPane.add(cancelButton);
Container contentPane = getContentPane(); Container contentPane = getContentPane();
contentPane.add(panel, BorderLayout.CENTER); JScrollPane scrollPane = new JScrollPane(panel);
scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
contentPane.add(scrollPane, BorderLayout.CENTER);
contentPane.add(buttonPane, BorderLayout.PAGE_END); contentPane.add(buttonPane, BorderLayout.PAGE_END);
getRootPane().setDefaultButton(saveBtn); getRootPane().setDefaultButton(saveBtn);
} }
...@@ -164,21 +170,6 @@ public class JadxSettingsWindow extends JDialog { ...@@ -164,21 +170,6 @@ public class JadxSettingsWindow extends JDialog {
private SettingsGroup makeEditorGroup() { private SettingsGroup makeEditorGroup() {
JButton fontBtn = new JButton(NLS.str("preferences.select_font")); JButton fontBtn = new JButton(NLS.str("preferences.select_font"));
fontBtn.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
JFontChooser fontChooser = new JFontChooser();
fontChooser.setSelectedFont(settings.getFont());
int result = fontChooser.showDialog(JadxSettingsWindow.this);
if (result == JFontChooser.OK_OPTION) {
Font font = fontChooser.getSelectedFont();
LOG.debug("Selected Font: {}", font);
settings.setFont(font);
mainWindow.updateFont(font);
mainWindow.loadSettings();
}
}
});
EditorTheme[] editorThemes = EditorTheme.getAllThemes(); EditorTheme[] editorThemes = EditorTheme.getAllThemes();
JComboBox<EditorTheme> themesCbx = new JComboBox<>(editorThemes); JComboBox<EditorTheme> themesCbx = new JComboBox<>(editorThemes);
...@@ -196,11 +187,33 @@ public class JadxSettingsWindow extends JDialog { ...@@ -196,11 +187,33 @@ public class JadxSettingsWindow extends JDialog {
}); });
SettingsGroup other = new SettingsGroup(NLS.str("preferences.editor")); SettingsGroup other = new SettingsGroup(NLS.str("preferences.editor"));
other.addRow(NLS.str("preferences.font"), fontBtn); JLabel fontLabel = other.addRow(getFontLabelStr(), fontBtn);
other.addRow(NLS.str("preferences.theme"), themesCbx); other.addRow(NLS.str("preferences.theme"), themesCbx);
fontBtn.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
JFontChooser fontChooser = new JFontChooser();
fontChooser.setSelectedFont(settings.getFont());
int result = fontChooser.showDialog(JadxSettingsWindow.this);
if (result == JFontChooser.OK_OPTION) {
Font font = fontChooser.getSelectedFont();
LOG.debug("Selected Font: {}", font);
settings.setFont(font);
mainWindow.loadSettings();
fontLabel.setText(getFontLabelStr());
}
}
});
return other; return other;
} }
private String getFontLabelStr() {
Font font = settings.getFont();
String fontStyleName = Utils.getFontStyleName(font.getStyle());
return NLS.str("preferences.font") + ": " + font.getFontName() + " " + fontStyleName + " " + font.getSize();
}
private SettingsGroup makeDecompilationGroup() { private SettingsGroup makeDecompilationGroup() {
JCheckBox fallback = new JCheckBox(); JCheckBox fallback = new JCheckBox();
fallback.setSelected(settings.isFallbackMode()); fallback.setSelected(settings.isFallbackMode());
...@@ -342,11 +355,11 @@ public class JadxSettingsWindow extends JDialog { ...@@ -342,11 +355,11 @@ public class JadxSettingsWindow extends JDialog {
c.weighty = 1.0; c.weighty = 1.0;
} }
public void addRow(String label, JComponent comp) { public JLabel addRow(String label, JComponent comp) {
addRow(label, null, comp); return addRow(label, null, comp);
} }
public void addRow(String label, String tooltip, JComponent comp) { public JLabel addRow(String label, String tooltip, JComponent comp) {
c.gridy = row++; c.gridy = row++;
JLabel jLabel = new JLabel(label); JLabel jLabel = new JLabel(label);
jLabel.setLabelFor(comp); jLabel.setLabelFor(comp);
...@@ -371,6 +384,7 @@ public class JadxSettingsWindow extends JDialog { ...@@ -371,6 +384,7 @@ public class JadxSettingsWindow extends JDialog {
add(comp, c); add(comp, c);
comp.addPropertyChangeListener("enabled", evt -> jLabel.setEnabled((boolean) evt.getNewValue())); comp.addPropertyChangeListener("enabled", evt -> jLabel.setEnabled((boolean) evt.getNewValue()));
return jLabel;
} }
public void end() { public void end() {
......
...@@ -652,10 +652,6 @@ public class MainWindow extends JFrame { ...@@ -652,10 +652,6 @@ public class MainWindow extends JFrame {
setSize((int) (w * WINDOW_RATIO), (int) (h * WINDOW_RATIO)); setSize((int) (w * WINDOW_RATIO), (int) (h * WINDOW_RATIO));
} }
public void updateFont(Font font) {
setFont(font);
}
public static void registerBundledFonts() { public static void registerBundledFonts() {
GraphicsEnvironment grEnv = GraphicsEnvironment.getLocalGraphicsEnvironment(); GraphicsEnvironment grEnv = GraphicsEnvironment.getLocalGraphicsEnvironment();
if (Utils.FONT_HACK != null) { if (Utils.FONT_HACK != null) {
......
...@@ -8,6 +8,7 @@ import java.awt.datatransfer.Transferable; ...@@ -8,6 +8,7 @@ import java.awt.datatransfer.Transferable;
import java.io.InputStream; import java.io.InputStream;
import java.net.URL; import java.net.URL;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
...@@ -175,4 +176,19 @@ public class Utils { ...@@ -175,4 +176,19 @@ public class Utils {
LOG.error("Failed copy string '{}' to clipboard", text, e); LOG.error("Failed copy string '{}' to clipboard", text, e);
} }
} }
@NotNull
public static String getFontStyleName(int style) {
if (style == 0) {
return "plain";
}
StringBuilder sb = new StringBuilder();
if ((style & Font.BOLD) != 0) {
sb.append("bold");
}
if ((style & Font.ITALIC) != 0) {
sb.append(" italic");
}
return sb.toString().trim();
}
} }
...@@ -99,7 +99,7 @@ preferences.raw_cfg=Generate RAW CFG graphs ...@@ -99,7 +99,7 @@ preferences.raw_cfg=Generate RAW CFG graphs
preferences.font=Editor font preferences.font=Editor font
preferences.theme=Editor theme preferences.theme=Editor theme
preferences.start_jobs=Auto start background decompilation preferences.start_jobs=Auto start background decompilation
preferences.select_font=Select preferences.select_font=Change
preferences.deobfuscation_on=Enable deobfuscation preferences.deobfuscation_on=Enable deobfuscation
preferences.deobfuscation_force=Force rewrite deobfuscation map file preferences.deobfuscation_force=Force rewrite deobfuscation map file
preferences.deobfuscation_min_len=Minimum name length preferences.deobfuscation_min_len=Minimum name length
......
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