feat: add option for code comments levels (#998)

This commit is contained in:
Skylot
2021-10-19 15:01:25 +01:00
parent 37adce2efb
commit 48252c3c3d
46 changed files with 340 additions and 208 deletions
+2 -1
View File
@@ -105,7 +105,8 @@ options:
--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)
--log-level - set log level, values: QUIET, PROGRESS, ERROR, WARN, INFO, DEBUG, default: PROGRESS
--comments-level - set code comments level, values: none, user_only, error, warn, info, debug, default: info
--log-level - set log level, values: quiet, progress, error, warn, info, debug, default: progress
-v, --verbose - verbose output (set --log-level to DEBUG)
-q, --quiet - turn off output (set --log-level to QUIET)
--version - print jadx version
@@ -6,6 +6,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.jetbrains.annotations.Nullable;
@@ -130,7 +131,7 @@ public class JCommanderWrapper<T> {
if (Enum.class.isAssignableFrom(fieldType)) {
Enum<?> val = (Enum<?>) f.get(args);
if (val != null) {
return val.name();
return val.name().toLowerCase(Locale.ROOT);
}
}
} catch (Exception e) {
@@ -11,6 +11,7 @@ import java.util.stream.Stream;
import com.beust.jcommander.IStringConverter;
import com.beust.jcommander.Parameter;
import jadx.api.CommentsLevel;
import jadx.api.JadxArgs;
import jadx.api.JadxArgs.RenameEnum;
import jadx.api.JadxDecompiler;
@@ -102,7 +103,7 @@ public class JadxCLIArgs {
@Parameter(
names = { "--rename-flags" },
description = "fix options (comma-separated list of): "
description = "fix options (comma-separated list of):"
+ "\n 'case' - fix case sensitivity issues (according to --fs-case-sensitive option),"
+ "\n 'valid' - rename java identifiers to make them valid,"
+ "\n 'printable' - remove non-printable chars from identifiers,"
@@ -124,9 +125,16 @@ public class JadxCLIArgs {
@Parameter(names = { "-f", "--fallback" }, description = "make simple dump (using goto instead of 'if', 'for', etc)")
protected boolean fallbackMode = false;
@Parameter(
names = { "--comments-level" },
description = "set code comments level, values: error, warn, info, debug, user_only, none",
converter = CommentsLevelConverter.class
)
protected CommentsLevel commentsLevel = CommentsLevel.INFO;
@Parameter(
names = { "--log-level" },
description = "set log level, values: QUIET, PROGRESS, ERROR, WARN, INFO, DEBUG",
description = "set log level, values: quiet, progress, error, warn, info, debug",
converter = LogHelper.LogLevelConverter.class
)
protected LogHelper.LogLevelEnum logLevel = LogHelper.LogLevelEnum.PROGRESS;
@@ -222,6 +230,7 @@ public class JadxCLIArgs {
args.setInlineMethods(inlineMethods);
args.setRenameFlags(renameFlags);
args.setFsCaseSensitive(fsCaseSensitive);
args.setCommentsLevel(commentsLevel);
return args;
}
@@ -349,6 +358,10 @@ public class JadxCLIArgs {
return fsCaseSensitive;
}
public CommentsLevel getCommentsLevel() {
return commentsLevel;
}
static class RenameConverter implements IStringConverter<Set<RenameEnum>> {
private final String paramName;
@@ -378,9 +391,22 @@ public class JadxCLIArgs {
}
}
public static class CommentsLevelConverter implements IStringConverter<CommentsLevel> {
@Override
public CommentsLevel convert(String value) {
try {
return CommentsLevel.valueOf(value.toUpperCase());
} catch (Exception e) {
throw new IllegalArgumentException(
'\'' + value + "' is unknown comments level, possible values are: "
+ JadxCLIArgs.enumValuesString(CommentsLevel.values()));
}
}
}
public static String enumValuesString(Enum<?>[] values) {
return Stream.of(values)
.map(v -> '\'' + v.name().toLowerCase(Locale.ROOT) + '\'')
.map(v -> v.name().toLowerCase(Locale.ROOT))
.collect(Collectors.joining(", "));
}
}
@@ -42,8 +42,7 @@ public class RenameConverterTest {
() -> converter.convert("wrong"),
"Expected convert() to throw, but it didn't");
assertEquals("'wrong' is unknown for parameter someParam, "
+ "possible values are 'case', 'valid', 'printable'",
assertEquals("'wrong' is unknown for parameter someParam, possible values are case, valid, printable",
thrown.getMessage());
}
}
@@ -0,0 +1,14 @@
package jadx.api;
public enum CommentsLevel {
NONE,
USER_ONLY,
ERROR,
WARN,
INFO,
DEBUG;
public boolean filter(CommentsLevel limit) {
return this.ordinal() <= limit.ordinal();
}
}
@@ -83,6 +83,8 @@ public class JadxArgs {
private ICodeData codeData;
private CommentsLevel commentsLevel = CommentsLevel.INFO;
public JadxArgs() {
// use default options
}
@@ -413,6 +415,14 @@ public class JadxArgs {
this.codeData = codeData;
}
public CommentsLevel getCommentsLevel() {
return commentsLevel;
}
public void setCommentsLevel(CommentsLevel commentsLevel) {
this.commentsLevel = commentsLevel;
}
@Override
public String toString() {
return "JadxArgs{" + "inputFiles=" + inputFiles
@@ -441,6 +451,7 @@ public class JadxArgs {
+ ", fsCaseSensitive=" + fsCaseSensitive
+ ", renameFlags=" + renameFlags
+ ", outputFormat=" + outputFormat
+ ", commentsLevel=" + commentsLevel
+ ", codeCache=" + codeCache
+ ", codeWriter=" + codeWriterProvider.apply(this).getClass().getSimpleName()
+ '}';
@@ -51,6 +51,7 @@ import jadx.core.xmlgen.ResourcesSaver;
*
* <pre>
* <code>
*
* JadxArgs args = new JadxArgs();
* args.getInputFiles().add(new File("test.apk"));
* args.setOutDir(new File("jadx-test-output"));
@@ -65,6 +66,7 @@ import jadx.core.xmlgen.ResourcesSaver;
*
* <pre>
* <code>
*
* for(JavaClass cls : jadx.getClasses()) {
* System.out.println(cls.getCode());
* }
+4 -1
View File
@@ -10,6 +10,7 @@ import java.util.jar.Manifest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.CommentsLevel;
import jadx.api.JadxArgs;
import jadx.core.dex.visitors.AttachCommentsVisitor;
import jadx.core.dex.visitors.AttachMethodDetails;
@@ -101,7 +102,9 @@ public class Jadx {
passes.add(new DebugInfoAttachVisitor());
}
passes.add(new AttachTryCatchVisitor());
passes.add(new AttachCommentsVisitor());
if (args.getCommentsLevel() != CommentsLevel.NONE) {
passes.add(new AttachCommentsVisitor());
}
passes.add(new AttachMethodDetails());
passes.add(new ProcessInstructionsVisitor());
@@ -14,6 +14,7 @@ import java.util.stream.Stream;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import jadx.api.CommentsLevel;
import jadx.api.ICodeInfo;
import jadx.api.ICodeWriter;
import jadx.api.JadxArgs;
@@ -24,11 +25,9 @@ import jadx.api.plugins.input.data.attributes.JadxAttrType;
import jadx.core.Consts;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.AttrNode;
import jadx.core.dex.attributes.FieldInitInsnAttr;
import jadx.core.dex.attributes.nodes.EnumClassAttr;
import jadx.core.dex.attributes.nodes.EnumClassAttr.EnumField;
import jadx.core.dex.attributes.nodes.JadxError;
import jadx.core.dex.attributes.nodes.LineAttrNode;
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
import jadx.core.dex.info.AccessInfo;
@@ -44,7 +43,6 @@ import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.CodeGenUtils;
import jadx.core.utils.EncodedValueUtils;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.Utils;
import jadx.core.utils.android.AndroidResourcesUtils;
import jadx.core.utils.exceptions.CodegenException;
@@ -125,9 +123,8 @@ public class ClassGen {
if (Consts.DEBUG_USAGE) {
addClassUsageInfo(code, cls);
}
insertDecompilationProblems(code, cls);
CodeGenUtils.addErrorsAndComments(code, cls);
CodeGenUtils.addSourceFileInfo(code, cls);
CodeGenUtils.addComments(code, cls);
addClassDeclaration(code);
addClassBody(code);
}
@@ -151,7 +148,7 @@ public class ClassGen {
annotationGen.addForClass(clsCode);
insertRenameInfo(clsCode, cls);
CodeGenUtils.addInputFileInfo(clsCode, cls);
clsCode.startLineWithNum(cls.getSourceLine()).add(af.makeString());
clsCode.startLineWithNum(cls.getSourceLine()).add(af.makeString(cls.checkCommentsLevel(CommentsLevel.INFO)));
if (af.isInterface()) {
if (af.isAnnotation()) {
clsCode.add('@');
@@ -247,7 +244,7 @@ public class ClassGen {
*/
public void addClassBody(ICodeWriter clsCode, boolean printClassName) throws CodegenException {
clsCode.add('{');
if (printClassName) {
if (printClassName && cls.checkCommentsLevel(CommentsLevel.INFO)) {
clsCode.add(" // from class: " + cls.getClassInfo().getFullName());
}
setBodyGenStarted(true);
@@ -304,12 +301,9 @@ public class ClassGen {
if (mth.getParentClass().getTopParentClass().contains(AFlag.RESTART_CODEGEN)) {
throw new JadxRuntimeException("Method generation error", e);
}
code.newLine().add("/*");
code.newLine().addMultiLine(ErrorsCounter.error(mth, "Method generation error", e));
Utils.appendStackTrace(code, e);
code.newLine().add("*/");
mth.addError("Method generation error", e);
CodeGenUtils.addErrors(code, mth);
code.setIndent(savedIndent);
mth.addError("Method generation error: " + e.getMessage(), e);
}
}
@@ -323,8 +317,7 @@ public class ClassGen {
}
public void addMethodCode(ICodeWriter code, MethodNode mth) throws CodegenException {
CodeGenUtils.addComments(code, mth);
insertDecompilationProblems(code, mth);
CodeGenUtils.addErrorsAndComments(code, mth);
if (mth.isNoCode()) {
MethodGen mthGen = new MethodGen(this, mth);
mthGen.addDefinition(code);
@@ -332,7 +325,6 @@ public class ClassGen {
} else {
boolean badCode = mth.contains(AFlag.INCONSISTENT_CODE);
if (badCode && showInconsistentCode) {
mth.remove(AFlag.INCONSISTENT_CODE);
badCode = false;
}
MethodGen mthGen;
@@ -352,27 +344,6 @@ public class ClassGen {
}
}
public void insertDecompilationProblems(ICodeWriter code, AttrNode node) {
List<JadxError> errors = node.getAll(AType.JADX_ERROR);
if (!errors.isEmpty()) {
errors.stream().distinct().sorted().forEach(err -> {
code.startLine("/* JADX ERROR: ").add(err.getError());
Throwable cause = err.getCause();
if (cause != null) {
code.incIndent();
Utils.appendStackTrace(code, cause);
code.decIndent();
}
code.add("*/");
});
}
List<String> warns = node.getAll(AType.JADX_WARN);
if (!warns.isEmpty()) {
warns.stream().distinct().sorted()
.forEach(warn -> code.startLine("/* JADX WARNING: ").addMultiLine(warn).add(" */"));
}
}
private void addFields(ICodeWriter code) throws CodegenException {
addEnumFields(code);
for (FieldNode f : cls.getFields()) {
@@ -390,11 +361,12 @@ public class ClassGen {
CodeGenUtils.addComments(code, f);
annotationGen.addForField(code, f);
if (f.getFieldInfo().isRenamed()) {
boolean addInfoComments = f.checkCommentsLevel(CommentsLevel.INFO);
if (f.getFieldInfo().isRenamed() && addInfoComments) {
code.newLine();
CodeGenUtils.addRenamedComment(code, f, f.getName());
}
code.startLine(f.getAccessFlags().makeString());
code.startLine(f.getAccessFlags().makeString(addInfoComments));
useType(code, f.getType());
code.add(' ');
code.attachDefinition(f);
@@ -707,7 +679,7 @@ public class ClassGen {
private void insertRenameInfo(ICodeWriter code, ClassNode cls) {
ClassInfo classInfo = cls.getClassInfo();
if (classInfo.hasAlias()) {
if (classInfo.hasAlias() && cls.checkCommentsLevel(CommentsLevel.INFO)) {
CodeGenUtils.addRenamedComment(code, cls, classInfo.getType().getObject());
}
}
@@ -9,6 +9,7 @@ import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.CommentsLevel;
import jadx.api.ICodeWriter;
import jadx.api.data.annotations.InsnCodeOffset;
import jadx.api.plugins.input.data.MethodHandleType;
@@ -278,7 +279,7 @@ public class InsnGen {
makeInsnBody(code, insn, EMPTY_FLAGS);
if (flag != Flags.INLINE) {
code.add(';');
CodeGenUtils.addCodeComments(code, insn);
CodeGenUtils.addCodeComments(code, mth, insn);
}
}
} catch (Exception e) {
@@ -608,7 +609,9 @@ public class InsnGen {
* Use one by one array fill (can be replaced with System.arrayCopy)
*/
private void fillArray(ICodeWriter code, FillArrayInsn arrayNode) throws CodegenException {
code.add("// fill-array-data instruction");
if (mth.checkCommentsLevel(CommentsLevel.INFO)) {
code.add("// fill-array-data instruction");
}
code.startLine();
InsnArg arrArg = arrayNode.getArg(0);
ArgType arrayType = arrArg.getType();
@@ -9,6 +9,7 @@ import java.util.stream.Stream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.CommentsLevel;
import jadx.api.ICodeWriter;
import jadx.api.data.annotations.InsnCodeOffset;
import jadx.api.plugins.input.data.AccessFlags;
@@ -110,12 +111,12 @@ public class MethodGen {
if (mth.getMethodInfo().hasAlias() && !ai.isConstructor()) {
CodeGenUtils.addRenamedComment(code, mth, mth.getName());
}
if (mth.contains(AFlag.INCONSISTENT_CODE)) {
code.startLine("/* Code decompiled incorrectly, please refer to instructions dump. */");
if (mth.contains(AFlag.INCONSISTENT_CODE) && mth.checkCommentsLevel(CommentsLevel.ERROR)) {
code.startLine("/* Code decompiled incorrectly, please refer to instructions dump */");
}
code.startLineWithNum(mth.getSourceLine());
code.add(ai.makeString());
code.add(ai.makeString(mth.checkCommentsLevel(CommentsLevel.INFO)));
if (clsAccFlags.isInterface() && !mth.isNoCode() && !mth.getAccessFlags().isStatic()) {
// add 'default' for method with code in interface
code.add("default ");
@@ -171,8 +172,11 @@ public class MethodGen {
}
if (!overrideAttr.isAtBaseMth()) {
code.startLine("@Override");
code.add(" // ");
code.add(Utils.listToString(overrideAttr.getOverrideList(), ", ", md -> md.getMethodInfo().getDeclClass().getAliasFullName()));
if (mth.checkCommentsLevel(CommentsLevel.INFO)) {
code.add(" // ");
code.add(Utils.listToString(overrideAttr.getOverrideList(), ", ",
md -> md.getMethodInfo().getDeclClass().getAliasFullName()));
}
}
if (Consts.DEBUG) {
code.startLine("// related by override: ");
@@ -217,7 +221,7 @@ public class MethodGen {
classGen.useType(code, elType);
code.add("...");
} else {
mth.addComment("JADX INFO: Last argument in varargs method is not array: " + var);
mth.addWarnComment("Last argument in varargs method is not array: " + var);
classGen.useType(code, argType);
}
} else {
@@ -261,23 +265,24 @@ public class MethodGen {
regionGen.makeRegion(code, mth.getRegion());
} catch (StackOverflowError | BootstrapMethodError e) {
mth.addError("Method code generation error", new JadxOverflowException("StackOverflow"));
classGen.insertDecompilationProblems(code, mth);
CodeGenUtils.addErrors(code, mth);
dumpInstructions(code);
} catch (Exception e) {
if (mth.getParentClass().getTopParentClass().contains(AFlag.RESTART_CODEGEN)) {
throw e;
}
mth.addError("Method code generation error", e);
classGen.insertDecompilationProblems(code, mth);
CodeGenUtils.addErrors(code, mth);
dumpInstructions(code);
}
}
public void dumpInstructions(ICodeWriter code) {
code.startLine("/*");
addFallbackMethodCode(code, COMMENTED_DUMP);
code.startLine("*/");
if (mth.checkCommentsLevel(CommentsLevel.ERROR)) {
code.startLine("/*");
addFallbackMethodCode(code, COMMENTED_DUMP);
code.startLine("*/");
}
code.startLine("throw new UnsupportedOperationException(\"Method not decompiled: ")
.add(mth.getParentClass().getClassInfo().getAliasFullName())
.add('.')
@@ -313,7 +318,7 @@ public class MethodGen {
code.startLine("// Can't load method instructions.");
return;
}
if (fallbackOption == COMMENTED_DUMP) {
if (fallbackOption == COMMENTED_DUMP && mth.getCommentsLevel() != CommentsLevel.DEBUG) {
long insnCountEstimate = Stream.of(insnArr)
.filter(Objects::nonNull)
.filter(insn -> insn.getType() != InsnType.NOP)
@@ -386,7 +391,7 @@ public class MethodGen {
if (catchAttr != null) {
code.add(" // " + catchAttr);
}
CodeGenUtils.addCodeComments(code, insn);
CodeGenUtils.addCodeComments(code, mth, insn);
} catch (Exception e) {
LOG.debug("Error generate fallback instruction: ", e.getCause());
code.setIndent(startIndent);
@@ -8,6 +8,7 @@ import java.util.Objects;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.CommentsLevel;
import jadx.api.ICodeWriter;
import jadx.api.data.ICodeComment;
import jadx.api.data.annotations.CustomOffsetRef;
@@ -86,7 +87,7 @@ public class RegionGen extends InsnGen {
code.attachLineAnnotation(new CustomOffsetRef(offset, ICodeComment.AttachType.VAR_DECLARE));
}
}
CodeGenUtils.addCodeComments(code, assignReg);
CodeGenUtils.addCodeComments(code, mth, assignReg);
}
private void makeRegionIndent(ICodeWriter code, IContainer region) throws CodegenException {
@@ -131,7 +132,7 @@ public class RegionGen extends InsnGen {
BlockNode blockNode = conditionBlocks.get(0);
InsnNode lastInsn = BlockUtils.getLastInsn(blockNode);
InsnCodeOffset.attach(code, lastInsn);
CodeGenUtils.addCodeComments(code, lastInsn);
CodeGenUtils.addCodeComments(code, mth, lastInsn);
}
}
makeRegionIndent(code, region.getThenRegion());
@@ -205,7 +206,7 @@ public class RegionGen extends InsnGen {
code.add("; ");
makeInsn(forLoop.getIncrInsn(), code, Flags.INLINE);
code.add(") {");
CodeGenUtils.addCodeComments(code, condInsn);
CodeGenUtils.addCodeComments(code, mth, condInsn);
makeRegionIndent(code, region.getBody());
code.startLine('}');
return;
@@ -217,7 +218,7 @@ public class RegionGen extends InsnGen {
code.add(" : ");
addArg(code, forEachLoop.getIterableArg(), false);
code.add(") {");
CodeGenUtils.addCodeComments(code, condInsn);
CodeGenUtils.addCodeComments(code, mth, condInsn);
makeRegionIndent(code, region.getBody());
code.startLine('}');
return;
@@ -226,7 +227,7 @@ public class RegionGen extends InsnGen {
}
if (region.isConditionAtEnd()) {
code.add("do {");
CodeGenUtils.addCodeComments(code, condInsn);
CodeGenUtils.addCodeComments(code, mth, condInsn);
makeRegionIndent(code, region.getBody());
code.startLineWithNum(region.getConditionSourceLine());
code.add("} while (");
@@ -236,7 +237,7 @@ public class RegionGen extends InsnGen {
code.add("while (");
conditionGen.add(code, condition);
code.add(") {");
CodeGenUtils.addCodeComments(code, condInsn);
CodeGenUtils.addCodeComments(code, mth, condInsn);
makeRegionIndent(code, region.getBody());
code.startLine('}');
}
@@ -249,7 +250,7 @@ public class RegionGen extends InsnGen {
code.add(") {");
InsnCodeOffset.attach(code, monitorEnterInsn);
CodeGenUtils.addCodeComments(code, monitorEnterInsn);
CodeGenUtils.addCodeComments(code, mth, monitorEnterInsn);
makeRegionIndent(code, cont.getRegion());
code.startLine('}');
@@ -263,7 +264,7 @@ public class RegionGen extends InsnGen {
addArg(code, arg, false);
code.add(") {");
InsnCodeOffset.attach(code, insn);
CodeGenUtils.addCodeComments(code, insn);
CodeGenUtils.addCodeComments(code, mth, insn);
code.incIndent();
for (CaseInfo caseInfo : sw.getCases()) {
@@ -291,10 +292,12 @@ public class RegionGen extends InsnGen {
code.add(fn.getAlias());
} else {
staticField(code, fn.getFieldInfo());
// print original value, sometimes replaced with incorrect field
EncodedValue constVal = fn.get(JadxAttrType.CONSTANT_VALUE);
if (constVal != null && constVal.getValue() != null) {
code.add(" /* ").add(constVal.getValue().toString()).add(" */");
if (mth.checkCommentsLevel(CommentsLevel.INFO)) {
// print original value, sometimes replaced with incorrect field
EncodedValue constVal = fn.get(JadxAttrType.CONSTANT_VALUE);
if (constVal != null && constVal.getValue() != null) {
code.add(" /* ").add(constVal.getValue().toString()).add(" */");
}
}
}
} else if (k instanceof Integer) {
@@ -309,7 +312,7 @@ public class RegionGen extends InsnGen {
InsnNode insn = BlockUtils.getFirstInsn(Utils.first(region.getTryCatchBlock().getBlocks()));
InsnCodeOffset.attach(code, insn);
CodeGenUtils.addCodeComments(code, insn);
CodeGenUtils.addCodeComments(code, mth, insn);
makeRegionIndent(code, region.getTryRegion());
// TODO: move search of 'allHandler' to 'TryCatchRegion'
@@ -388,7 +391,7 @@ public class RegionGen extends InsnGen {
code.add(") {");
InsnCodeOffset.attach(code, handler.getHandlerOffset());
CodeGenUtils.addCodeComments(code, handler.getHandlerBlock());
CodeGenUtils.addCodeComments(code, mth, handler.getHandlerBlock());
makeRegionIndent(code, region);
}
@@ -85,8 +85,7 @@ public class JsonCodeGen {
}
ICodeWriter cw = new SimpleCodeWriter();
CodeGenUtils.addComments(cw, cls);
classGen.insertDecompilationProblems(cw, cls);
CodeGenUtils.addErrorsAndComments(cw, cls);
classGen.addClassDeclaration(cw);
jsonCls.setDeclaration(cw.getCodeStr());
@@ -10,6 +10,7 @@ import jadx.core.dex.attributes.nodes.EnumMapAttr;
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
import jadx.core.dex.attributes.nodes.ForceReturnAttr;
import jadx.core.dex.attributes.nodes.GenericInfoAttr;
import jadx.core.dex.attributes.nodes.JadxCommentsAttr;
import jadx.core.dex.attributes.nodes.JadxError;
import jadx.core.dex.attributes.nodes.JumpInfo;
import jadx.core.dex.attributes.nodes.LocalVarsDebugInfoAttr;
@@ -43,9 +44,8 @@ public final class AType<T extends IJadxAttribute> implements IJadxAttrType<T> {
public static final AType<RenameReasonAttr> RENAME_REASON = new AType<>();
// class, method
public static final AType<AttrList<JadxError>> JADX_ERROR = new AType<>(); // code failed to decompile completely
public static final AType<AttrList<String>> JADX_WARN = new AType<>(); // mark code as inconsistent (code can be viewed)
public static final AType<AttrList<String>> COMMENTS = new AType<>(); // any additional info about decompilation
public static final AType<AttrList<JadxError>> JADX_ERROR = new AType<>(); // code failed to decompile
public static final AType<JadxCommentsAttr> JADX_COMMENTS = new AType<>(); // additional info about decompilation
// class
public static final AType<EnumClassAttr> ENUM_CLASS = new AType<>();
@@ -0,0 +1,47 @@
package jadx.core.dex.attributes.nodes;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import jadx.api.CommentsLevel;
import jadx.api.plugins.input.data.attributes.IJadxAttrType;
import jadx.api.plugins.input.data.attributes.IJadxAttribute;
import jadx.core.dex.attributes.AType;
public class JadxCommentsAttr implements IJadxAttribute {
private final Map<CommentsLevel, List<String>> comments = new EnumMap<>(CommentsLevel.class);
public void add(CommentsLevel level, String comment) {
comments.computeIfAbsent(level, (l) -> new ArrayList<>()).add(comment);
}
public List<String> formatAndFilter(CommentsLevel level) {
if (level == CommentsLevel.NONE || level == CommentsLevel.USER_ONLY) {
return Collections.emptyList();
}
return comments.entrySet().stream()
.filter(e -> e.getKey().filter(level))
.flatMap(e -> {
String levelName = e.getKey().name();
return e.getValue().stream()
.map(v -> "JADX " + levelName + ": " + v);
})
.distinct()
.sorted()
.collect(Collectors.toList());
}
public Map<CommentsLevel, List<String>> getComments() {
return comments;
}
@Override
public IJadxAttrType<JadxCommentsAttr> getAttrType() {
return AType.JADX_COMMENTS;
}
}
@@ -27,7 +27,7 @@ public class MethodInlineAttr extends PinnedAttribute {
MethodInlineAttr mia = new MethodInlineAttr(replaceInsn, regNums);
mth.addAttr(mia);
if (Consts.DEBUG) {
mth.addAttr(AType.COMMENTS, "Removed for inline");
mth.addDebugComment("Removed for inline");
} else {
mth.add(AFlag.DONT_GENERATE);
}
@@ -1,43 +1,56 @@
package jadx.core.dex.attributes.nodes;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.CommentsLevel;
import jadx.api.ICodeWriter;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.nodes.ICodeNode;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.Utils;
public abstract class NotificationAttrNode extends LineAttrNode implements ICodeNode {
private static final Logger LOG = LoggerFactory.getLogger(NotificationAttrNode.class);
public boolean checkCommentsLevel(CommentsLevel required) {
return required.filter(this.root().getArgs().getCommentsLevel());
}
public void addError(String errStr, Throwable e) {
ErrorsCounter.error(this, errStr, e);
}
public void addWarn(String warnStr) {
ErrorsCounter.warning(this, warnStr);
public void addWarn(String warn) {
ErrorsCounter.warning(this, warn);
initCommentsAttr().add(CommentsLevel.WARN, warn);
this.add(AFlag.INCONSISTENT_CODE);
}
public void addWarnComment(String warn) {
addWarnComment(warn, null);
initCommentsAttr().add(CommentsLevel.WARN, warn);
}
public void addWarnComment(String warn, @Nullable Throwable exc) {
String commentStr = "JADX WARN: " + warn;
addAttr(AType.COMMENTS, commentStr);
if (exc != null) {
LOG.warn("{} in {}", warn, this, exc);
} else {
LOG.warn("{} in {}", warn, this);
}
public void addWarnComment(String warn, Throwable exc) {
String commentStr = warn + ICodeWriter.NL + Utils.getStackTrace(exc);
initCommentsAttr().add(CommentsLevel.WARN, commentStr);
}
public void addComment(String commentStr) {
addAttr(AType.COMMENTS, commentStr);
public void addInfoComment(String commentStr) {
initCommentsAttr().add(CommentsLevel.INFO, commentStr);
}
public void addDebugComment(String commentStr) {
addAttr(AType.COMMENTS, "JADX DEBUG: " + commentStr);
initCommentsAttr().add(CommentsLevel.DEBUG, commentStr);
}
public CommentsLevel getCommentsLevel() {
return this.root().getArgs().getCommentsLevel();
}
private JadxCommentsAttr initCommentsAttr() {
JadxCommentsAttr commentsAttr = this.get(AType.JADX_COMMENTS);
if (commentsAttr == null) {
commentsAttr = new JadxCommentsAttr();
this.addAttr(commentsAttr);
}
return commentsAttr;
}
}
@@ -155,7 +155,7 @@ public class AccessInfo {
return type;
}
public String makeString() {
public String makeString(boolean showHidden) {
StringBuilder code = new StringBuilder();
if (isPublic()) {
code.append("public ");
@@ -183,11 +183,13 @@ public class AccessInfo {
if (isSynchronized()) {
code.append("synchronized ");
}
if (isBridge()) {
code.append("/* bridge */ ");
}
if (Consts.DEBUG && isVarArgs()) {
code.append("/* varargs */ ");
if (showHidden) {
if (isBridge()) {
code.append("/* bridge */ ");
}
if (Consts.DEBUG && isVarArgs()) {
code.append("/* varargs */ ");
}
}
break;
@@ -204,20 +206,22 @@ public class AccessInfo {
if ((accFlags & AccessFlags.STRICT) != 0) {
code.append("strict ");
}
if (isModuleInfo()) {
code.append("/* module-info */ ");
}
if (Consts.DEBUG) {
if ((accFlags & AccessFlags.SUPER) != 0) {
code.append("/* super */ ");
if (showHidden) {
if (isModuleInfo()) {
code.append("/* module-info */ ");
}
if ((accFlags & AccessFlags.ENUM) != 0) {
code.append("/* enum */ ");
if (Consts.DEBUG) {
if ((accFlags & AccessFlags.SUPER) != 0) {
code.append("/* super */ ");
}
if ((accFlags & AccessFlags.ENUM) != 0) {
code.append("/* enum */ ");
}
}
}
break;
}
if (isSynthetic()) {
if (isSynthetic() && showHidden) {
code.append("/* synthetic */ ");
}
return code.toString();
@@ -245,6 +249,6 @@ public class AccessInfo {
@Override
public String toString() {
return "AccessInfo: " + type + " 0x" + Integer.toHexString(accFlags) + " (" + makeString() + ')';
return "AccessInfo: " + type + " 0x" + Integer.toHexString(accFlags) + " (" + makeString(true) + ')';
}
}
@@ -4,13 +4,13 @@ import java.util.Collections;
import java.util.List;
import jadx.api.plugins.input.data.IFieldData;
import jadx.core.dex.attributes.nodes.LineAttrNode;
import jadx.core.dex.attributes.nodes.NotificationAttrNode;
import jadx.core.dex.info.AccessInfo;
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 implements ICodeNode {
public class FieldNode extends NotificationAttrNode implements ICodeNode {
private final ClassNode parentClass;
private final FieldInfo fieldInfo;
@@ -22,7 +22,6 @@ import jadx.api.plugins.input.data.IClassData;
import jadx.api.plugins.input.data.ILoadResult;
import jadx.core.Jadx;
import jadx.core.clsp.ClspGraph;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.ConstStorage;
import jadx.core.dex.info.FieldInfo;
@@ -146,7 +145,7 @@ public class RootNode {
.filter(s -> !s.equals(thisSource))
.sorted()
.collect(Collectors.joining("\n "));
cls.addAttr(AType.COMMENTS, "WARNING: Classes with same name are omitted:\n " + otherSourceStr + '\n');
cls.addWarnComment("Classes with same name are omitted:\n " + otherSourceStr + '\n');
});
});
}
@@ -23,8 +23,8 @@ import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.exceptions.JadxRuntimeException;
@JadxVisitor(
name = "Attach comments",
desc = "Attach comments",
name = "AttachComments",
desc = "Attach user code comments",
runBefore = {
ProcessInstructionsVisitor.class
}
@@ -7,7 +7,6 @@ import java.util.Objects;
import jadx.api.plugins.input.data.AccessFlags;
import jadx.core.Consts;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
import jadx.core.dex.info.AccessInfo;
@@ -149,7 +148,7 @@ public class ClassModifier extends AbstractVisitor {
ClassNode cls = mth.getParentClass();
if (removeBridgeMethod(cls, mth)) {
if (Consts.DEBUG) {
mth.addAttr(AType.COMMENTS, "Removed as synthetic bridge method");
mth.addDebugComment("Removed as synthetic bridge method");
} else {
mth.add(AFlag.DONT_GENERATE);
}
@@ -116,7 +116,7 @@ public class DotGraphVisitor extends AbstractVisitor {
}
dot.startLine("MethodNode[shape=record,label=\"{");
dot.add(escape(mth.getAccessFlags().makeString()));
dot.add(escape(mth.getAccessFlags().makeString(true)));
dot.add(escape(mth.getReturnType() + " "
+ mth.getParentClass() + '.' + mth.getName()
+ '(' + Utils.listToString(mth.getAllArgRegs()) + ") "));
@@ -15,7 +15,6 @@ import jadx.api.plugins.input.data.AccessFlags;
import jadx.core.codegen.TypeGen;
import jadx.core.deobf.NameMapper;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.EnumClassAttr;
import jadx.core.dex.attributes.nodes.EnumClassAttr.EnumField;
import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr;
@@ -76,7 +75,7 @@ public class EnumVisitor extends AbstractVisitor {
AccessInfo accessFlags = cls.getAccessFlags();
if (accessFlags.isEnum()) {
cls.setAccessFlags(accessFlags.remove(AccessFlags.ENUM));
cls.addAttr(AType.COMMENTS, "JADX INFO: Failed to restore enum class, 'enum' modifier removed");
cls.addWarnComment("Failed to restore enum class, 'enum' modifier removed");
}
}
return true;
@@ -88,7 +87,7 @@ public class EnumVisitor extends AbstractVisitor {
}
MethodNode classInitMth = cls.getClassInitMth();
if (classInitMth == null) {
cls.addAttr(AType.COMMENTS, "JADX INFO: Enum class init method not found");
cls.addWarnComment("Enum class init method not found");
return false;
}
if (classInitMth.getBasicBlocks().isEmpty()) {
@@ -117,7 +116,7 @@ public class EnumVisitor extends AbstractVisitor {
}
}
if (valuesCandidates.size() != 1) {
cls.addAttr(AType.COMMENTS, "JADX INFO: found several \"values\" enum fields: " + valuesCandidates);
cls.addWarnComment("Found several \"values\" enum fields: " + valuesCandidates);
return false;
}
FieldNode valuesField = valuesCandidates.get(0);
@@ -352,7 +351,7 @@ public class EnumVisitor extends AbstractVisitor {
FieldInfo fldInfo = FieldInfo.from(cls.root(), cls.getClassInfo(), name, cls.getType());
enumFieldNode = new FieldNode(cls, fldInfo, 0);
enumFieldNode.add(AFlag.SYNTHETIC);
enumFieldNode.addAttr(AType.COMMENTS, "Fake field, exist only in values array");
enumFieldNode.addInfoComment("Fake field, exist only in values array");
return enumFieldNode;
}
@@ -4,9 +4,9 @@ import jadx.api.plugins.input.data.AccessFlags;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
import jadx.core.dex.attributes.nodes.NotificationAttrNode;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.ICodeNode;
import jadx.core.dex.nodes.IMethodDetails;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
@@ -49,12 +49,12 @@ public class FixAccessModifiers extends AbstractVisitor {
}
}
public static void changeVisibility(ICodeNode node, int newVisFlag) {
public static void changeVisibility(NotificationAttrNode node, int newVisFlag) {
AccessInfo accessFlags = node.getAccessFlags();
AccessInfo newAccFlags = accessFlags.changeVisibility(newVisFlag);
if (newAccFlags != accessFlags) {
node.setAccessFlags(newAccFlags);
node.addAttr(AType.COMMENTS, "access modifiers changed from: " + accessFlags.visibilityName());
node.addInfoComment("Access modifiers changed from: " + accessFlags.visibilityName());
}
}
@@ -132,7 +132,7 @@ public class MarkMethodsForInline extends AbstractVisitor {
}
}
if (Consts.DEBUG) {
mth.addAttr(AType.COMMENTS, "JADX DEBUG: can't inline method, not implemented redirect type: " + insn);
mth.addDebugComment("can't inline method, not implemented redirect type: " + insn);
}
return false;
}
@@ -80,7 +80,7 @@ public class MethodInvokeVisitor extends AbstractVisitor {
IMethodDetails mthDetails = root.getMethodUtils().getMethodDetails(invokeInsn);
if (mthDetails == null) {
if (Consts.DEBUG) {
parentMth.addComment("JADX DEBUG: Method info not found: " + callMth);
parentMth.addDebugComment("Method info not found: " + callMth);
}
processUnknown(invokeInsn);
} else {
@@ -276,7 +276,7 @@ public class MethodInvokeVisitor extends AbstractVisitor {
}
if (Consts.DEBUG_OVERLOADED_CASTS) {
// TODO: try to minimize casts count
parentMth.addComment("JADX DEBUG: Failed to find minimal casts for resolve overloaded methods, cast all args instead"
parentMth.addDebugComment("Failed to find minimal casts for resolve overloaded methods, cast all args instead"
+ ICodeWriter.NL + " method: " + mthDetails
+ ICodeWriter.NL + " arg types: " + compilerVarTypes
+ ICodeWriter.NL + " candidates:"
@@ -253,7 +253,7 @@ public class OverrideMethodVisitor extends AbstractVisitor {
return;
}
if (updateReturnType(mth, baseMth, superTypes)) {
mth.addComment("Return type fixed from '" + returnType + "' to match base method");
mth.addInfoComment("Return type fixed from '" + returnType + "' to match base method");
}
}
@@ -231,11 +231,11 @@ public class PrepareForCodeGen extends AbstractVisitor {
Set<RegisterArg> regArgs = new HashSet<>();
constrInsn.getRegisterArgs(regArgs);
regArgs.remove(mth.getThisArg());
regArgs.removeAll(mth.getArgRegs());
mth.getArgRegs().forEach(regArgs::remove);
if (!regArgs.isEmpty()) {
mth.addWarn("Illegal instructions before constructor call");
} else {
mth.addComment("JADX INFO: '" + callType + "' call moved to the top of the method (can break code semantics)");
mth.addWarnComment("'" + callType + "' call moved to the top of the method (can break code semantics)");
}
}
}
@@ -31,7 +31,6 @@ import jadx.core.dex.visitors.ssa.SSATransform;
import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor;
import jadx.core.dex.visitors.typeinference.TypeUpdateResult;
import jadx.core.utils.BlockUtils;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.exceptions.JadxException;
@JadxVisitor(
@@ -54,7 +53,7 @@ public class DebugInfoApplyVisitor extends AbstractVisitor {
}
checkTypes(mth);
} catch (Exception e) {
LOG.error("Error to apply debug info: {}", ErrorsCounter.formatMsg(mth, e.getMessage()), e);
mth.addWarnComment("Failed to apply debug info", e);
}
}
@@ -94,7 +93,7 @@ public class DebugInfoApplyVisitor extends AbstractVisitor {
RegDebugInfoAttr debugInfo = debugInfoSet.iterator().next();
applyDebugInfo(mth, ssaVar, debugInfo.getRegType(), debugInfo.getName());
} else {
mth.addComment("JADX INFO: Multiple debug info for " + ssaVar + ": " + debugInfoSet);
mth.addInfoComment("Multiple debug info for " + ssaVar + ": " + debugInfoSet);
for (RegDebugInfoAttr debugInfo : debugInfoSet) {
applyDebugInfo(mth, ssaVar, debugInfo.getRegType(), debugInfo.getName());
}
@@ -207,7 +206,7 @@ public class DebugInfoApplyVisitor extends AbstractVisitor {
if (names.size() == 1) {
setNameForInsn(phiInsn, names.iterator().next());
} else if (names.size() > 1) {
LOG.warn("Different names in phi insn: {}, use first", names);
mth.addDebugComment("Different variable names in phi insn: " + names + ", use first");
setNameForInsn(phiInsn, names.iterator().next());
}
}
@@ -3,10 +3,6 @@ package jadx.core.dex.visitors.debuginfo;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.ICodeWriter;
import jadx.api.plugins.input.data.IDebugInfo;
import jadx.api.plugins.input.data.ILocalVar;
import jadx.core.dex.attributes.nodes.LocalVarsDebugInfoAttr;
@@ -21,8 +17,6 @@ import jadx.core.dex.visitors.AbstractVisitor;
import jadx.core.dex.visitors.JadxVisitor;
import jadx.core.dex.visitors.blocks.BlockSplitter;
import jadx.core.dex.visitors.ssa.SSATransform;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxException;
@JadxVisitor(
@@ -35,8 +29,6 @@ import jadx.core.utils.exceptions.JadxException;
)
public class DebugInfoAttachVisitor extends AbstractVisitor {
private static final Logger LOG = LoggerFactory.getLogger(DebugInfoAttachVisitor.class);
@Override
public void visit(MethodNode mth) throws JadxException {
try {
@@ -45,9 +37,7 @@ public class DebugInfoAttachVisitor extends AbstractVisitor {
processDebugInfo(mth, debugInfo);
}
} catch (Exception e) {
mth.addComment("JADX WARNING: Error to parse debug info: "
+ ErrorsCounter.formatMsg(mth, e.getMessage())
+ ICodeWriter.NL + Utils.getStackTrace(e));
mth.addWarnComment("Failed to parse debug info", e);
}
}
@@ -129,21 +119,21 @@ public class DebugInfoAttachVisitor extends AbstractVisitor {
try {
ArgType gType = new SignatureParser(sign).consumeType();
ArgType expandedType = mth.root().getTypeUtils().expandTypeVariables(mth, gType);
if (checkSignature(type, expandedType)) {
if (checkSignature(mth, type, expandedType)) {
return expandedType;
}
} catch (Exception e) {
LOG.error("Can't parse signature for local variable: {}", sign, e);
mth.addWarnComment("Can't parse signature for local variable: " + sign, e);
}
return type;
}
private static boolean checkSignature(ArgType type, ArgType gType) {
private static boolean checkSignature(MethodNode mth, ArgType type, ArgType gType) {
boolean apply;
ArgType el = gType.getArrayRootElement();
if (el.isGeneric()) {
if (!type.getArrayRootElement().getObject().equals(el.getObject())) {
LOG.warn("Generic type in debug info not equals: {} != {}", type, gType);
mth.addWarnComment("Generic types in debug info not equals: " + type + " != " + gType);
}
apply = true;
} else {
@@ -179,7 +179,7 @@ public class MarkFinallyVisitor extends AbstractVisitor {
return false;
}
if (!checkSlices(extractInfo)) {
mth.addComment("JADX INFO: finally extract failed");
mth.addWarnComment("Finally extract failed");
return false;
}
@@ -44,7 +44,6 @@ import jadx.core.dex.trycatch.ExcHandlerAttr;
import jadx.core.dex.trycatch.ExceptionHandler;
import jadx.core.dex.trycatch.TryCatchBlockAttr;
import jadx.core.utils.BlockUtils;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.RegionUtils;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxOverflowException;
@@ -839,7 +838,7 @@ public class RegionMaker {
if (!fallThroughCases.isEmpty() && isBadCasesOrder(blocksMap, fallThroughCases)) {
Map<BlockNode, List<Object>> newBlocksMap = reOrderSwitchCases(blocksMap, fallThroughCases);
if (isBadCasesOrder(newBlocksMap, fallThroughCases)) {
mth.addComment("JADX INFO: Can't fix incorrect switch cases order, some code will duplicate");
mth.addWarnComment("Can't fix incorrect switch cases order, some code will duplicate");
fallThroughCases.clear();
} else {
blocksMap = newBlocksMap;
@@ -1019,7 +1018,7 @@ public class RegionMaker {
blocks.add(handlerBlock);
splitters.add(BlockUtils.getTopSplitterForHandler(handlerBlock));
} else {
LOG.debug(ErrorsCounter.formatMsg(mth, "No exception handler block: " + handler));
mth.addDebugComment("No exception handler block: " + handler);
}
}
Set<BlockNode> exits = new HashSet<>();
@@ -1030,7 +1029,7 @@ public class RegionMaker {
}
List<BlockNode> s = splitter.getSuccessors();
if (s.isEmpty()) {
LOG.debug(ErrorsCounter.formatMsg(mth, "No successors for splitter: " + splitter));
mth.addDebugComment("No successors for splitter: " + splitter);
continue;
}
BlockNode ss = s.get(0);
@@ -100,7 +100,7 @@ public class TypeSearch {
for (TypeSearchVarInfo var : updatedVars) {
TypeUpdateResult res = typeUpdate.applyWithWiderIgnSame(mth, var.getVar(), var.getCurrentType());
if (res == TypeUpdateResult.REJECT) {
mth.addComment("JADX DEBUG: Multi-variable search result rejected for " + var);
mth.addDebugComment("Multi-variable search result rejected for " + var);
applySuccess = false;
}
}
@@ -5,12 +5,15 @@ import java.util.List;
import org.jetbrains.annotations.Nullable;
import jadx.api.CodePosition;
import jadx.api.CommentsLevel;
import jadx.api.ICodeWriter;
import jadx.api.plugins.input.data.attributes.JadxAttrType;
import jadx.api.plugins.input.data.attributes.types.SourceFileAttr;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.AttrNode;
import jadx.core.dex.attributes.IAttributeNode;
import jadx.core.dex.attributes.nodes.JadxCommentsAttr;
import jadx.core.dex.attributes.nodes.JadxError;
import jadx.core.dex.attributes.nodes.NotificationAttrNode;
import jadx.core.dex.attributes.nodes.RenameReasonAttr;
import jadx.core.dex.instructions.args.CodeVar;
import jadx.core.dex.instructions.args.RegisterArg;
@@ -20,16 +23,49 @@ import jadx.core.dex.nodes.ICodeNode;
public class CodeGenUtils {
public static void addComments(ICodeWriter code, IAttributeNode node) {
List<String> comments = node.getAll(AType.COMMENTS);
if (!comments.isEmpty()) {
comments.stream().distinct()
.forEach(comment -> code.startLine("/* ").addMultiLine(comment).add(" */"));
}
addCodeComments(code, node);
public static void addErrorsAndComments(ICodeWriter code, NotificationAttrNode node) {
addErrors(code, node);
addComments(code, node);
}
public static void addCodeComments(ICodeWriter code, @Nullable IAttributeNode node) {
public static void addErrors(ICodeWriter code, NotificationAttrNode node) {
if (!node.checkCommentsLevel(CommentsLevel.ERROR)) {
return;
}
List<JadxError> errors = node.getAll(AType.JADX_ERROR);
if (!errors.isEmpty()) {
errors.stream().distinct().sorted().forEach(err -> {
code.startLine("/* JADX ERROR: ").add(err.getError());
Throwable cause = err.getCause();
if (cause != null) {
code.incIndent();
Utils.appendStackTrace(code, cause);
code.decIndent();
}
code.add("*/");
});
}
}
public static void addComments(ICodeWriter code, NotificationAttrNode node) {
JadxCommentsAttr commentsAttr = node.get(AType.JADX_COMMENTS);
if (commentsAttr != null) {
commentsAttr.formatAndFilter(node.getCommentsLevel())
.forEach(comment -> code.startLine("/* ").addMultiLine(comment).add(" */"));
}
addCodeComments(code, node, node);
}
public static void addCodeComments(ICodeWriter code, NotificationAttrNode parent, @Nullable IAttributeNode node) {
if (node == null) {
return;
}
if (parent.checkCommentsLevel(CommentsLevel.USER_ONLY)) {
addCodeComments(code, node);
}
}
private static void addCodeComments(ICodeWriter code, @Nullable IAttributeNode node) {
if (node == null) {
return;
}
@@ -38,7 +74,7 @@ public class CodeGenUtils {
return;
}
if (node instanceof ICodeNode) {
// for classes, fields and methods add on line before node declaration
// for classes, fields and methods add one line before node declaration
code.startLine();
} else {
code.add(' ');
@@ -78,7 +114,10 @@ public class CodeGenUtils {
}
}
public static void addRenamedComment(ICodeWriter code, AttrNode node, String origName) {
public static void addRenamedComment(ICodeWriter code, NotificationAttrNode node, String origName) {
if (!node.checkCommentsLevel(CommentsLevel.INFO)) {
return;
}
code.startLine("/* renamed from: ").add(origName);
RenameReasonAttr renameReasonAttr = node.get(AType.RENAME_REASON);
if (renameReasonAttr != null) {
@@ -89,6 +128,9 @@ public class CodeGenUtils {
}
public static void addSourceFileInfo(ICodeWriter code, ClassNode node) {
if (!node.checkCommentsLevel(CommentsLevel.INFO)) {
return;
}
SourceFileAttr sourceFileAttr = node.get(JadxAttrType.SOURCE_FILE);
if (sourceFileAttr != null) {
String fileName = sourceFileAttr.getFileName();
@@ -102,7 +144,7 @@ public class CodeGenUtils {
}
public static void addInputFileInfo(ICodeWriter code, ClassNode node) {
if (node.getClsData() != null) {
if (node.getClsData() != null && node.checkCommentsLevel(CommentsLevel.INFO)) {
String inputFileName = node.getClsData().getInputFileName();
if (inputFileName != null) {
code.startLine("/* loaded from: ").add(inputFileName).add(" */");
@@ -10,7 +10,6 @@ import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.IAttributeNode;
import jadx.core.dex.attributes.nodes.JadxError;
@@ -31,8 +30,8 @@ public class ErrorsCounter {
return node.root().getErrorsCounter().addError(node, warnMsg, th);
}
public static <N extends IDexNode & IAttributeNode> String warning(N node, String warnMsg) {
return node.root().getErrorsCounter().addWarning(node, warnMsg);
public static <N extends IDexNode & IAttributeNode> void warning(N node, String warnMsg) {
node.root().getErrorsCounter().addWarning(node, warnMsg);
}
public static String formatMsg(IDexNode node, String msg) {
@@ -63,24 +62,14 @@ public class ErrorsCounter {
} else {
LOG.error(msg, e);
}
node.addAttr(AType.JADX_ERROR, new JadxError(error, e));
node.remove(AFlag.INCONSISTENT_CODE);
return msg;
}
private synchronized <N extends IDexNode & IAttributeNode> String addWarning(N node, String warn) {
private synchronized <N extends IDexNode & IAttributeNode> void addWarning(N node, String warn) {
warnNodes.add(node);
warnsCount++;
node.addAttr(AType.JADX_WARN, warn);
if (!node.contains(AType.JADX_ERROR)) {
node.add(AFlag.INCONSISTENT_CODE);
}
String msg = formatMsg(node, warn);
LOG.warn(msg);
return msg;
LOG.warn(formatMsg(node, warn));
}
public void printReport() {
@@ -16,7 +16,6 @@ import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.core.codegen.ClassGen;
import jadx.core.deobf.NameMapper;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.ConstStorage;
@@ -91,7 +90,7 @@ public class AndroidResourcesUtils {
private static ClassNode makeClass(RootNode root, String clsName, ResourceStorage resStorage) {
ClassNode rCls = ClassNode.addSyntheticClass(root, clsName, AccessFlags.PUBLIC | AccessFlags.FINAL);
rCls.addAttr(AType.COMMENTS, "This class is generated by JADX");
rCls.addInfoComment("This class is generated by JADX");
rCls.setState(ProcessState.PROCESS_COMPLETE);
return rCls;
}
@@ -122,7 +121,7 @@ public class AndroidResourcesUtils {
rField.addAttr(new EncodedValue(EncodedType.ENCODED_INT, resource.getId()));
typeCls.getFields().add(rField);
if (rClsExists) {
rField.addAttr(AType.COMMENTS, "added by JADX");
rField.addInfoComment("Added by JADX");
}
}
FieldNode fieldNode = resFieldsMap.get(resource.getId());
@@ -142,7 +141,7 @@ public class AndroidResourcesUtils {
AccessFlags.PUBLIC | AccessFlags.STATIC | AccessFlags.FINAL);
resCls.addInnerClass(newTypeCls);
if (rClsExists) {
newTypeCls.addAttr(AType.COMMENTS, "Added by JADX");
newTypeCls.addInfoComment("Added by JADX");
}
return newTypeCls;
}
@@ -25,6 +25,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.CodePosition;
import jadx.api.CommentsLevel;
import jadx.api.ICodeInfo;
import jadx.api.ICodeWriter;
import jadx.api.JadxArgs;
@@ -33,8 +34,8 @@ import jadx.api.JadxInternalAccess;
import jadx.api.data.annotations.InsnCodeOffset;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.AttrList;
import jadx.core.dex.attributes.IAttributeNode;
import jadx.core.dex.attributes.nodes.JadxCommentsAttr;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
@@ -307,19 +308,13 @@ public abstract class IntegrationTest extends TestUtils {
}
private boolean hasErrors(IAttributeNode node) {
if (node.contains(AFlag.INCONSISTENT_CODE)
|| node.contains(AType.JADX_ERROR)
|| (node.contains(AType.JADX_WARN) && !allowWarnInCode)) {
if (node.contains(AFlag.INCONSISTENT_CODE) || node.contains(AType.JADX_ERROR)) {
return true;
}
if (!allowWarnInCode) {
AttrList<String> commentsAttr = node.get(AType.COMMENTS);
JadxCommentsAttr commentsAttr = node.get(AType.JADX_COMMENTS);
if (commentsAttr != null) {
for (String comment : commentsAttr.getList()) {
if (comment.contains("JADX WARN")) {
return true;
}
}
return commentsAttr.getComments().get(CommentsLevel.WARN) != null;
}
}
return false;
@@ -17,6 +17,6 @@ public class TestEnums10 extends SmaliTest {
.code()
.doesNotContain("Failed to restore enum class")
.containsOne("enum TestEnums10 {")
.countString(4, "/* Fake field");
.countString(4, "Fake field");
}
}
@@ -26,6 +26,7 @@ import org.slf4j.LoggerFactory;
import com.beust.jcommander.Parameter;
import jadx.api.CommentsLevel;
import jadx.api.JadxArgs;
import jadx.cli.JadxCLIArgs;
import jadx.cli.LogHelper;
@@ -551,6 +552,10 @@ public class JadxSettings extends JadxCLIArgs {
this.adbDialogPort = port;
}
public void setCommentsLevel(CommentsLevel level) {
this.commentsLevel = level;
}
private void upgradeSettings(int fromVersion) {
LOG.debug("upgrade settings from version: {} to {}", fromVersion, CURRENT_SETTINGS_VERSION);
if (fromVersion == 0) {
@@ -58,6 +58,7 @@ import com.google.gson.JsonObject;
import say.swing.JFontChooser;
import jadx.api.CommentsLevel;
import jadx.api.JadxArgs;
import jadx.gui.ui.MainWindow;
import jadx.gui.ui.codearea.EditorTheme;
@@ -500,6 +501,13 @@ public class JadxSettingsWindow extends JDialog {
needReload();
});
JComboBox<CommentsLevel> commentsLevel = new JComboBox<>(CommentsLevel.values());
commentsLevel.setSelectedItem(settings.getCommentsLevel());
commentsLevel.addActionListener(e -> {
settings.setCommentsLevel((CommentsLevel) commentsLevel.getSelectedItem());
needReload();
});
SettingsGroup other = new SettingsGroup(NLS.str("preferences.decompile"));
other.addRow(NLS.str("preferences.threads"), threadsCount);
other.addRow(NLS.str("preferences.excludedPackages"), NLS.str("preferences.excludedPackages.tooltip"),
@@ -515,6 +523,7 @@ public class JadxSettingsWindow extends JDialog {
other.addRow(NLS.str("preferences.fsCaseSensitive"), fsCaseSensitive);
other.addRow(NLS.str("preferences.fallback"), fallback);
other.addRow(NLS.str("preferences.skipResourcesDecode"), resourceDecode);
other.addRow(NLS.str("preferences.commentsLevel"), commentsLevel);
return other;
}
@@ -130,6 +130,7 @@ preferences.inlineAnonymous=Anonyme Inline-Klassen
preferences.inlineMethods=Inline-Methoden
preferences.fsCaseSensitive=Dateisystem unterscheidet zwischen Groß/Kleinschreibung
preferences.skipResourcesDecode=Keine Ressourcen dekodieren
#preferences.commentsLevel=Code comments level
preferences.autoSave=Autom. speichern
preferences.threads=Verarbeitungs-Thread-Anzahl
preferences.excludedPackages=Ausgeschlossene Pakete
@@ -130,6 +130,7 @@ preferences.inlineAnonymous=Inline anonymous classes
preferences.inlineMethods=Inline methods
preferences.fsCaseSensitive=File system is case sensitive
preferences.skipResourcesDecode=Don't decode resources
preferences.commentsLevel=Code comments level
preferences.autoSave=Auto save
preferences.threads=Processing threads count
preferences.excludedPackages=Excluded packages
@@ -130,6 +130,7 @@ preferences.replaceConsts=Reemplazar constantes
#preferences.inlineMethods=Inline methods
#preferences.fsCaseSensitive=
preferences.skipResourcesDecode=No descodificar recursos
#preferences.commentsLevel=Code comments level
#preferences.autoSave=
preferences.threads=Número de hilos a procesar
#preferences.excludedPackages=
@@ -130,6 +130,7 @@ preferences.inlineAnonymous=인라인 익명 클래스
#preferences.inlineMethods=Inline methods
preferences.fsCaseSensitive=파일 시스템 대소문자 구별
preferences.skipResourcesDecode=리소스 디코딩 하지 않기
#preferences.commentsLevel=Code comments level
preferences.autoSave=자동 저장
preferences.threads=처리 스레드 수
preferences.excludedPackages=제외할 패키지
@@ -130,6 +130,7 @@ preferences.inlineAnonymous=内联匿名类
#preferences.inlineMethods=Inline methods
preferences.fsCaseSensitive=文件系统区分大小写
preferences.skipResourcesDecode=不反编译资源文件
#preferences.commentsLevel=Code comments level
preferences.autoSave=自动保存
preferences.threads=并行线程数
preferences.excludedPackages=排除的包