[v2] checkpatch: check for missing Fixes tags

Message ID ce2d9aa7-b1e6-402e-8471-ad52a321c008@moroto.mountain
State New
Headers
Series [v2] checkpatch: check for missing Fixes tags |

Commit Message

Dan Carpenter June 20, 2023, 11:45 a.m. UTC
  This check looks for common words that probably indicate a patch
is a fix.  For now the regex is:

	(?:BUG: KASAN|Call Trace:|stable\@|syzkaller)

Why are stable patches encouraged to have a fixes tag?  Some people mark
their stable patches as "# 5.10" etc.  This is useful but a Fixes tag is
still a good idea.  For example, the Fixes tag helps in review.  It
helps people to not cherry-pick buggy patches without also
cherry-picking the fix.

Also if a bug affects the 5.7 kernel some people will round it up to
5.10+ because 5.7 is not supported on kernel.org.  It's possible the Bad
Binder bug was caused by this sort of gap where companies outside of
kernel.org are supporting different kernels from kernel.org.

Should it be counted as a Fix when a patch just silences harmless
WARN_ON() stack trace.  Yes.  Definitely.

Is silencing compiler warnings a fix?  It seems unfair to the original
authors, but we use -Werror now, and warnings break the build so let's
just add Fixes tags.  I tell people that silencing static checker
warnings is not a fix but the rules on this vary by subsystem.

Is fixing a minor LTP issue (Linux Test Project) a fix?  Probably?  It's
hard to know what to do if the LTP test has technically always been
broken.

One clear false positive from this check is when someone updated their
debug output and included before and after Call Traces.  Or when crashes
are introduced deliberately for testing.  In those cases, you should
just ignore checkpatch.

Signed-off-by: Dan Carpenter <dan.carpenter@linaro.org>
---
v2: I fixed the formatting issues Joe pointed out.  I also silenced the
warning if the commit was a Revert because revert patches already
include the hash.

I tested adding Closes: and regression to the regexp, but in the end I
left them out.  They both find some missing tags but they end up adding
false positives.  The problem with "regression" is that people say "this
doesn't cause a regression".  Closes: finds a lot of harmless static
checker warnings.

We're, no doubt, going to fine tune the regex in the future.  I ran this
on the most recent 2000 patches and the results are good.  I'm also
thinking about how to create a Fixes-tag-bot which searches lore for
missing tags.

 scripts/checkpatch.pl | 24 ++++++++++++++++++++++++
 1 file changed, 24 insertions(+)
  

Comments

Kees Cook June 20, 2023, 8:08 p.m. UTC | #1
On Tue, Jun 20, 2023 at 02:45:12PM +0300, Dan Carpenter wrote:
> This check looks for common words that probably indicate a patch
> is a fix.  For now the regex is:
> 
> 	(?:BUG: KASAN|Call Trace:|stable\@|syzkaller)

I thought the bare word "syzkaller" was going to match too much, but
looking through commit logs, it seems fine. If it does end up too noisy,
perhaps "@syzkaller"?

For "BUG: KASAN", though, I think we need to make this more general to
catch KCSAN, KMSAN, UBSAN, etc:

	(?:(?:BUG: K.|UB)SAN: |Call Trace:|stable\@|syzkaller)

UBSAN does not prefix itself with "BUG: " like the others:

$ git grep 'pr_err(".*SAN:'
kernel/kcsan/report.c:          pr_err("BUG: KCSAN: %s in %ps / %ps\n",
kernel/kcsan/report.c:          pr_err("BUG: KCSAN: %s in %pS\n", get_bug_type(ai->access_type),
lib/ubsan.c:    pr_err("UBSAN: %s in %s:%d:%d\n", reason, loc->file_name,
mm/kasan/report.c:      pr_err("BUG: KASAN: %s in %pS\n", info->bug_type, (void *)info->ip);
mm/kasan/report.c:      pr_err("BUG: KASAN: invalid-access\n");
mm/kmsan/report.c:      pr_err("BUG: KMSAN: %s in %pSb\n", bug_type,

But, yes, please, I love this idea. :)

-Kees
  

Patch

diff --git a/scripts/checkpatch.pl b/scripts/checkpatch.pl
index 7bfa4d39d17f..e059df623dea 100755
--- a/scripts/checkpatch.pl
+++ b/scripts/checkpatch.pl
@@ -28,6 +28,7 @@  my %verbose_messages = ();
 my %verbose_emitted = ();
 my $tree = 1;
 my $chk_signoff = 1;
+my $chk_fixes_tag = 1;
 my $chk_patch = 1;
 my $tst_only;
 my $emacs = 0;
@@ -86,6 +87,7 @@  Options:
   -v, --verbose              verbose mode
   --no-tree                  run without a kernel tree
   --no-signoff               do not check for 'Signed-off-by' line
+  --no-fixes-tag             do not check for 'Fixes:' tag
   --patch                    treat FILE as patchfile (default)
   --emacs                    emacs compile window format
   --terse                    one line per report
@@ -293,6 +295,7 @@  GetOptions(
 	'v|verbose!'	=> \$verbose,
 	'tree!'		=> \$tree,
 	'signoff!'	=> \$chk_signoff,
+	'fixes-tag!'	=> \$chk_fixes_tag,
 	'patch!'	=> \$chk_patch,
 	'emacs!'	=> \$emacs,
 	'terse!'	=> \$terse,
@@ -1254,6 +1257,7 @@  sub git_commit_info {
 }
 
 $chk_signoff = 0 if ($file);
+$chk_fixes_tag = 0 if ($file);
 
 my @rawlines = ();
 my @lines = ();
@@ -2633,6 +2637,9 @@  sub process {
 
 	our $clean = 1;
 	my $signoff = 0;
+	my $fixes_tag = 0;
+	my $is_revert = 0;
+	my $needs_fixes_tag = 0;
 	my $author = '';
 	my $authorsignoff = 0;
 	my $author_sob = '';
@@ -3186,6 +3193,16 @@  sub process {
 			}
 		}
 
+# These indicate a bug fix
+		if (!$in_header_lines && !$is_patch &&
+			$line =~ /^This reverts commit/) {
+			$is_revert = 1;
+		}
+
+		if (!$in_header_lines && !$is_patch &&
+			$line =~ /\b(?:BUG: KASAN|Call Trace:|stable\@|syzkaller)/) {
+			$needs_fixes_tag = 1;
+		}
 
 # Check Fixes: styles is correct
 		if (!$in_header_lines &&
@@ -3198,6 +3215,7 @@  sub process {
 			my $id_length = 1;
 			my $id_case = 1;
 			my $title_has_quotes = 0;
+			$fixes_tag = 1;
 
 			if ($line =~ /(\s*fixes:?)\s+([0-9a-f]{5,})\s+($balanced_parens)/i) {
 				my $tag = $1;
@@ -7636,6 +7654,12 @@  sub process {
 		ERROR("NOT_UNIFIED_DIFF",
 		      "Does not appear to be a unified-diff format patch\n");
 	}
+	if ($is_patch && $has_commit_log && $chk_fixes_tag) {
+		if ($needs_fixes_tag && !$is_revert && !$fixes_tag) {
+			WARN("MISSING_FIXES_TAG",
+			     "This looks like a fix but there is no Fixes: tag\n");
+		}
+	}
 	if ($is_patch && $has_commit_log && $chk_signoff) {
 		if ($signoff == 0) {
 			ERROR("MISSING_SIGN_OFF",