diff --git a/README.md b/README.md index 370654f382cb38ace9235947dd282640d395ccbb..285337ada5b4d5496703e9d337d6b1f9d0039c48 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/jadx-cli/src/main/java/jadx/cli/JCommanderWrapper.java b/jadx-cli/src/main/java/jadx/cli/JCommanderWrapper.java index 00666a02a631387b803696f48b812cc2820f8aaf..1b2d19defceaa95b8f4a17aef1cbcf10168d16fc 100644 --- a/jadx-cli/src/main/java/jadx/cli/JCommanderWrapper.java +++ b/jadx-cli/src/main/java/jadx/cli/JCommanderWrapper.java @@ -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 { 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) { diff --git a/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java b/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java index 0a06170be101fcf283f67159bbc037118513cefc..ae4f2a5950b50fb93a0e4beb5d86d404684bda66 100644 --- a/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java +++ b/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java @@ -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> { private final String paramName; @@ -378,9 +391,22 @@ public class JadxCLIArgs { } } + public static class CommentsLevelConverter implements IStringConverter { + @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(", ")); } } diff --git a/jadx-cli/src/test/java/jadx/cli/RenameConverterTest.java b/jadx-cli/src/test/java/jadx/cli/RenameConverterTest.java index b20d7f00d5cea05d19c7b2b609ceeac045babcb2..e863d9d48e17dc2dda493717b98aa56a659fd86e 100644 --- a/jadx-cli/src/test/java/jadx/cli/RenameConverterTest.java +++ b/jadx-cli/src/test/java/jadx/cli/RenameConverterTest.java @@ -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()); } } diff --git a/jadx-core/src/main/java/jadx/api/CommentsLevel.java b/jadx-core/src/main/java/jadx/api/CommentsLevel.java new file mode 100644 index 0000000000000000000000000000000000000000..724ace385e3040b2436fce1cd2a728bb52d361bb --- /dev/null +++ b/jadx-core/src/main/java/jadx/api/CommentsLevel.java @@ -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(); + } +} diff --git a/jadx-core/src/main/java/jadx/api/JadxArgs.java b/jadx-core/src/main/java/jadx/api/JadxArgs.java index 0c01c0465be6b34ad925a33d28b8492e38f2ec81..03fc58817948f8325a70b6fe1a07ae4606dfad1c 100644 --- a/jadx-core/src/main/java/jadx/api/JadxArgs.java +++ b/jadx-core/src/main/java/jadx/api/JadxArgs.java @@ -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() + '}'; diff --git a/jadx-core/src/main/java/jadx/api/JadxDecompiler.java b/jadx-core/src/main/java/jadx/api/JadxDecompiler.java index 5acaa697cb19542ae4e6ae4663c6cbee16db43fb..e1dc1a200292e723ddc00cec5368e7a25d43ed73 100644 --- a/jadx-core/src/main/java/jadx/api/JadxDecompiler.java +++ b/jadx-core/src/main/java/jadx/api/JadxDecompiler.java @@ -51,6 +51,7 @@ import jadx.core.xmlgen.ResourcesSaver; * *
  * 
+ *
  * 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;
  *
  * 
  * 
+ *
  *  for(JavaClass cls : jadx.getClasses()) {
  *      System.out.println(cls.getCode());
  *  }
diff --git a/jadx-core/src/main/java/jadx/core/Jadx.java b/jadx-core/src/main/java/jadx/core/Jadx.java
index 16e7ae9fdf8204ebd6af2dc64a882cfe1f7f74d4..9231c3f3e1e54e9dc5a0331aff23ee908e53117a 100644
--- a/jadx-core/src/main/java/jadx/core/Jadx.java
+++ b/jadx-core/src/main/java/jadx/core/Jadx.java
@@ -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());
 
diff --git a/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java b/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java
index d88c08538fb79bef80b7c95eb00a670d6c7991a1..f0bae6932242059c21289cc3feb22db9c8d9e231 100644
--- a/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java
+++ b/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java
@@ -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 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 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());
 		}
 	}
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 5f89b0673fae9b093bfe3de9de762e554a000302..589c6d5e19e3f9db3505e9f75c9c131d21be489b 100644
--- a/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java
+++ b/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java
@@ -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();
diff --git a/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java b/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java
index dc33bd17ad30397944982498d4b420b9c29977e5..3233c7371e920bc2d76748773bf14f6bf51fe51f 100644
--- a/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java
+++ b/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java
@@ -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);
diff --git a/jadx-core/src/main/java/jadx/core/codegen/RegionGen.java b/jadx-core/src/main/java/jadx/core/codegen/RegionGen.java
index 9fb3e3f5d72bdfc3d002ad5cba628f3d92a734c9..a8dd31951d3776840aa0251f3146a81420659795 100644
--- a/jadx-core/src/main/java/jadx/core/codegen/RegionGen.java
+++ b/jadx-core/src/main/java/jadx/core/codegen/RegionGen.java
@@ -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);
 	}
