mirror of
https://github.com/torvalds/linux.git
synced 2026-07-27 09:36:22 +02:00
kconfig: add kconfig-sym-check static checker
Add 'make kconfig-sym-check', a static checker that finds Kconfig symbols referenced in expressions (select, depends on, default, etc.) but never defined via config/menuconfig anywhere in the tree. New dangling symbols are reported as errors (exit 1) unless they are listed in an exclusion file, e.g. KCONFIG_SYM_CHECK_EXCLUDES=sym-check-excludes make kconfig-sym-check The exclusion file lists one symbol per line; blank lines and lines starting with '#' are ignored. The checker also warns about uppercase N/Y/M used as tristate literal values following the same logic as checkpatch. This new static checker is the script used for [1] with a few improvements to avoid some false positives. Link: https://bugzilla.kernel.org/show_bug.cgi?id=216748 [1] Assisted-by: Claude:claude-sonnet-4-6 Signed-off-by: Andrew Jones <andrew.jones@linux.dev> Acked-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com> Acked-by: Randy Dunlap <rdunlap@infradead.org> Tested-by: Randy Dunlap <rdunlap@infradead.org> Tested-by: Julian Braha <julianbraha@gmail.com> Tested-by: Nicolas Schier <nsc@kernel.org> Acked-by: Nicolas Schier <nsc@kernel.org> Link: https://patch.msgid.link/20260527142703.107110-1-andrew.jones@linux.dev Signed-off-by: Nathan Chancellor <nathan@kernel.org>
This commit is contained in:
parent
a48bd961fb
commit
f58316a441
23
Makefile
23
Makefile
|
|
@ -293,6 +293,7 @@ version_h := include/generated/uapi/linux/version.h
|
|||
clean-targets := %clean mrproper cleandocs
|
||||
no-dot-config-targets := $(clean-targets) \
|
||||
cscope gtags TAGS tags help% %docs check% coccicheck \
|
||||
kconfig-sym-check \
|
||||
$(version_h) headers headers_% archheaders archscripts \
|
||||
%asm-generic kernelversion %src-pkg dt_binding_check \
|
||||
outputmakefile rustavailable rustfmt rustfmtcheck \
|
||||
|
|
@ -1800,14 +1801,15 @@ help:
|
|||
echo ' (default: $(INSTALL_HDR_PATH))'; \
|
||||
echo ''
|
||||
@echo 'Static analysers:'
|
||||
@echo ' checkstack - Generate a list of stack hogs and consider all functions'
|
||||
@echo ' with a stack size larger than MINSTACKSIZE (default: 100)'
|
||||
@echo ' versioncheck - Sanity check on version.h usage'
|
||||
@echo ' includecheck - Check for duplicate included header files'
|
||||
@echo ' headerdep - Detect inclusion cycles in headers'
|
||||
@echo ' coccicheck - Check with Coccinelle'
|
||||
@echo ' clang-analyzer - Check with clang static analyzer'
|
||||
@echo ' clang-tidy - Check with clang-tidy'
|
||||
@echo ' checkstack - Generate a list of stack hogs and consider all functions'
|
||||
@echo ' with a stack size larger than MINSTACKSIZE (default: 100)'
|
||||
@echo ' versioncheck - Sanity check on version.h usage'
|
||||
@echo ' includecheck - Check for duplicate included header files'
|
||||
@echo ' headerdep - Detect inclusion cycles in headers'
|
||||
@echo ' coccicheck - Check with Coccinelle'
|
||||
@echo ' kconfig-sym-check - Check for dangling Kconfig symbol references'
|
||||
@echo ' clang-analyzer - Check with clang static analyzer'
|
||||
@echo ' clang-tidy - Check with clang-tidy'
|
||||
@echo ''
|
||||
@echo 'Tools:'
|
||||
@echo ' nsdeps - Generate missing symbol namespace dependencies'
|
||||
|
|
@ -2227,7 +2229,7 @@ endif
|
|||
# Scripts to check various things for consistency
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
PHONY += includecheck versioncheck coccicheck
|
||||
PHONY += includecheck versioncheck coccicheck kconfig-sym-check
|
||||
|
||||
includecheck:
|
||||
find $(srctree)/* $(RCS_FIND_IGNORE) \
|
||||
|
|
@ -2242,6 +2244,9 @@ versioncheck:
|
|||
coccicheck:
|
||||
$(Q)$(BASH) $(srctree)/scripts/$@
|
||||
|
||||
kconfig-sym-check:
|
||||
$(Q)$(PERL) $(srctree)/scripts/kconfig/kconfig-sym-check.pl $(srctree) $(KCONFIG_SYM_CHECK_EXCLUDES)
|
||||
|
||||
PHONY += checkstack kernelrelease kernelversion image_name
|
||||
|
||||
# UML needs a little special treatment here. It wants to use the host
|
||||
|
|
|
|||
132
scripts/kconfig/kconfig-sym-check.pl
Executable file
132
scripts/kconfig/kconfig-sym-check.pl
Executable file
|
|
@ -0,0 +1,132 @@
|
|||
#!/usr/bin/env perl
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
use warnings;
|
||||
use strict;
|
||||
|
||||
my $srctree = shift @ARGV;
|
||||
unless (defined $srctree) {
|
||||
$srctree = `git rev-parse --show-toplevel 2>/dev/null`;
|
||||
chomp $srctree;
|
||||
my $msg = "Usage: $0 <srctree> [excludes file]\n";
|
||||
$msg .= "Please provide <srctree>.";
|
||||
$msg .= " Is it '$srctree'?" if $srctree;
|
||||
$msg .= "\n";
|
||||
die $msg;
|
||||
}
|
||||
my $kconfig_sym_check_excludes = defined $ARGV[0] ? $ARGV[0] : undef;
|
||||
|
||||
sub indent_depth {
|
||||
my ($ws) = @_;
|
||||
my $col = 0;
|
||||
for my $c (split //, $ws) {
|
||||
$col = $c eq "\t" ? int($col / 8) * 8 + 8 : $col + 1;
|
||||
}
|
||||
return $col;
|
||||
}
|
||||
|
||||
my @files = `git -C \Q$srctree\E ls-files '*Kconfig*' 2>/dev/null`;
|
||||
if (@files) {
|
||||
chomp @files;
|
||||
@files = map { "$srctree/$_" } @files;
|
||||
} else {
|
||||
@files = `find \Q$srctree\E -name '*Kconfig*'`;
|
||||
chomp @files;
|
||||
}
|
||||
|
||||
@files = grep { !m{/scripts/kconfig/tests/} } @files;
|
||||
|
||||
my %configs = ();
|
||||
my %refs = ();
|
||||
|
||||
foreach my $file (@files) {
|
||||
open F, $file or die "Cannot open $file: $!";
|
||||
|
||||
my $help = 0;
|
||||
my $help_level;
|
||||
my $level;
|
||||
|
||||
while (<F>) {
|
||||
chomp;
|
||||
|
||||
while (/\\\s*$/) {
|
||||
s/\\\s*$/ /;
|
||||
my $cont = <F> // last;
|
||||
chomp $cont;
|
||||
$_ .= $cont;
|
||||
}
|
||||
|
||||
next if /^\s*$/;
|
||||
next if /^\s*#/;
|
||||
|
||||
/^(\s*)/;
|
||||
$level = indent_depth($1);
|
||||
|
||||
if ($help && $level < $help_level) {
|
||||
$help = 0;
|
||||
}
|
||||
|
||||
next if ($help);
|
||||
|
||||
if (/^\s*(help|\-\-\-help\-\-\-)$/) {
|
||||
$help = 1;
|
||||
my $next;
|
||||
while (defined($next = <F>)) {
|
||||
last unless $next =~ /^\s*(?:#.*)?$/;
|
||||
}
|
||||
last unless defined $next;
|
||||
$next =~ /^(\s*)/;
|
||||
if (indent_depth($1) >= $level) {
|
||||
$help_level = indent_depth($1);
|
||||
} else {
|
||||
$help = 0;
|
||||
}
|
||||
$_ = $next;
|
||||
redo;
|
||||
}
|
||||
|
||||
if (/^\s*(config|menuconfig)\s+([a-zA-Z0-9_]+)\s*(#.*)?$/) {
|
||||
$configs{$2}++;
|
||||
next;
|
||||
}
|
||||
|
||||
if (/^\s*(default|def_bool|def_tristate|select|depends\s+on|imply|visible\s+if|range|if|bool|tristate|int|hex|string|prompt)\s+(.+)\s*$/) {
|
||||
my $s = $2;
|
||||
$s =~ s/"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'//g;
|
||||
$s =~ s/#.*//;
|
||||
$s =~ s/\$\((?:[^()]*|\((?:[^()]*|\([^()]*\))*\))*\)//g;
|
||||
$s =~ s/%%[^%]*%%//g;
|
||||
my @syms = split /[^a-zA-Z0-9_]+/, $s;
|
||||
map {
|
||||
$refs{$_}++ if (/[a-zA-Z]/ && $_ ne "if" && $_ ne "y" && $_ ne "n" && $_ ne "m" && !/^0[xX][0-9a-fA-F]+$/);
|
||||
} @syms
|
||||
}
|
||||
}
|
||||
|
||||
close F;
|
||||
}
|
||||
|
||||
my %known_syms = ();
|
||||
if (defined $kconfig_sym_check_excludes) {
|
||||
my $file = $kconfig_sym_check_excludes;
|
||||
open(F, "<", $file) or die "Cannot open $file: $!";
|
||||
while (<F>) {
|
||||
chomp;
|
||||
next if /^\s*$/;
|
||||
next if /^\s*#/;
|
||||
$known_syms{$1}++ if (/^\s*([a-zA-Z0-9_]+)\s*(#.*)?$/);
|
||||
}
|
||||
}
|
||||
|
||||
my $ret = 0;
|
||||
foreach my $k (sort keys %refs) {
|
||||
next if (exists $configs{$k} || exists $known_syms{$k});
|
||||
|
||||
print "$k";
|
||||
print " - warning: '$k' is probably not what you want; Kconfig tristate literals are always lowercase ('n', 'y', 'm')" if ($k eq "N" || $k eq "Y" || $k eq "M");
|
||||
print "\n";
|
||||
|
||||
$ret = 1;
|
||||
}
|
||||
|
||||
exit $ret;
|
||||
Loading…
Reference in New Issue
Block a user