Commit e7e7b664 authored by Skylot's avatar Skylot

feat: add option to disable anonymous class inline (#633)

parent db7f2cf5
...@@ -54,31 +54,38 @@ Run **jadx** on itself: ...@@ -54,31 +54,38 @@ Run **jadx** on itself:
### Usage ### Usage
``` ```
jadx[-gui] [options] <input file> (.dex, .apk, .jar or .class) jadx[-gui] [options] <input file> (.apk, .dex, .jar, .class, .smali, .zip, .aar, .arsc)
options: options:
-d, --output-dir - output directory -d, --output-dir - output directory
-ds, --output-dir-src - output directory for sources -ds, --output-dir-src - output directory for sources
-dr, --output-dir-res - output directory for resources -dr, --output-dir-res - output directory for resources
-j, --threads-count - processing threads count -j, --threads-count - processing threads count
-r, --no-res - do not decode resources -r, --no-res - do not decode resources
-s, --no-src - do not decompile source code -s, --no-src - do not decompile source code
-e, --export-gradle - save as android gradle project -e, --export-gradle - save as android gradle project
--show-bad-code - show inconsistent code (incorrectly decompiled) --show-bad-code - show inconsistent code (incorrectly decompiled)
--no-imports - disable use of imports, always write entire package name --no-imports - disable use of imports, always write entire package name
--no-replace-consts - don't replace constant value with matching constant field --no-debug-info - disable debug info
--escape-unicode - escape non latin characters in strings (with \u) --no-inline-anonymous - disable anonymous classes inline
--deobf - activate deobfuscation --no-replace-consts - don't replace constant value with matching constant field
--deobf-min - min length of name --escape-unicode - escape non latin characters in strings (with \u)
--deobf-max - max length of name --respect-bytecode-access-modifiers - don't change original access modifiers
--deobf-rewrite-cfg - force to save deobfuscation map --deobf - activate deobfuscation
--deobf-use-sourcename - use source file name as class name alias --deobf-min - min length of name, renamed if shorter (default: 3)
--cfg - save methods control flow graph to dot file --deobf-max - max length of name, renamed if longer (default: 64)
--raw-cfg - save methods control flow graph (use raw instructions) --deobf-rewrite-cfg - force to save deobfuscation map
-f, --fallback - make simple dump (using goto instead of 'if', 'for', etc) --deobf-use-sourcename - use source file name as class name alias
-v, --verbose - verbose output --rename-flags - what to rename, comma-separated, 'case' for system case sensitivity, 'valid' for java identifiers, 'printable' characters, 'none' or 'all'
-h, --help - print this help --cfg - save methods control flow graph to dot file
--raw-cfg - save methods control flow graph (use raw instructions)
-f, --fallback - make simple dump (using goto instead of 'if', 'for', etc)
-v, --verbose - verbose output
--version - print jadx version
-h, --help - print this help
Example: Example:
jadx -d out classes.dex jadx -d out classes.dex
jadx --rename-flags "none" classes.dex
jadx --rename-flags "valid,printable" classes.dex
``` ```
These options also worked on jadx-gui running from command line and override options from preferences dialog These options also worked on jadx-gui running from command line and override options from preferences dialog
......
...@@ -57,6 +57,9 @@ public class JadxCLIArgs { ...@@ -57,6 +57,9 @@ public class JadxCLIArgs {
@Parameter(names = {"--no-debug-info"}, description = "disable debug info") @Parameter(names = {"--no-debug-info"}, description = "disable debug info")
protected boolean debugInfo = true; protected boolean debugInfo = true;
@Parameter(names = { "--no-inline-anonymous" }, description = "disable anonymous classes inline")
protected boolean inlineAnonymousClasses = true;
@Parameter(names = "--no-replace-consts", description = "don't replace constant value with matching constant field") @Parameter(names = "--no-replace-consts", description = "don't replace constant value with matching constant field")
protected boolean replaceConsts = true; protected boolean replaceConsts = true;
...@@ -175,6 +178,7 @@ public class JadxCLIArgs { ...@@ -175,6 +178,7 @@ public class JadxCLIArgs {
args.setExportAsGradleProject(exportAsGradleProject); args.setExportAsGradleProject(exportAsGradleProject);
args.setUseImports(useImports); args.setUseImports(useImports);
args.setDebugInfo(debugInfo); args.setDebugInfo(debugInfo);
args.setInlineAnonymousClasses(inlineAnonymousClasses);
args.setRenameCaseSensitive(isRenameCaseSensitive()); args.setRenameCaseSensitive(isRenameCaseSensitive());
args.setRenameValid(isRenameValid()); args.setRenameValid(isRenameValid());
args.setRenamePrintable(isRenamePrintable()); args.setRenamePrintable(isRenamePrintable());
...@@ -225,6 +229,10 @@ public class JadxCLIArgs { ...@@ -225,6 +229,10 @@ public class JadxCLIArgs {
return debugInfo; return debugInfo;
} }
public boolean isInlineAnonymousClasses() {
return inlineAnonymousClasses;
}
public boolean isDeobfuscationOn() { public boolean isDeobfuscationOn() {
return deobfuscationOn; return deobfuscationOn;
} }
......
...@@ -31,6 +31,7 @@ public class JadxArgs { ...@@ -31,6 +31,7 @@ public class JadxArgs {
private boolean useImports = true; private boolean useImports = true;
private boolean debugInfo = true; private boolean debugInfo = true;
private boolean inlineAnonymousClasses = true;
private boolean skipResources = false; private boolean skipResources = false;
private boolean skipSources = false; private boolean skipSources = false;
...@@ -155,6 +156,14 @@ public class JadxArgs { ...@@ -155,6 +156,14 @@ public class JadxArgs {
this.debugInfo = debugInfo; this.debugInfo = debugInfo;
} }
public boolean isInlineAnonymousClasses() {
return inlineAnonymousClasses;
}
public void setInlineAnonymousClasses(boolean inlineAnonymousClasses) {
this.inlineAnonymousClasses = inlineAnonymousClasses;
}
public boolean isSkipResources() { public boolean isSkipResources() {
return skipResources; return skipResources;
} }
......
...@@ -26,6 +26,7 @@ import jadx.core.dex.visitors.MarkFinallyVisitor; ...@@ -26,6 +26,7 @@ import jadx.core.dex.visitors.MarkFinallyVisitor;
import jadx.core.dex.visitors.MethodInlineVisitor; import jadx.core.dex.visitors.MethodInlineVisitor;
import jadx.core.dex.visitors.ModVisitor; import jadx.core.dex.visitors.ModVisitor;
import jadx.core.dex.visitors.PrepareForCodeGen; import jadx.core.dex.visitors.PrepareForCodeGen;
import jadx.core.dex.visitors.ProcessAnonymous;
import jadx.core.dex.visitors.ReSugarCode; import jadx.core.dex.visitors.ReSugarCode;
import jadx.core.dex.visitors.RenameVisitor; import jadx.core.dex.visitors.RenameVisitor;
import jadx.core.dex.visitors.SimplifyVisitor; import jadx.core.dex.visitors.SimplifyVisitor;
...@@ -104,6 +105,7 @@ public class Jadx { ...@@ -104,6 +105,7 @@ public class Jadx {
passes.add(new ExtractFieldInit()); passes.add(new ExtractFieldInit());
passes.add(new FixAccessModifiers()); passes.add(new FixAccessModifiers());
passes.add(new ProcessAnonymous());
passes.add(new ClassModifier()); passes.add(new ClassModifier());
passes.add(new MethodInlineVisitor()); passes.add(new MethodInlineVisitor());
passes.add(new EnumVisitor()); passes.add(new EnumVisitor());
......
...@@ -228,8 +228,7 @@ public class ClassGen { ...@@ -228,8 +228,7 @@ public class ClassGen {
private void addInnerClasses(CodeWriter code, ClassNode cls) throws CodegenException { private void addInnerClasses(CodeWriter code, ClassNode cls) throws CodegenException {
for (ClassNode innerCls : cls.getInnerClasses()) { for (ClassNode innerCls : cls.getInnerClasses()) {
if (innerCls.contains(AFlag.DONT_GENERATE) if (innerCls.contains(AFlag.DONT_GENERATE)) {
|| innerCls.contains(AFlag.ANONYMOUS_CLASS)) {
continue; continue;
} }
ClassGen inClGen = new ClassGen(innerCls, getParentGen()); ClassGen inClGen = new ClassGen(innerCls, getParentGen());
......
...@@ -556,7 +556,7 @@ public class InsnGen { ...@@ -556,7 +556,7 @@ public class InsnGen {
private void makeConstructor(ConstructorInsn insn, CodeWriter code) private void makeConstructor(ConstructorInsn insn, CodeWriter code)
throws CodegenException { throws CodegenException {
ClassNode cls = mth.dex().resolveClass(insn.getClassType()); ClassNode cls = mth.dex().resolveClass(insn.getClassType());
if (cls != null && cls.contains(AFlag.ANONYMOUS_CLASS) && !fallback) { if (cls != null && cls.isAnonymous() && !fallback) {
inlineAnonymousConstructor(code, cls, insn); inlineAnonymousConstructor(code, cls, insn);
return; return;
} }
......
...@@ -122,7 +122,6 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode { ...@@ -122,7 +122,6 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
accFlagsValue = cls.getAccessFlags(); accFlagsValue = cls.getAccessFlags();
} }
this.accessFlags = new AccessInfo(accFlagsValue, AFType.CLASS); this.accessFlags = new AccessInfo(accFlagsValue, AFType.CLASS);
markAnonymousClass();
buildCache(); buildCache();
} catch (Exception e) { } catch (Exception e) {
throw new JadxRuntimeException("Error decode class: " + clsInfo, e); throw new JadxRuntimeException("Error decode class: " + clsInfo, e);
...@@ -401,41 +400,8 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode { ...@@ -401,41 +400,8 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
&& getSuperClass().getObject().equals(ArgType.ENUM.getObject()); && getSuperClass().getObject().equals(ArgType.ENUM.getObject());
} }
public boolean markAnonymousClass() {
if (isAnonymous() || isLambdaCls()) {
add(AFlag.ANONYMOUS_CLASS);
add(AFlag.DONT_GENERATE);
for (MethodNode mth : getMethods()) {
if (mth.isConstructor()) {
mth.add(AFlag.ANONYMOUS_CONSTRUCTOR);
}
}
return true;
}
return false;
}
public boolean isAnonymous() { public boolean isAnonymous() {
return clsInfo.isInner() return contains(AFlag.ANONYMOUS_CLASS);
&& Character.isDigit(clsInfo.getShortName().charAt(0))
&& methods.stream().filter(MethodNode::isConstructor).count() == 1;
}
public boolean isLambdaCls() {
return accessFlags.isSynthetic() && accessFlags.isFinal()
&& clsInfo.getType().getObject().contains(".-$$Lambda$")
&& countStaticFields() == 0;
}
private int countStaticFields() {
int c = 0;
for (FieldNode field : fields) {
if (field.getAccessFlags().isStatic()) {
c++;
}
}
return c;
} }
@Nullable @Nullable
......
...@@ -37,7 +37,8 @@ import jadx.core.utils.exceptions.JadxException; ...@@ -37,7 +37,8 @@ import jadx.core.utils.exceptions.JadxException;
desc = "Remove synthetic classes, methods and fields", desc = "Remove synthetic classes, methods and fields",
runAfter = { runAfter = {
ModVisitor.class, ModVisitor.class,
FixAccessModifiers.class FixAccessModifiers.class,
ProcessAnonymous.class
} }
) )
public class ClassModifier extends AbstractVisitor { public class ClassModifier extends AbstractVisitor {
...@@ -51,7 +52,6 @@ public class ClassModifier extends AbstractVisitor { ...@@ -51,7 +52,6 @@ public class ClassModifier extends AbstractVisitor {
cls.add(AFlag.DONT_GENERATE); cls.add(AFlag.DONT_GENERATE);
return false; return false;
} }
cls.markAnonymousClass();
removeSyntheticFields(cls); removeSyntheticFields(cls);
cls.getMethods().forEach(ClassModifier::removeSyntheticMethods); cls.getMethods().forEach(ClassModifier::removeSyntheticMethods);
cls.getMethods().forEach(ClassModifier::removeEmptyMethods); cls.getMethods().forEach(ClassModifier::removeEmptyMethods);
...@@ -73,7 +73,7 @@ public class ClassModifier extends AbstractVisitor { ...@@ -73,7 +73,7 @@ public class ClassModifier extends AbstractVisitor {
if (cls.getAccessFlags().isStatic()) { if (cls.getAccessFlags().isStatic()) {
return; return;
} }
boolean inline = cls.contains(AFlag.ANONYMOUS_CLASS); boolean inline = cls.isAnonymous();
if (inline || cls.getClassInfo().isInner()) { if (inline || cls.getClassInfo().isInner()) {
for (FieldNode field : cls.getFields()) { for (FieldNode field : cls.getFields()) {
if (field.getAccessFlags().isSynthetic() && field.getType().isObject()) { if (field.getAccessFlags().isSynthetic() && field.getType().isObject()) {
......
...@@ -229,11 +229,8 @@ public class ModVisitor extends AbstractVisitor { ...@@ -229,11 +229,8 @@ public class ModVisitor extends AbstractVisitor {
} }
ClassNode classNode = callMthNode.getParentClass(); ClassNode classNode = callMthNode.getParentClass();
if (!classNode.contains(AFlag.ANONYMOUS_CLASS)) { if (!classNode.isAnonymous()) {
// check if class can be anonymous but not yet marked due to dependency issues return;
if (!classNode.markAnonymousClass()) {
return;
}
} }
if (!mth.getParentClass().getInnerClasses().contains(classNode)) { if (!mth.getParentClass().getInnerClasses().contains(classNode)) {
return; return;
......
package jadx.core.dex.visitors;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.visitors.regions.RegionMakerVisitor;
@JadxVisitor(
name = "ProcessAnonymous",
desc = "Mark anonymous and lambda classes (for future inline)",
runAfter = RegionMakerVisitor.class
)
public class ProcessAnonymous extends AbstractVisitor {
@Override
public void init(RootNode root) {
if (!root.getArgs().isInlineAnonymousClasses()) {
return;
}
for (ClassNode cls : root.getClasses(true)) {
markAnonymousClass(cls);
}
}
private static boolean markAnonymousClass(ClassNode cls) {
if (isAnonymous(cls) || isLambdaCls(cls)) {
cls.add(AFlag.ANONYMOUS_CLASS);
cls.add(AFlag.DONT_GENERATE);
for (MethodNode mth : cls.getMethods()) {
if (mth.isConstructor()) {
mth.add(AFlag.ANONYMOUS_CONSTRUCTOR);
}
}
return true;
}
return false;
}
private static boolean isAnonymous(ClassNode cls) {
return cls.getClassInfo().isInner()
&& Character.isDigit(cls.getClassInfo().getShortName().charAt(0))
&& cls.getMethods().stream().filter(MethodNode::isConstructor).count() == 1;
}
private static boolean isLambdaCls(ClassNode cls) {
return cls.getAccessFlags().isSynthetic()
&& cls.getAccessFlags().isFinal()
&& cls.getClassInfo().getRawName().contains(".-$$Lambda$")
&& countStaticFields(cls) == 0;
}
private static int countStaticFields(ClassNode cls) {
int c = 0;
for (FieldNode field : cls.getFields()) {
if (field.getAccessFlags().isStatic()) {
c++;
}
}
return c;
}
}
...@@ -8,6 +8,7 @@ import org.junit.jupiter.api.Test; ...@@ -8,6 +8,7 @@ import org.junit.jupiter.api.Test;
import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.ClassNode;
import jadx.tests.api.IntegrationTest; import jadx.tests.api.IntegrationTest;
import static jadx.tests.api.utils.JadxMatchers.containsOne;
import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
...@@ -37,5 +38,17 @@ public class TestAnonymousClass extends IntegrationTest { ...@@ -37,5 +38,17 @@ public class TestAnonymousClass extends IntegrationTest {
assertThat(code, not(containsString("this"))); assertThat(code, not(containsString("this")));
assertThat(code, not(containsString("null"))); assertThat(code, not(containsString("null")));
assertThat(code, not(containsString("AnonymousClass_"))); assertThat(code, not(containsString("AnonymousClass_")));
assertThat(code, not(containsString("class AnonymousClass")));
}
@Test
public void testNoInline() {
getArgs().setInlineAnonymousClasses(false);
ClassNode cls = getClassNode(TestCls.class);
String code = cls.getCode().toString();
assertThat(code, containsString("class AnonymousClass1 implements FilenameFilter {"));
assertThat(code, containsOne("new AnonymousClass1()"));
} }
} }
package jadx.gui.settings; package jadx.gui.settings;
import java.awt.Font; import java.awt.*;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Window;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.ArrayList; import java.util.ArrayList;
...@@ -15,7 +12,7 @@ import java.util.Map; ...@@ -15,7 +12,7 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.function.Consumer; import java.util.function.Consumer;
import javax.swing.JFrame; import javax.swing.*;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
...@@ -281,6 +278,10 @@ public class JadxSettings extends JadxCLIArgs { ...@@ -281,6 +278,10 @@ public class JadxSettings extends JadxCLIArgs {
this.useImports = useImports; this.useImports = useImports;
} }
public void setInlineAnonymousClasses(boolean inlineAnonymousClasses) {
this.inlineAnonymousClasses = inlineAnonymousClasses;
}
public boolean isAutoStartJobs() { public boolean isAutoStartJobs() {
return autoStartJobs; return autoStartJobs;
} }
......
...@@ -347,6 +347,13 @@ public class JadxSettingsWindow extends JDialog { ...@@ -347,6 +347,13 @@ public class JadxSettingsWindow extends JDialog {
needReload(); needReload();
}); });
JCheckBox inlineAnonymous = new JCheckBox();
inlineAnonymous.setSelected(settings.isInlineAnonymousClasses());
inlineAnonymous.addItemListener(e -> {
settings.setInlineAnonymousClasses(e.getStateChange() == ItemEvent.SELECTED);
needReload();
});
SettingsGroup other = new SettingsGroup(NLS.str("preferences.decompile")); SettingsGroup other = new SettingsGroup(NLS.str("preferences.decompile"));
other.addRow(NLS.str("preferences.threads"), threadsCount); other.addRow(NLS.str("preferences.threads"), threadsCount);
other.addRow(NLS.str("preferences.excludedPackages"), NLS.str("preferences.excludedPackages.tooltip"), other.addRow(NLS.str("preferences.excludedPackages"), NLS.str("preferences.excludedPackages.tooltip"),
...@@ -357,6 +364,7 @@ public class JadxSettingsWindow extends JDialog { ...@@ -357,6 +364,7 @@ public class JadxSettingsWindow extends JDialog {
other.addRow(NLS.str("preferences.replaceConsts"), replaceConsts); other.addRow(NLS.str("preferences.replaceConsts"), replaceConsts);
other.addRow(NLS.str("preferences.respectBytecodeAccessModifiers"), respectBytecodeAccessModifiers); other.addRow(NLS.str("preferences.respectBytecodeAccessModifiers"), respectBytecodeAccessModifiers);
other.addRow(NLS.str("preferences.useImports"), useImports); other.addRow(NLS.str("preferences.useImports"), useImports);
other.addRow(NLS.str("preferences.inlineAnonymous"), inlineAnonymous);
other.addRow(NLS.str("preferences.fallback"), fallback); other.addRow(NLS.str("preferences.fallback"), fallback);
other.addRow(NLS.str("preferences.skipResourcesDecode"), resourceDecode); other.addRow(NLS.str("preferences.skipResourcesDecode"), resourceDecode);
return other; return other;
......
...@@ -90,6 +90,7 @@ preferences.escapeUnicode=Escape unicode ...@@ -90,6 +90,7 @@ preferences.escapeUnicode=Escape unicode
preferences.replaceConsts=Replace constants preferences.replaceConsts=Replace constants
preferences.respectBytecodeAccessModifiers=Respect bytecode access modifiers preferences.respectBytecodeAccessModifiers=Respect bytecode access modifiers
preferences.useImports=Use import statements preferences.useImports=Use import statements
preferences.inlineAnonymous=Inline anonymous classes
preferences.skipResourcesDecode=Don't decode resources preferences.skipResourcesDecode=Don't decode resources
preferences.autoSave=Auto save preferences.autoSave=Auto save
preferences.threads=Processing threads count preferences.threads=Processing threads count
......
...@@ -90,6 +90,7 @@ preferences.escapeUnicode=Escape unicode ...@@ -90,6 +90,7 @@ preferences.escapeUnicode=Escape unicode
preferences.replaceConsts=Reemplazar constantes preferences.replaceConsts=Reemplazar constantes
#preferences.respectBytecodeAccessModifiers= #preferences.respectBytecodeAccessModifiers=
#preferences.useImports= #preferences.useImports=
#preferences.inlineAnonymous=
preferences.skipResourcesDecode=No descodificar recursos preferences.skipResourcesDecode=No descodificar recursos
#preferences.autoSave= #preferences.autoSave=
preferences.threads=Número de hilos a procesar preferences.threads=Número de hilos a procesar
......
...@@ -90,6 +90,7 @@ preferences.escapeUnicode=将 Unicode 字符转义 ...@@ -90,6 +90,7 @@ preferences.escapeUnicode=将 Unicode 字符转义
preferences.replaceConsts=替换常量 preferences.replaceConsts=替换常量
preferences.respectBytecodeAccessModifiers=遵守字节码访问修饰符 preferences.respectBytecodeAccessModifiers=遵守字节码访问修饰符
preferences.useImports=使用 import 语句 preferences.useImports=使用 import 语句
#preferences.inlineAnonymous=
preferences.skipResourcesDecode=不反编译资源文件 preferences.skipResourcesDecode=不反编译资源文件
#preferences.autoSave= #preferences.autoSave=
preferences.threads=并行线程数 preferences.threads=并行线程数
......
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