diff --git a/jadx-core/src/main/java/jadx/core/codegen/json/JsonCodeGen.java b/jadx-core/src/main/java/jadx/core/codegen/json/JsonCodeGen.java
index b8cc75c91f585c160e4df1d79a6e6bbecaf81237..ad7148dd1b43c2699b1bfd4b38e06472cb42bd3b 100644
--- a/jadx-core/src/main/java/jadx/core/codegen/json/JsonCodeGen.java
+++ b/jadx-core/src/main/java/jadx/core/codegen/json/JsonCodeGen.java
@@ -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());
 
diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/AType.java b/jadx-core/src/main/java/jadx/core/dex/attributes/AType.java
index a8264ed66d7d04449ddf069da9236745c98c03ce..9da221d1a8ca96ce072fffe49a8fbd8fe4bf3d05 100644
--- a/jadx-core/src/main/java/jadx/core/dex/attributes/AType.java
+++ b/jadx-core/src/main/java/jadx/core/dex/attributes/AType.java
@@ -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 implements IJadxAttrType {
 	public static final AType RENAME_REASON = new AType<>();
 
 	// class, method
-	public static final AType> JADX_ERROR = new AType<>(); // code failed to decompile completely
-	public static final AType> JADX_WARN = new AType<>(); // mark code as inconsistent (code can be viewed)
-	public static final AType> COMMENTS = new AType<>(); // any additional info about decompilation
+	public static final AType> JADX_ERROR = new AType<>(); // code failed to decompile
+	public static final AType JADX_COMMENTS = new AType<>(); // additional info about decompilation
 
 	// class
 	public static final AType ENUM_CLASS = new AType<>();
diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/JadxCommentsAttr.java b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/JadxCommentsAttr.java
new file mode 100644
index 0000000000000000000000000000000000000000..29f2ef8c1772ef50ecd250c984712c32cda88eaf
--- /dev/null
+++ b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/JadxCommentsAttr.java
@@ -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> comments = new EnumMap<>(CommentsLevel.class);
+
+	public void add(CommentsLevel level, String comment) {
+		comments.computeIfAbsent(level, (l) -> new ArrayList<>()).add(comment);
+	}
+
+	public List 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> getComments() {
+		return comments;
+	}
+
+	@Override
+	public IJadxAttrType getAttrType() {
+		return AType.JADX_COMMENTS;
+	}
+}
diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/MethodInlineAttr.java b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/MethodInlineAttr.java
index 89fe110223e23e03dd067d17ec6a2ad87878a2bc..fdda93a72e41e45b53a029224bc1e35b8a034691 100644
--- a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/MethodInlineAttr.java
+++ b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/MethodInlineAttr.java
@@ -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);
 		}
diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/NotificationAttrNode.java b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/NotificationAttrNode.java
index e8d85489966f49c0c9d19ee63bc80e67e327785e..60a2cd6d9162e76b1118370da981fe7c8099b992 100644
--- a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/NotificationAttrNode.java
+++ b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/NotificationAttrNode.java
@@ -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;
 	}
 }
diff --git a/jadx-core/src/main/java/jadx/core/dex/info/AccessInfo.java b/jadx-core/src/main/java/jadx/core/dex/info/AccessInfo.java
index f88f5d12bf160a67bd896deb56003e8ede3707f0..4a7eb8b0cea75ae091b684f9a8ddcaeccb25ef1a 100644
--- a/jadx-core/src/main/java/jadx/core/dex/info/AccessInfo.java
+++ b/jadx-core/src/main/java/jadx/core/dex/info/AccessInfo.java
@@ -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) + ')';
 	}
 }
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 e2fde72f930c04060a9edf8a0bf0f663fb79644e..074a23700a4a80fd6942886501a6b130c5e4cd18 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
@@ -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;
diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java
index f1d4c3e97e7814cdd3f2a792689951537b512d07..8232dfd9d36c2ee250aeaffdc1495775f59ec98d 100644
--- a/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java
+++ b/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java
@@ -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');
 					});
 				});
 	}
diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/AttachCommentsVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/AttachCommentsVisitor.java
index fc7fde7aa04fe12733d4a2b5d989b71c42771452..6780d36a7c85272c2664286d6ce9df210e7d9aeb 100644
--- a/jadx-core/src/main/java/jadx/core/dex/visitors/AttachCommentsVisitor.java
+++ b/jadx-core/src/main/java/jadx/core/dex/visitors/AttachCommentsVisitor.java
@@ -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
 		}
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 b419e57e0cdec66eea4cd79473edcd28e6b02a80..ba34dfb6a3761dabbf310147d832cd2939e0d1a6 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
@@ -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);
 			}
diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/DotGraphVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/DotGraphVisitor.java
index 0637301e1b029053209e5aac7f6b1452da47c0aa..eb627d774e99af8c965bdcc5e054df364d26ee2d 100644
--- a/jadx-core/src/main/java/jadx/core/dex/visitors/DotGraphVisitor.java
+++ b/jadx-core/src/main/java/jadx/core/dex/visitors/DotGraphVisitor.java
@@ -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()) + ") "));
diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/EnumVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/EnumVisitor.java
index d63862c4b89d7497b35d49c86f614592838d74f8..2fdd998e55b29768b5994d3e4650d7bf15903a0b 100644
--- a/jadx-core/src/main/java/jadx/core/dex/visitors/EnumVisitor.java
+++ b/jadx-core/src/main/java/jadx/core/dex/visitors/EnumVisitor.java
@@ -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;
 	}
 
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 8ccb4b7e20043dba7ba2e351cf300c83476275ba..e7c6dde7a069ed3acb696ea020e5eedabc27e0b6 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,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());
 		}
 	}
 
diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/MarkMethodsForInline.java b/jadx-core/src/main/java/jadx/core/dex/visitors/MarkMethodsForInline.java
index fadffd99b4a599e4d9c87a101b6b0a3346a38dd9..3cfcc3402fee02a01d91dd37c0222ec085849d19 100644
--- a/jadx-core/src/main/java/jadx/core/dex/visitors/MarkMethodsForInline.java
+++ b/jadx-core/src/main/java/jadx/core/dex/visitors/MarkMethodsForInline.java
@@ -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;
 	}
diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/MethodInvokeVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/MethodInvokeVisitor.java
index 744d092cf6168a8fec5beef843dadde83f2d7a1f..007b48bc4918ee81cc7a8d0b20be065f8a8b4627 100644
--- a/jadx-core/src/main/java/jadx/core/dex/visitors/MethodInvokeVisitor.java
+++ b/jadx-core/src/main/java/jadx/core/dex/visitors/MethodInvokeVisitor.java
@@ -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:"
diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/OverrideMethodVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/OverrideMethodVisitor.java
index 3cb05be834ac9a58d4a523dcfac3385de92e00dc..db257cdb84451171e0f9834965212800d80176ef 100644
--- a/jadx-core/src/main/java/jadx/core/dex/visitors/OverrideMethodVisitor.java
+++ b/jadx-core/src/main/java/jadx/core/dex/visitors/OverrideMethodVisitor.java
@@ -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");
 		}
 	}
 
diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/PrepareForCodeGen.java b/jadx-core/src/main/java/jadx/core/dex/visitors/PrepareForCodeGen.java
index e39f7320f85c1885a05423c6ac7adb1ba8db94af..2a7bf7d5aacb29825007faaa2279dfecdd56dda8 100644
--- a/jadx-core/src/main/java/jadx/core/dex/visitors/PrepareForCodeGen.java
+++ b/jadx-core/src/main/java/jadx/core/dex/visitors/PrepareForCodeGen.java
@@ -231,11 +231,11 @@ public class PrepareForCodeGen extends AbstractVisitor {
 					Set 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)");
 					}
 				}
 			}
diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoApplyVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoApplyVisitor.java
index 4e65a90dac00ae38bf5f877fc5cbcd68486db518..d96a68b10e6707640e3381ea1330cba69a1459bf 100644
--- a/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoApplyVisitor.java
+++ b/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoApplyVisitor.java
@@ -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());
 				}
 			}
diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoAttachVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoAttachVisitor.java
index f211fa9ffe3cade56728fd04f35942fed95b6a66..11e20697c73b23366bdd95072f7a0564f537ed05 100644
--- a/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoAttachVisitor.java
+++ b/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoAttachVisitor.java
@@ -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 {
diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/finaly/MarkFinallyVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/finaly/MarkFinallyVisitor.java
index a3cd46e6c0f955bdeb573cf4f475c5b1dc93e34a..1f280e15a9de1408e82090455c56b682f63df81d 100644
--- a/jadx-core/src/main/java/jadx/core/dex/visitors/finaly/MarkFinallyVisitor.java
+++ b/jadx-core/src/main/java/jadx/core/dex/visitors/finaly/MarkFinallyVisitor.java
@@ -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;
 		}
 
diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/RegionMaker.java b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/RegionMaker.java
index 47897e3ae7c0d2f17f781b0b3a08f61bdccbb7f2..2a715f28bc56df3de669a50109ab0863947effd7 100644
--- a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/RegionMaker.java
+++ b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/RegionMaker.java
@@ -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> 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 exits = new HashSet<>();
@@ -1030,7 +1029,7 @@ public class RegionMaker {
 					}
 					List 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);
diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeSearch.java b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeSearch.java
index 3573695187f16df1ad6e1fe41282d76e93b59021..d2576e97bd86d567f571f33d36308f2269229802 100644
--- a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeSearch.java
+++ b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeSearch.java
@@ -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;
 			}
 		}
diff --git a/jadx-core/src/main/java/jadx/core/utils/CodeGenUtils.java b/jadx-core/src/main/java/jadx/core/utils/CodeGenUtils.java
index 898462587c73149a02d4d83e15086057a2f88d93..dabf7560e805fb3fd74079134477ecc309887c06 100644
--- a/jadx-core/src/main/java/jadx/core/utils/CodeGenUtils.java
+++ b/jadx-core/src/main/java/jadx/core/utils/CodeGenUtils.java
@@ -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 comments = node.getAll(AType.COMMENTS);
-		if (!comments.isEmpty()) {
-			comments.stream().distinct()
+	public static void addErrorsAndComments(ICodeWriter code, NotificationAttrNode node) {
+		addErrors(code, node);
+		addComments(code, node);
+	}
+
+	public static void addErrors(ICodeWriter code, NotificationAttrNode node) {
+		if (!node.checkCommentsLevel(CommentsLevel.ERROR)) {
+			return;
+		}
+		List 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);
+		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);
+		}
 	}
 
-	public static void addCodeComments(ICodeWriter code, @Nullable IAttributeNode 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(" */");
diff --git a/jadx-core/src/main/java/jadx/core/utils/ErrorsCounter.java b/jadx-core/src/main/java/jadx/core/utils/ErrorsCounter.java
index f456c3abe2bf636625e81885bc60b75200f79162..8d53ce04f634670be580a9a3ec6f4f681db9a90d 100644
--- a/jadx-core/src/main/java/jadx/core/utils/ErrorsCounter.java
+++ b/jadx-core/src/main/java/jadx/core/utils/ErrorsCounter.java
@@ -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  String warning(N node, String warnMsg) {
-		return node.root().getErrorsCounter().addWarning(node, warnMsg);
+	public static  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  String addWarning(N node, String warn) {
+	private synchronized  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() {
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 997771451ff56d074061714dfb4c5dea0fbb094a..96a51d3846ae94da225d8b0d5ef1f61a45f0571a 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
@@ -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;
 	}
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 8c2de54d2e54202423f6d54717a873dede8046e2..b045c987592de579574a3ab03fad8db61fa7210f 100644
--- a/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java
+++ b/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java
@@ -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 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;
diff --git a/jadx-core/src/test/java/jadx/tests/integration/enums/TestEnums10.java b/jadx-core/src/test/java/jadx/tests/integration/enums/TestEnums10.java
index 010802925cef9bfda9114686c8dcfaf0b0dbde47..a9af4173ebb64df5d3e0adcc23bb388483d3918b 100644
--- a/jadx-core/src/test/java/jadx/tests/integration/enums/TestEnums10.java
+++ b/jadx-core/src/test/java/jadx/tests/integration/enums/TestEnums10.java
@@ -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");
 	}
 }
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 917f4bff0635b83cf9ab83bfacffca5895eb3b08..a04e78f253eef3b07dd400c8878ae39cfddef5c2 100644
--- a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java
+++ b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java
@@ -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) {
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 da46f9bbbd38d4dfc5b700cc6970576ed28423cd..0dbc9c4af70936f04d70e1220d7fe9c965cc247f 100644
--- a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java
+++ b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java
@@ -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 = 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;
 	}
 
diff --git a/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties b/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties
index a8fe1700eb93c6f3de3e5e6ba2d11e57654ea086..76145b96b8d1ccf2fb8669032411902165191e55 100644
--- a/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties
+++ b/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties
@@ -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
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 9e5d7bf03de794cb5fb86e76931620e6a5554f6d..53b8b16d1a11136727b1b01c450489d2bf83e33a 100644
--- a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties
+++ b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties
@@ -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
diff --git a/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties b/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties
index cff20d2fd568c1346a2565b016d92fc0bb41bb86..e6df6293fd73afa02b754e5c4ec39abeb484dd84 100644
--- a/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties
+++ b/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties
@@ -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=
diff --git a/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties b/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties
index ee75171852f406a46c8a9332550d6ede58ee40cf..17ff585613b6d94146f1d5a98c977fee48f01eb4 100644
--- a/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties
+++ b/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties
@@ -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=제외할 패키지
diff --git a/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties b/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties
index 2bfd7ad42cc443a2c6755cc4b1ef6cad65f1df8d..8fc6a5307d8f4c2e042fe0767584f51275c6b5c4 100644
--- a/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties
+++ b/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties
@@ -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=排除的包