Re: [PATCH] checkpatch: Add test for possible misuse of IS_ENABLED() without CONFIG_
From: Joe Perches
Date: Fri Jun 05 2020 - 21:59:14 EST
On Fri, 2020-06-05 at 17:32 -0700, Andrew Morton wrote:
> On Fri, 05 Jun 2020 11:24:43 -0700 Joe Perches <joe@xxxxxxxxxxx> wrote:
>
> > IS_ENABLED is almost always used with CONFIG_<FOO> defines.
> >
> > Add a test to verify that the #define being tested starts with CONFIG_.
>
> Yay.
>
> I wonder if there's a simple way of testing whether the CONFIG_ thing
> can *ever* be enabled. So detect if someone does
>
> if (IS_ENABLED(CONFIG_BLOCKK))
No, not really. There's no simple way to do that.
It's doable, but it's not at all simple.
I think it would require something similar to the
checkpatch seed_camelcase_includes function to look
for all current config symbols and verify whatever
CONFIG_<DEFINE> against that list.
$ git grep -P -oh "^\s*config\s+\w+" -- '*/Kconfig*' | \
sed -r -e 's/^\s+//' -e 's/\s+/ /g' | \
sort | uniq -cym
Right now that takes ~1.5 seconds with my laptop
against an uncached git tree, and ~0.25 seconds cached.
Without a git tree it takes 20+ seconds.
Anyway, maybe this.
It only does the time consuming lookup when
it finds a IS_ENABLED line.
---
scripts/checkpatch.pl | 101 ++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 101 insertions(+)
diff --git a/scripts/checkpatch.pl b/scripts/checkpatch.pl
index 5f00df2c3f59..aabb01cf1e6c 100755
--- a/scripts/checkpatch.pl
+++ b/scripts/checkpatch.pl
@@ -47,6 +47,7 @@ my $gitroot = $ENV{'GIT_DIR'};
$gitroot = ".git" if !defined($gitroot);
my %debug;
my %camelcase = ();
+my %Kconfig_syms = ();
my %use_type = ();
my @use = ();
my %ignore_type = ();
@@ -911,6 +912,90 @@ sub is_SPDX_License_valid {
return 1;
}
+sub seed_Kconfig_file {
+ my ($file) = @_;
+
+ return if (!(-f $file));
+
+ local $/;
+
+ open(my $Kconfig_file, '<', "$file")
+ or warn "$P: Can't read '$file' $!\n";
+ my $text = <$Kconfig_file>;
+ close($Kconfig_file);
+
+ my @lines = split('\n', $text);
+
+ foreach my $line (@lines) {
+ next if ($line !~ /^\s*config\s+(\w+)/);
+ $Kconfig_syms{$1} = 1;
+ }
+}
+
+my $Kconfig_symbols_seeded = 0;
+sub seed_Kconfig_symbols {
+ return if ($Kconfig_symbols_seeded);
+
+ my $files;
+ my @Kconfig_files = ();
+ my $Kconfig_syms_cache = "";
+
+ $Kconfig_symbols_seeded = 1;
+
+ if (-e "$gitroot") {
+ my $git_last_include_commit = `${git_command} log --no-merges --pretty=format:"%h%n" -1 -- include`;
+ chomp $git_last_include_commit;
+ $Kconfig_syms_cache = ".checkpatch-Kconfig_syms.git.$git_last_include_commit";
+ } else {
+ my $last_mod_date = 0;
+ $files = `find $root/ -name "Kconfig*"`;
+ @Kconfig_files = split('\n', $files);
+ foreach my $file (@Kconfig_files) {
+ my $date = POSIX::strftime("%Y%m%d%H%M",
+ localtime((stat $file)[9]));
+ $last_mod_date = $date if ($last_mod_date < $date);
+ }
+ $Kconfig_syms_cache = ".checkpatch-Kconfig_syms.date.$last_mod_date";
+ }
+
+ if ($Kconfig_syms_cache ne "" && -f $Kconfig_syms_cache) {
+ open(my $Kconfig_syms_file, '<', "$Kconfig_syms_cache")
+ or warn "$P: Can't read '$Kconfig_syms_cache' $!\n";
+ while (<$Kconfig_syms_file>) {
+ chomp;
+ $Kconfig_syms{$_} = 1;
+ }
+ close($Kconfig_syms_file);
+
+ return;
+ }
+
+ if (-e "$gitroot") {
+ my @syms = `${git_command} grep -P -oh '^\\s*config\\s+\\w+' -- '*/Kconfig*'`;
+ s/^\s+// for @syms;
+ s/config\s+// for @syms;
+ s/\n$// for @syms;
+ @syms = sort(uniq(@syms));
+ foreach my $sym (@syms) {
+ $Kconfig_syms{$sym} = 1;
+ }
+ } else {
+ foreach my $file (@Kconfig_files) {
+ seed_Kconfig_file($file);
+ }
+ }
+
+ if ($Kconfig_syms_cache ne "") {
+ unlink glob ".checkpatch-Kconfig_syms.*";
+ open(my $Kconfig_syms_file, '>', "$Kconfig_syms_cache")
+ or warn "$P: Can't write '$Kconfig_syms_cache' $!\n";
+ foreach (sort { lc($a) cmp lc($b) } keys(%Kconfig_syms)) {
+ print $Kconfig_syms_file ("$_\n");
+ }
+ close($Kconfig_syms_file);
+ }
+}
+
my $camelcase_seeded = 0;
sub seed_camelcase_includes {
return if ($camelcase_seeded);
@@ -6480,6 +6565,22 @@ sub process {
}
}
+# check for IS_ENABLED() used without CONFIG_<FOO> ($rawline for comment use)
+# or if the CONFIG_<FOO> symbol is not a known Kconfig entry
+ if ($rawline =~ /\bIS_ENABLED\s*\(\s*(\w+)\s*\)/) {
+ my $sym = $1;
+ seed_Kconfig_symbols();
+ if ($sym !~ /^CONFIG_/) {
+ WARN("IS_ENABLED_CONFIG",
+ "IS_ENABLED($sym) is normally used as IS_ENABLED(CONFIG_$1)\n" . $herecurr);
+ }
+ if (!exists($Kconfig_syms{$sym})) {
+ WARN("IS_ENABLED_CONFIG",
+ "'$sym' is not a known Kconfig config entry in the current kernel sources\n" . $herecurr);
+
+ }
+ }
+
# check for #if defined CONFIG_<FOO> || defined CONFIG_<FOO>_MODULE
if ($line =~ /^\+\s*#\s*if\s+defined(?:\s*\(?\s*|\s+)(CONFIG_[A-Z_]+)\s*\)?\s*\|\|\s*defined(?:\s*\(?\s*|\s+)\1_MODULE\s*\)?\s*$/) {
my $config = $1;