diff --git a/jadx-core/src/main/java/jadx/api/JadxArgsValidator.java b/jadx-core/src/main/java/jadx/api/JadxArgsValidator.java index 4bb856d29..7604b39e5 100644 --- a/jadx-core/src/main/java/jadx/api/JadxArgsValidator.java +++ b/jadx-core/src/main/java/jadx/api/JadxArgsValidator.java @@ -53,13 +53,18 @@ public class JadxArgsValidator { } else { outDir = makeDirFromInput(args); } + args.setOutDir(outDir); + } + 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)); } - args.setOutDir(outDir); - setFromOut(args); - checkDir(args.getOutDir()); - checkDir(args.getOutDirSrc()); - checkDir(args.getOutDirRes()); + checkDir(args.getOutDir(), "Output"); + checkDir(args.getOutDirSrc(), "Source output"); + checkDir(args.getOutDirRes(), "Resources output"); } @NotNull @@ -79,15 +84,6 @@ public class JadxArgsValidator { 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) { if (!file.exists()) { throw new JadxArgsValidateException("File not found " + file.getAbsolutePath()); @@ -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()) { - throw new JadxArgsValidateException("Output directory exists as file " + dir); + throw new JadxArgsValidateException(desc + " directory exists as file " + dir); } } diff --git a/jadx-core/src/main/java/jadx/core/Jadx.java b/jadx-core/src/main/java/jadx/core/Jadx.java index 24531bd3f..0519033ad 100644 --- a/jadx-core/src/main/java/jadx/core/Jadx.java +++ b/jadx-core/src/main/java/jadx/core/Jadx.java @@ -96,10 +96,10 @@ public class Jadx { passes.add(new SimplifyVisitor()); passes.add(new CheckRegions()); - passes.add(new MethodInlineVisitor()); passes.add(new ExtractFieldInit()); passes.add(new FixAccessModifiers()); passes.add(new ClassModifier()); + passes.add(new MethodInlineVisitor()); passes.add(new EnumVisitor()); passes.add(new LoopRegionVisitor()); diff --git a/jadx-core/src/main/java/jadx/core/clsp/ClsSet.java b/jadx-core/src/main/java/jadx/core/clsp/ClsSet.java index d4eb07859..338fac8cd 100644 --- a/jadx-core/src/main/java/jadx/core/clsp/ClsSet.java +++ b/jadx-core/src/main/java/jadx/core/clsp/ClsSet.java @@ -103,11 +103,11 @@ public class ClsSet { } private static NClass getCls(String fullName, Map names) { - NClass id = names.get(fullName); - if (id == null && !names.containsKey(fullName)) { + NClass cls = names.get(fullName); + if (cls == null) { LOG.debug("Class not found: {}", fullName); } - return id; + return cls; } void save(File output) throws IOException { diff --git a/jadx-core/src/main/java/jadx/core/clsp/NClass.java b/jadx-core/src/main/java/jadx/core/clsp/NClass.java index f7049f97d..e2eac23a5 100644 --- a/jadx-core/src/main/java/jadx/core/clsp/NClass.java +++ b/jadx-core/src/main/java/jadx/core/clsp/NClass.java @@ -7,7 +7,7 @@ public class NClass { private final String name; private NClass[] parents; - private int id; + private final int id; public NClass(String name, int id) { this.name = name; @@ -22,10 +22,6 @@ public class NClass { return id; } - public void setId(int id) { - this.id = id; - } - public NClass[] getParents() { return parents; } diff --git a/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java b/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java index 88037f960..788fafae9 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java @@ -10,6 +10,7 @@ import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import jadx.core.Consts; import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.nodes.FieldReplaceAttr; @@ -624,6 +625,11 @@ public class InsnGen { break; case SUPER: + ClassInfo superCallCls = getClassForSuperCall(code, callMth); + if (superCallCls != null) { + useClass(code, superCallCls); + code.add('.'); + } // use 'super' instead 'this' in 0 arg code.add("super").add('.'); k++; @@ -647,6 +653,36 @@ public class InsnGen { 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, @Nullable MethodNode callMth) throws CodegenException { int k = startArgNum; @@ -744,6 +780,9 @@ public class InsnGen { return false; } InsnNode inl = mia.getInsn(); + if (Consts.DEBUG) { + code.add("/* inline method: ").add(callMthNode.toString()).add("*/").startLine(); + } if (callMthNode.getMethodInfo().getArgumentsTypes().isEmpty()) { makeInsn(inl, code, Flags.BODY_ONLY); } else { diff --git a/jadx-core/src/main/java/jadx/core/deobf/Deobfuscator.java b/jadx-core/src/main/java/jadx/core/deobf/Deobfuscator.java index 859341f95..e555b05de 100644 --- a/jadx-core/src/main/java/jadx/core/deobf/Deobfuscator.java +++ b/jadx-core/src/main/java/jadx/core/deobf/Deobfuscator.java @@ -483,21 +483,7 @@ public class Deobfuscator { if (name.length() > maxLength) { return "x" + Integer.toHexString(name.hashCode()); } - if (!NameMapper.isAllCharsPrintable(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(); + return NameMapper.removeInvalidCharsMiddle(name); } private void dumpClassAlias(ClassNode cls) { diff --git a/jadx-core/src/main/java/jadx/core/deobf/NameMapper.java b/jadx-core/src/main/java/jadx/core/deobf/NameMapper.java index 4e8e75686..9262f051f 100644 --- a/jadx-core/src/main/java/jadx/core/deobf/NameMapper.java +++ b/jadx-core/src/main/java/jadx/core/deobf/NameMapper.java @@ -91,6 +91,14 @@ public class NameMapper { && 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) { return 32 <= c && c <= 126; } @@ -105,6 +113,49 @@ public class NameMapper { return true; } + /** + * Return modified string with removed: + *

+ * Note: this 'middle' method must be used with prefixed string: + *

+ */ + 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} + *

+ * 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() { } } diff --git a/jadx-core/src/main/java/jadx/core/dex/info/MethodInfo.java b/jadx-core/src/main/java/jadx/core/dex/info/MethodInfo.java index 8aab5fca1..a53ec0f56 100644 --- a/jadx-core/src/main/java/jadx/core/dex/info/MethodInfo.java +++ b/jadx-core/src/main/java/jadx/core/dex/info/MethodInfo.java @@ -43,8 +43,12 @@ public final class MethodInfo { } public String makeSignature(boolean includeRetType) { + return makeSignature(false, includeRetType); + } + + public String makeSignature(boolean useAlias, boolean includeRetType) { StringBuilder signature = new StringBuilder(); - signature.append(name); + signature.append(useAlias ? alias : name); signature.append('('); for (ArgType arg : args) { signature.append(TypeGen.signature(arg)); diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java index dc5e0a70a..73b028650 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java @@ -38,12 +38,12 @@ import jadx.core.utils.exceptions.JadxRuntimeException; 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 final DexNode dex; private final ClassInfo clsInfo; - private final AccessInfo accessFlags; + private AccessInfo accessFlags; private ArgType superClass; private List interfaces; private Map> genericMap; @@ -409,10 +409,16 @@ public class ClassNode extends LineAttrNode implements ILoadable, IDexNode { return null; } + @Override public AccessInfo getAccessFlags() { return accessFlags; } + @Override + public void setAccessFlags(AccessInfo accessFlags) { + this.accessFlags = accessFlags; + } + @Override public DexNode dex() { return dex; diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/FieldNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/FieldNode.java index a11f55f56..fa1d142b2 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/FieldNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/FieldNode.java @@ -8,11 +8,11 @@ import jadx.core.dex.info.AccessInfo.AFType; import jadx.core.dex.info.FieldInfo; 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 FieldInfo fieldInfo; - private final AccessInfo accFlags; + private AccessInfo accFlags; private ArgType type; @@ -32,10 +32,16 @@ public class FieldNode extends LineAttrNode { return fieldInfo; } + @Override public AccessInfo getAccessFlags() { return accFlags; } + @Override + public void setAccessFlags(AccessInfo accFlags) { + this.accFlags = accFlags; + } + public String getName() { return fieldInfo.getName(); } @@ -56,6 +62,21 @@ public class FieldNode extends LineAttrNode { return parent; } + @Override + public String typeName() { + return "field"; + } + + @Override + public DexNode dex() { + return parent.dex(); + } + + @Override + public RootNode root() { + return parent.root(); + } + @Override public int hashCode() { return fieldInfo.hashCode(); diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/ICodeNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/ICodeNode.java new file mode 100644 index 000000000..edd577cf5 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/ICodeNode.java @@ -0,0 +1,10 @@ +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); +} diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java index e96b8f208..5fed36ceb 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java @@ -46,7 +46,7 @@ import jadx.core.utils.exceptions.JadxRuntimeException; 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 final MethodInfo mthInfo; @@ -607,12 +607,14 @@ public class MethodNode extends LineAttrNode implements ILoadable, IDexNode { return sVars; } + @Override public AccessInfo getAccessFlags() { return accFlags; } - public void setAccFlags(AccessInfo accFlags) { - this.accFlags = accFlags; + @Override + public void setAccessFlags(AccessInfo newAccessFlags) { + this.accFlags = newAccessFlags; } public Region getRegion() { diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/ClassModifier.java b/jadx-core/src/main/java/jadx/core/dex/visitors/ClassModifier.java index c7d148c7e..04a493bcd 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/ClassModifier.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/ClassModifier.java @@ -5,6 +5,7 @@ import java.util.Objects; import com.android.dx.rop.code.AccessFlags; +import jadx.core.Consts; import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.nodes.FieldReplaceAttr; @@ -15,6 +16,7 @@ import jadx.core.dex.info.MethodInfo; import jadx.core.dex.instructions.IndexInsnNode; import jadx.core.dex.instructions.InsnType; 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.InsnArg; import jadx.core.dex.instructions.args.InsnWrapArg; @@ -140,7 +142,11 @@ public class ClassModifier extends AbstractVisitor { return; } if (removeBridgeMethod(cls, mth)) { - mth.add(AFlag.DONT_GENERATE); + if (Consts.DEBUG) { + mth.addAttr(AType.COMMENTS, "Removed as synthetic bridge method"); + } else { + mth.add(AFlag.DONT_GENERATE); + } return; } // remove synthetic constructor for inner classes @@ -219,40 +225,45 @@ public class ClassModifier extends AbstractVisitor { private static boolean checkSyntheticWrapper(MethodNode mth, InsnNode insn) { InsnType insnType = insn.getType(); - if (insnType == InsnType.INVOKE) { - MethodInfo callMth = ((InvokeNode) insn).getCallMth(); - MethodNode wrappedMth = mth.root().deepResolveMethod(callMth); - if (wrappedMth != null) { - AccessInfo wrappedAccFlags = wrappedMth.getAccessFlags(); - if (wrappedAccFlags.isStatic()) { - return false; - } - if (callMth.getArgsCount() != mth.getMethodInfo().getArgsCount()) { - return false; - } - // rename method only from current class - if (!mth.getParentClass().equals(wrappedMth.getParentClass())) { - return false; - } - // all args must be registers passed from method args (allow only casts insns) - for (InsnArg arg : insn.getArguments()) { - if (!registersAndCastsOnly(arg)) { - return false; - } - } - String alias = mth.getAlias(); - if (Objects.equals(wrappedMth.getAlias(), alias)) { - return true; - } - if (!wrappedAccFlags.isPublic()) { - // must be public - FixAccessModifiers.changeVisibility(wrappedMth, AccessFlags.ACC_PUBLIC); - } - wrappedMth.getMethodInfo().setAlias(alias); - return true; + if (insnType != InsnType.INVOKE) { + return false; + } + InvokeNode invokeInsn = (InvokeNode) insn; + if (invokeInsn.getInvokeType() == InvokeType.SUPER) { + return false; + } + MethodInfo callMth = invokeInsn.getCallMth(); + MethodNode wrappedMth = mth.root().deepResolveMethod(callMth); + if (wrappedMth == null) { + return false; + } + AccessInfo wrappedAccFlags = wrappedMth.getAccessFlags(); + if (wrappedAccFlags.isStatic()) { + return false; + } + if (callMth.getArgsCount() != mth.getMethodInfo().getArgsCount()) { + return false; + } + // rename method only from current class + if (!mth.getParentClass().equals(wrappedMth.getParentClass())) { + return false; + } + // all args must be registers passed from method args (allow only casts insns) + for (InsnArg arg : insn.getArguments()) { + if (!registersAndCastsOnly(arg)) { + return false; } } - return false; + // remove confirmed, change visibility and name if needed + if (!wrappedAccFlags.isPublic()) { + // must be public + FixAccessModifiers.changeVisibility(wrappedMth, AccessFlags.ACC_PUBLIC); + } + String alias = mth.getAlias(); + if (!Objects.equals(wrappedMth.getAlias(), alias)) { + wrappedMth.getMethodInfo().setAlias(alias); + } + return true; } private static boolean registersAndCastsOnly(InsnArg arg) { @@ -274,8 +285,7 @@ public class ClassModifier extends AbstractVisitor { if (otherMth != mth) { MethodInfo omi = otherMth.getMethodInfo(); if (omi.getName().equals(mi.getName()) - && omi.getArgumentsTypes().size() == mi.getArgumentsTypes().size()) { - // TODO: check objects types + && Objects.equals(omi.getArgumentsTypes(), mi.getArgumentsTypes())) { return false; } } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/FixAccessModifiers.java b/jadx-core/src/main/java/jadx/core/dex/visitors/FixAccessModifiers.java index 62a1808bb..cd270f921 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/FixAccessModifiers.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/FixAccessModifiers.java @@ -4,6 +4,7 @@ import com.android.dx.rop.code.AccessFlags; import jadx.core.dex.attributes.AType; import jadx.core.dex.info.AccessInfo; +import jadx.core.dex.nodes.ICodeNode; import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.RootNode; @@ -32,12 +33,12 @@ public class FixAccessModifiers extends AbstractVisitor { } } - public static void changeVisibility(MethodNode mth, int newVisFlag) { - AccessInfo accessFlags = mth.getAccessFlags(); + public static void changeVisibility(ICodeNode node, int newVisFlag) { + AccessInfo accessFlags = node.getAccessFlags(); AccessInfo newAccFlags = accessFlags.changeVisibility(newVisFlag); if (newAccFlags != accessFlags) { - mth.setAccFlags(newAccFlags); - mth.addAttr(AType.COMMENTS, "access modifiers changed from: " + accessFlags.rawString()); + node.setAccessFlags(newAccFlags); + node.addAttr(AType.COMMENTS, "access modifiers changed from: " + accessFlags.rawString()); } } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/MethodInlineVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/MethodInlineVisitor.java index ac60d783f..3798cd043 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/MethodInlineVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/MethodInlineVisitor.java @@ -2,28 +2,44 @@ package jadx.core.dex.visitors; 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.AType; import jadx.core.dex.attributes.nodes.MethodInlineAttr; 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.InvokeNode; import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.InsnArg; +import jadx.core.dex.instructions.args.InsnWrapArg; import jadx.core.dex.instructions.args.RegisterArg; import jadx.core.dex.nodes.BlockNode; +import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.MethodNode; import jadx.core.utils.exceptions.JadxException; -/** - * Inline synthetic methods. - */ +@JadxVisitor( + name = "InlineMethods", + desc = "Inline synthetic static methods", + runAfter = { + FixAccessModifiers.class, + ClassModifier.class + } +) public class MethodInlineVisitor extends AbstractVisitor { @Override public void visit(MethodNode mth) throws JadxException { + if (mth.isNoCode() || mth.contains(AFlag.DONT_GENERATE)) { + return; + } AccessInfo accessFlags = mth.getAccessFlags(); - if (accessFlags.isSynthetic() - && accessFlags.isStatic() + if (accessFlags.isSynthetic() && accessFlags.isStatic() && mth.getBasicBlocks().size() == 2) { BlockNode returnBlock = mth.getBasicBlocks().get(1); if (returnBlock.contains(AFlag.RETURN) || returnBlock.getInstructions().isEmpty()) { @@ -72,7 +88,47 @@ public class MethodInlineVisitor extends AbstractVisitor { } private static void addInlineAttr(MethodNode mth, InsnNode insn) { - mth.addAttr(new MethodInlineAttr(insn)); - mth.add(AFlag.DONT_GENERATE); + if (fixVisibilityOfInlineCode(mth, insn)) { + if (Consts.DEBUG) { + mth.addAttr(AType.COMMENTS, "Removed for inline"); + } else { + mth.addAttr(new MethodInlineAttr(insn)); + 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; } } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/RenameVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/RenameVisitor.java index 802e27942..4f91ed490 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/RenameVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/RenameVisitor.java @@ -72,14 +72,9 @@ public class RenameVisitor extends AbstractVisitor { ClassInfo classInfo = cls.getClassInfo(); ClassInfo alias = classInfo.getAlias(); String clsName = alias.getShortName(); - String newShortName = null; - char firstChar = clsName.charAt(0); - if (Character.isDigit(firstChar)) { - newShortName = Consts.ANONYMOUS_CLASS_PREFIX + clsName; - } else if (firstChar == '$') { - newShortName = "C" + clsName; - } - if (newShortName != null) { + + String newShortName = fixClsShortName(clsName); + if (!newShortName.equals(clsName)) { classInfo.rename(cls.root(), alias.makeFullClsName(newShortName, true)); } if (alias.getPackage().isEmpty()) { @@ -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) { Set names = new HashSet<>(); for (FieldNode field : cls.getFields()) { @@ -101,17 +107,22 @@ public class RenameVisitor extends AbstractVisitor { } private void checkMethods(ClassNode cls) { + for (MethodNode mth : cls.getMethods()) { + if (!NameMapper.isValidIdentifier(mth.getAlias())) { + deobfuscator.forceRenameMethod(mth); + } + } Set names = new HashSet<>(); for (MethodNode mth : cls.getMethods()) { AccessInfo accessFlags = mth.getAccessFlags(); if (accessFlags.isConstructor() || accessFlags.isBridge() || accessFlags.isSynthetic() - || mth.contains(AFlag.DONT_GENERATE)) { + || mth.contains(AFlag.DONT_GENERATE) /* this flag not set yet */) { continue; } - String signature = mth.getMethodInfo().makeSignature(false); - if (!names.add(signature) || !NameMapper.isValidIdentifier(mth.getAlias())) { + String signature = mth.getMethodInfo().makeSignature(true, false); + if (!names.add(signature)) { deobfuscator.forceRenameMethod(mth); } } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockSplitter.java b/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockSplitter.java index ac4d80d89..e2f1fd08e 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockSplitter.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockSplitter.java @@ -1,7 +1,10 @@ package jadx.core.dex.visitors.blocksmaker; +import java.util.ArrayDeque; +import java.util.Deque; import java.util.EnumSet; import java.util.HashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -47,6 +50,7 @@ public class BlockSplitter extends AbstractVisitor { removeJumpAttr(mth); removeInsns(mth); removeEmptyDetachedBlocks(mth); + removeUnreachableBlocks(mth); initBlocksInTargetNodes(mth); removeJumpAttributes(mth.getInstructions()); @@ -335,4 +339,37 @@ public class BlockSplitter extends AbstractVisitor { } } } + + private void removeUnreachableBlocks(MethodNode mth) { + Set 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 toRemove) { + Deque 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); + } + } } diff --git a/jadx-core/src/main/java/jadx/core/utils/android/AndroidResourcesUtils.java b/jadx-core/src/main/java/jadx/core/utils/android/AndroidResourcesUtils.java index 3cfbcd7e2..53d49dfca 100644 --- a/jadx-core/src/main/java/jadx/core/utils/android/AndroidResourcesUtils.java +++ b/jadx-core/src/main/java/jadx/core/utils/android/AndroidResourcesUtils.java @@ -54,6 +54,10 @@ public class AndroidResourcesUtils { } LOG.info("App 'R' class not found, put all resources ids to : '{}'", fullName); 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); return resCls; } @@ -81,18 +85,18 @@ public class AndroidResourcesUtils { return rCls; } - private static void addResourceFields(ClassNode cls, ResourceStorage resStorage, boolean rClsExists) { + private static void addResourceFields(ClassNode resCls, ResourceStorage resStorage, boolean rClsExists) { Map innerClsMap = new TreeMap<>(); if (rClsExists) { - for (ClassNode innerClass : cls.getInnerClasses()) { + for (ClassNode innerClass : resCls.getInnerClasses()) { innerClsMap.put(innerClass.getShortName(), innerClass); } } for (ResourceEntry resource : resStorage.getResources()) { 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); - cls.addInnerClass(newTypeCls); + resCls.addInnerClass(newTypeCls); if (rClsExists) { newTypeCls.addAttr(AType.COMMENTS, "added by JADX"); } diff --git a/jadx-core/src/main/java/jadx/core/utils/files/InputFile.java b/jadx-core/src/main/java/jadx/core/utils/files/InputFile.java index ce4398563..0d2cd18e8 100644 --- a/jadx-core/src/main/java/jadx/core/utils/files/InputFile.java +++ b/jadx-core/src/main/java/jadx/core/utils/files/InputFile.java @@ -77,8 +77,7 @@ public class InputFile { if (skipSources) { return; } - - throw new DecodeException("Unsupported input file format: " + file); + LOG.warn("No dex files found in {}", file); } private void addDexFile(Dex dexBuf) { diff --git a/jadx-core/src/test/java/jadx/core/deobf/NameMapperTest.java b/jadx-core/src/test/java/jadx/core/deobf/NameMapperTest.java new file mode 100644 index 000000000..b5ff7b203 --- /dev/null +++ b/jadx-core/src/test/java/jadx/core/deobf/NameMapperTest.java @@ -0,0 +1,38 @@ +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")); + } +} diff --git a/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java b/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java index 536619641..6ff935f79 100644 --- a/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java +++ b/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java @@ -215,12 +215,14 @@ public abstract class IntegrationTest extends TestUtils { rethrow("Original check failed", ie); } // run 'check' method from decompiled class - try { - invoke("check"); - } catch (InvocationTargetException ie) { - rethrow("Decompiled check failed", ie); + if (compile) { + try { + invoke("check"); + } catch (InvocationTargetException ie) { + rethrow("Decompiled check failed", ie); + } + System.out.println("Auto check: PASSED"); } - System.out.println("Auto check: PASSED"); } catch (Exception e) { e.printStackTrace(); fail("Auto check exception: " + e.getMessage()); diff --git a/jadx-core/src/test/java/jadx/tests/integration/inline/TestSyntheticInline2.java b/jadx-core/src/test/java/jadx/tests/integration/inline/TestSyntheticInline2.java new file mode 100644 index 000000000..58e6a8c6c --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/inline/TestSyntheticInline2.java @@ -0,0 +1,62 @@ +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();")); + } +} diff --git a/jadx-core/src/test/java/jadx/tests/integration/names/TestDuplicatedNames.java b/jadx-core/src/test/java/jadx/tests/integration/names/TestDuplicatedNames.java index 7adc7f184..75684d896 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/names/TestDuplicatedNames.java +++ b/jadx-core/src/test/java/jadx/tests/integration/names/TestDuplicatedNames.java @@ -46,6 +46,6 @@ public class TestDuplicatedNames extends SmaliTest { assertThat(code, containsOne("this.f0fieldName")); assertThat(code, containsOne("public Object run() {")); - assertThat(code, containsOne("public String m0run() {")); + assertThat(code, containsOne("public String m1run() {")); } } diff --git a/jadx-gui/src/main/java/jadx/gui/JadxWrapper.java b/jadx-gui/src/main/java/jadx/gui/JadxWrapper.java index 11bf8c68c..52ec3908b 100644 --- a/jadx-gui/src/main/java/jadx/gui/JadxWrapper.java +++ b/jadx-gui/src/main/java/jadx/gui/JadxWrapper.java @@ -35,7 +35,7 @@ public class JadxWrapper { this.decompiler.getArgs().setInputFiles(Collections.singletonList(file)); this.decompiler.load(); } catch (Exception e) { - LOG.error("Error load file: {}", file, e); + LOG.error("Jadx init error", e); } } diff --git a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java index 6b6fd1267..f0c190229 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java @@ -10,6 +10,7 @@ import java.util.Map; import java.util.Set; import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; +import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -18,20 +19,19 @@ import jadx.cli.JadxCLIArgs; import jadx.gui.ui.codearea.EditorTheme; import jadx.gui.utils.LangLocale; import jadx.gui.utils.NLS; - -import static jadx.gui.utils.Utils.FONT_HACK; +import jadx.gui.utils.Utils; public class JadxSettings extends JadxCLIArgs { private static final Logger LOG = LoggerFactory.getLogger(JadxSettings.class); private static final String USER_HOME = System.getProperty("user.home"); 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 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 lastSaveFilePath = USER_HOME; @@ -262,8 +262,19 @@ public class JadxSettings extends JadxCLIArgs { return Font.decode(fontStr); } - public void setFont(Font font) { - this.fontStr = font.getFontName() + addStyleName(font.getStyle()) + "-" + font.getSize(); + public void setFont(@Nullable Font font) { + 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() { @@ -274,19 +285,6 @@ public class JadxSettings extends JadxCLIArgs { 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) { LOG.debug("upgrade settings from version: {} to {}", fromVersion, CURRENT_SETTINGS_VERSION); if (fromVersion == 0) { @@ -319,6 +317,18 @@ public class JadxSettings extends JadxCLIArgs { } if (fromVersion == 5) { 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; sync(); diff --git a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java index 5bcbf5351..81579a996 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java @@ -16,6 +16,7 @@ import jadx.gui.ui.MainWindow; import jadx.gui.ui.codearea.EditorTheme; import jadx.gui.utils.LangLocale; import jadx.gui.utils.NLS; +import jadx.gui.utils.Utils; public class JadxSettingsWindow extends JDialog { private static final long serialVersionUID = -1804570470377354148L; @@ -73,6 +74,7 @@ public class JadxSettingsWindow extends JDialog { JButton cancelButton = new JButton(NLS.str("preferences.cancel")); cancelButton.addActionListener(event -> { JadxSettingsAdapter.fill(settings, startSettings); + mainWindow.loadSettings(); dispose(); }); @@ -86,6 +88,8 @@ public class JadxSettingsWindow extends JDialog { if (res == JOptionPane.YES_OPTION) { String defaults = JadxSettingsAdapter.makeString(JadxSettings.makeDefault()); JadxSettingsAdapter.fill(settings, defaults); + mainWindow.loadSettings(); + needReload(); getContentPane().removeAll(); initUI(); pack(); @@ -103,7 +107,9 @@ public class JadxSettingsWindow extends JDialog { buttonPane.add(cancelButton); 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); getRootPane().setDefaultButton(saveBtn); } @@ -164,21 +170,6 @@ public class JadxSettingsWindow extends JDialog { private SettingsGroup makeEditorGroup() { 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(); JComboBox themesCbx = new JComboBox<>(editorThemes); @@ -196,11 +187,33 @@ public class JadxSettingsWindow extends JDialog { }); 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); + + 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; } + 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() { JCheckBox fallback = new JCheckBox(); fallback.setSelected(settings.isFallbackMode()); @@ -342,11 +355,11 @@ public class JadxSettingsWindow extends JDialog { c.weighty = 1.0; } - public void addRow(String label, JComponent comp) { - addRow(label, null, comp); + public JLabel addRow(String label, JComponent 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++; JLabel jLabel = new JLabel(label); jLabel.setLabelFor(comp); @@ -371,6 +384,7 @@ public class JadxSettingsWindow extends JDialog { add(comp, c); comp.addPropertyChangeListener("enabled", evt -> jLabel.setEnabled((boolean) evt.getNewValue())); + return jLabel; } public void end() { diff --git a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java index 14be9ca2b..4885eb93e 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java @@ -652,10 +652,6 @@ public class MainWindow extends JFrame { setSize((int) (w * WINDOW_RATIO), (int) (h * WINDOW_RATIO)); } - public void updateFont(Font font) { - setFont(font); - } - public static void registerBundledFonts() { GraphicsEnvironment grEnv = GraphicsEnvironment.getLocalGraphicsEnvironment(); if (Utils.FONT_HACK != null) { diff --git a/jadx-gui/src/main/java/jadx/gui/utils/Utils.java b/jadx-gui/src/main/java/jadx/gui/utils/Utils.java index 120aafb0f..f6f724273 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/Utils.java +++ b/jadx-gui/src/main/java/jadx/gui/utils/Utils.java @@ -8,6 +8,7 @@ import java.awt.datatransfer.Transferable; import java.io.InputStream; import java.net.URL; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -175,4 +176,19 @@ public class Utils { 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(); + } } diff --git a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties index fe5504adf..c8b6a055a 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties @@ -99,7 +99,7 @@ preferences.raw_cfg=Generate RAW CFG graphs preferences.font=Editor font preferences.theme=Editor theme preferences.start_jobs=Auto start background decompilation -preferences.select_font=Select +preferences.select_font=Change preferences.deobfuscation_on=Enable deobfuscation preferences.deobfuscation_force=Force rewrite deobfuscation map file preferences.deobfuscation_min_len=Minimum name length