[PATCH] scripts/checkpatch: fix false positives from learned types and modifiers
From: Jian Guo
Date: Mon Aug 31 2026 - 06:03:12 EST
checkpatch incrementally learns unknown identifiers as types/modifiers
while scanning a patch: possible() pushes them into @typeListFile and
@modifierListFile and rebuilds the $Type/$Modifier regexes via
build_types(). This heuristic misfires on identifier-heavy code such
as BPF programs declaring kconfig/ksym externs, producing two families
of false positives.
1) POINTER_LOCATION: when an identifier is a strict prefix of a later
identifier (e.g. CONFIG_X86 followed by CONFIG_X86_64), the learned
prefix is treated as a type modifier and the remainder of the
identifier is captured as a "modifier" between the type and the
declared name:
extern void a32_elf_hwcap __ksym;
extern void a32_elf_hwcap2 __ksym;
extern bool CONFIG_X86 __kconfig __weak;
extern bool CONFIG_X86_64 __kconfig __weak;
ERROR: "foo2 bar" should be "foo 2 bar"
ERROR: "foo_64 bar" should be "foo _64 bar"
The match splits the identifier mid-word: both sides of the split are
word characters, which can never happen for a genuine pointer location
problem (the type is always followed by whitespace or a '*'). Add a
(?!\w) after $NonptrType in both POINTER_LOCATION regexes to reject
mid-identifier splits.
2) SPACING: annotate_values() unconditionally marks a learned modifier
as type 'T'. When the token is actually used as a value, e.g.
if (CONFIG_MEMCG_KMEM && CONFIG_ZSWAP) {
the following '&&' is classified as unary ('&&U', meant for the GNU
'&&label' label-address extension) and checkpatch reports:
ERROR: space prohibited after that '&&' (ctx:WxW)
A real type modifier is never followed directly by a binary operator,
so require a plausible follower (identifier, '*', '(', ',', ';', ')',
'[' or end of line), mirroring the DECLARE branch. Anything else now
falls through to the IDENT branch and the token is treated as a value,
which also fixes the same misclassification for binary '-' and friends.
Verified that the false positives above are gone, that genuine
unary-operator errors (!, ~, -, *, &, &&label) and genuine
POINTER_LOCATION errors are still reported, and that the output on 100
recent commits is unchanged.
Signed-off-by: Jian Guo <guojian@xxxxxxxxxx>
---
scripts/checkpatch.pl | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/scripts/checkpatch.pl b/scripts/checkpatch.pl
index 7a846a3ea127..1b3e409269e2 100755
--- a/scripts/checkpatch.pl
+++ b/scripts/checkpatch.pl
@@ -2141,7 +2141,7 @@ sub annotate_values {
print "DECLARE($1)\n" if ($dbg_values > 1);
$type = 'T';
- } elsif ($cur =~ /^($Modifier)\s*/) {
+ } elsif ($cur =~ /^($Modifier)\s*(?=$Ident|[\*\(,;)\[]|\s*$)/) {
print "MODIFIER($1)\n" if ($dbg_values > 1);
$type = 'T';
@@ -4862,7 +4862,7 @@ sub process {
# * goes on variable not on type
# (char*[ const])
- while ($line =~ m{(\($NonptrType(\s*(?:$Modifier\b\s*|\*\s*)+)\))}g) {
+ while ($line =~ m{(\($NonptrType(?!\w)(\s*(?:$Modifier\b\s*|\*\s*)+)\))}g) {
#print "AA<$1>\n";
my ($ident, $from, $to) = ($1, $2, $2);
@@ -4887,7 +4887,7 @@ sub process {
}
}
}
- while ($line =~ m{(\b$NonptrType(\s*(?:$Modifier\b\s*|\*\s*)+)($Ident))}g) {
+ while ($line =~ m{(\b$NonptrType(?!\w)(\s*(?:$Modifier\b\s*|\*\s*)+)($Ident))}g) {
#print "BB<$1>\n";
my ($match, $from, $to, $ident) = ($1, $2, $2, $3);
--
2.50.1 (Apple Git-155)