scripts/checkpatch.pl: Resync with v6.17

This resyncs us with the version found in v6.17 of the Linux kernel with
the following exceptions:
- Keep our u-boot specific tests / code area.
- Keep the location of our checkpatch.rst
- Change the "use strscpy" test as we don't have that to strlcpy
- Keep debug/printf in the list for $logFunctions
- Keep checks to "env" files
- Keep our tests for strncpy/strncat

This also syncs the spdxcheck.py tool and all the associated
documentation.

Signed-off-by: Kory Maincent <kory.maincent@bootlin.com>
[trini: Keep our strlcpy/cat check]
Signed-off-by: Tom Rini <trini@konsulko.com>
This commit is contained in:
Kory Maincent
2025-09-29 19:24:32 +02:00
committed by Tom Rini
parent 7781f1d832
commit ddde9ac63d
3 changed files with 605 additions and 149 deletions

View File

@@ -168,7 +168,7 @@ Available options:
- --fix - --fix
This is an EXPERIMENTAL feature. If correctable errors exists, a file This is an EXPERIMENTAL feature. If correctable errors exist, a file
<inputfile>.EXPERIMENTAL-checkpatch-fixes is created which has the <inputfile>.EXPERIMENTAL-checkpatch-fixes is created which has the
automatically fixable errors corrected. automatically fixable errors corrected.
@@ -181,7 +181,7 @@ Available options:
- --ignore-perl-version - --ignore-perl-version
Override checking of perl version. Runtime errors maybe encountered after Override checking of perl version. Runtime errors may be encountered after
enabling this flag if the perl version does not meet the minimum specified. enabling this flag if the perl version does not meet the minimum specified.
- --codespell - --codespell
@@ -342,24 +342,6 @@ API usage
See: https://www.kernel.org/doc/html/latest/RCU/whatisRCU.html#full-list-of-rcu-apis See: https://www.kernel.org/doc/html/latest/RCU/whatisRCU.html#full-list-of-rcu-apis
**DEPRECATED_VARIABLE**
EXTRA_{A,C,CPP,LD}FLAGS are deprecated and should be replaced by the new
flags added via commit f77bf01425b1 ("kbuild: introduce ccflags-y,
asflags-y and ldflags-y").
The following conversion scheme maybe used::
EXTRA_AFLAGS -> asflags-y
EXTRA_CFLAGS -> ccflags-y
EXTRA_CPPFLAGS -> cppflags-y
EXTRA_LDFLAGS -> ldflags-y
See:
1. https://lore.kernel.org/lkml/20070930191054.GA15876@uranus.ravnborg.org/
2. https://lore.kernel.org/lkml/1313384834-24433-12-git-send-email-lacombar@gmail.com/
3. https://www.kernel.org/doc/html/latest/kbuild/makefiles.html#compilation-flags
**DEVICE_ATTR_FUNCTIONS** **DEVICE_ATTR_FUNCTIONS**
The function names used in DEVICE_ATTR is unusual. The function names used in DEVICE_ATTR is unusual.
Typically, the store and show functions are used with <attr>_store and Typically, the store and show functions are used with <attr>_store and
@@ -470,8 +452,6 @@ API usage
usleep_range() should be preferred over udelay(). The proper way of usleep_range() should be preferred over udelay(). The proper way of
using usleep_range() is mentioned in the kernel docs. using usleep_range() is mentioned in the kernel docs.
See: https://www.kernel.org/doc/html/latest/timers/timers-howto.html#delays-information-on-the-various-kernel-delay-sleep-mechanisms
Comments Comments
-------- --------
@@ -515,6 +495,15 @@ Comments
See: https://lore.kernel.org/lkml/20131006222342.GT19510@leaf/ See: https://lore.kernel.org/lkml/20131006222342.GT19510@leaf/
**UNCOMMENTED_RGMII_MODE**
Historically, the RGMII PHY modes specified in Device Trees have been
used inconsistently, often referring to the usage of delays on the PHY
side rather than describing the board.
PHY modes "rgmii", "rgmii-rxid" and "rgmii-txid" modes require the clock
signal to be delayed on the PCB; this unusual configuration should be
described in a comment. If they are not (meaning that the delay is realized
internally in the MAC or PHY), "rgmii-id" is the correct PHY mode.
Commit message Commit message
-------------- --------------
@@ -612,6 +601,13 @@ Commit message
See: https://www.kernel.org/doc/html/latest/process/submitting-patches.html#describe-your-changes See: https://www.kernel.org/doc/html/latest/process/submitting-patches.html#describe-your-changes
**BAD_FIXES_TAG**
The Fixes: tag is malformed or does not follow the community conventions.
This can occur if the tag have been split into multiple lines (e.g., when
pasted in an email program with word wrapping enabled).
See: https://www.kernel.org/doc/html/latest/process/submitting-patches.html#describe-your-changes
Comparison style Comparison style
---------------- ----------------
@@ -899,6 +895,20 @@ Macros, Attributes and Symbols
See: https://lore.kernel.org/lkml/1399671106.2912.21.camel@joe-AO725/ See: https://lore.kernel.org/lkml/1399671106.2912.21.camel@joe-AO725/
**MACRO_ARG_UNUSED**
If function-like macros do not utilize a parameter, it might result
in a build warning. We advocate for utilizing static inline functions
to replace such macros.
For example, for a macro such as the one below::
#define test(a) do { } while (0)
there would be a warning like below::
WARNING: Argument 'a' is not used in function-like macro.
See: https://www.kernel.org/doc/html/latest/process/coding-style.html#macros-enums-and-rtl
**SINGLE_STATEMENT_DO_WHILE_MACRO** **SINGLE_STATEMENT_DO_WHILE_MACRO**
For the multi-statement macros, it is necessary to use the do-while For the multi-statement macros, it is necessary to use the do-while
loop to avoid unpredictable code paths. The do-while loop helps to loop to avoid unpredictable code paths. The do-while loop helps to

View File

@@ -28,6 +28,7 @@ my %verbose_messages = ();
my %verbose_emitted = (); my %verbose_emitted = ();
my $tree = 1; my $tree = 1;
my $chk_signoff = 1; my $chk_signoff = 1;
my $chk_fixes_tag = 1;
my $chk_patch = 1; my $chk_patch = 1;
my $tst_only; my $tst_only;
my $emacs = 0; my $emacs = 0;
@@ -75,6 +76,8 @@ my $git_command ='export LANGUAGE=en_US.UTF-8; git';
my $tabsize = 8; my $tabsize = 8;
my ${CONFIG_} = "CONFIG_"; my ${CONFIG_} = "CONFIG_";
my %maybe_linker_symbol; # for externs in c exceptions, when seen in *vmlinux.lds.h
sub help { sub help {
my ($exitcode) = @_; my ($exitcode) = @_;
@@ -87,6 +90,7 @@ Options:
-v, --verbose verbose mode -v, --verbose verbose mode
--no-tree run without a kernel tree --no-tree run without a kernel tree
--no-signoff do not check for 'Signed-off-by' line --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) --patch treat FILE as patchfile (default)
--emacs emacs compile window format --emacs emacs compile window format
--terse one line per report --terse one line per report
@@ -110,7 +114,8 @@ Options:
--max-line-length=n set the maximum line length, (default $max_line_length) --max-line-length=n set the maximum line length, (default $max_line_length)
if exceeded, warn on patches if exceeded, warn on patches
requires --strict for use with --file requires --strict for use with --file
--min-conf-desc-length=n set the min description length, if shorter, warn --min-conf-desc-length=n set the minimum description length for config symbols
in lines, if shorter, warn (default $min_conf_desc_length)
--tab-size=n set the number of spaces for tab (default $tabsize) --tab-size=n set the number of spaces for tab (default $tabsize)
--root=PATH PATH to the kernel tree root --root=PATH PATH to the kernel tree root
--no-summary suppress the per-file summary --no-summary suppress the per-file summary
@@ -148,6 +153,24 @@ EOM
exit($exitcode); exit($exitcode);
} }
my $DO_WHILE_0_ADVICE = q{
do {} while (0) advice is over-stated in a few situations:
The more obvious case is macros, like MODULE_PARM_DESC, invoked at
file-scope, where C disallows code (it must be in functions). See
$exceptions if you have one to add by name.
More troublesome is declarative macros used at top of new scope,
like DECLARE_PER_CPU. These might just compile with a do-while-0
wrapper, but would be incorrect. Most of these are handled by
detecting struct,union,etc declaration primitives in $exceptions.
Theres also macros called inside an if (block), which "return" an
expression. These cannot do-while, and need a ({}) wrapper.
Enjoy this qualification while we work to improve our heuristics.
};
sub uniq { sub uniq {
my %seen; my %seen;
return grep { !$seen{$_}++ } @_; return grep { !$seen{$_}++ } @_;
@@ -295,6 +318,7 @@ GetOptions(
'v|verbose!' => \$verbose, 'v|verbose!' => \$verbose,
'tree!' => \$tree, 'tree!' => \$tree,
'signoff!' => \$chk_signoff, 'signoff!' => \$chk_signoff,
'fixes-tag!' => \$chk_fixes_tag,
'patch!' => \$chk_patch, 'patch!' => \$chk_patch,
'emacs!' => \$emacs, 'emacs!' => \$emacs,
'terse!' => \$terse, 'terse!' => \$terse,
@@ -337,7 +361,7 @@ if ($user_codespellfile) {
} elsif (!(-f $codespellfile)) { } elsif (!(-f $codespellfile)) {
# If /usr/share/codespell/dictionary.txt is not present, try to find it # If /usr/share/codespell/dictionary.txt is not present, try to find it
# under codespell's install directory: <codespell_root>/data/dictionary.txt # under codespell's install directory: <codespell_root>/data/dictionary.txt
if (($codespell || $help) && which("codespell") ne "" && which("python") ne "") { if (($codespell || $help) && which("python3") ne "") {
my $python_codespell_dict = << "EOF"; my $python_codespell_dict = << "EOF";
import os.path as op import os.path as op
@@ -347,7 +371,7 @@ codespell_file = op.join(codespell_dir, 'data', 'dictionary.txt')
print(codespell_file, end='') print(codespell_file, end='')
EOF EOF
my $codespell_dict = `python -c "$python_codespell_dict" 2> /dev/null`; my $codespell_dict = `python3 -c "$python_codespell_dict" 2> /dev/null`;
$codespellfile = $codespell_dict if (-f $codespell_dict); $codespellfile = $codespell_dict if (-f $codespell_dict);
} }
} }
@@ -513,6 +537,7 @@ our $Attribute = qr{
__ro_after_init| __ro_after_init|
__kprobes| __kprobes|
$InitAttribute| $InitAttribute|
__aligned\s*\(.*\)|
____cacheline_aligned| ____cacheline_aligned|
____cacheline_aligned_in_smp| ____cacheline_aligned_in_smp|
____cacheline_internodealigned_in_smp| ____cacheline_internodealigned_in_smp|
@@ -579,10 +604,14 @@ our $typeKernelTypedefs = qr{(?x:
(?:__)?(?:u|s|be|le)(?:8|16|32|64)| (?:__)?(?:u|s|be|le)(?:8|16|32|64)|
atomic_t atomic_t
)}; )};
our $typeStdioTypedefs = qr{(?x:
FILE
)};
our $typeTypedefs = qr{(?x: our $typeTypedefs = qr{(?x:
$typeC99Typedefs\b| $typeC99Typedefs\b|
$typeOtherOSTypedefs\b| $typeOtherOSTypedefs\b|
$typeKernelTypedefs\b $typeKernelTypedefs\b|
$typeStdioTypedefs\b
)}; )};
our $zero_initializer = qr{(?:(?:0[xX])?0+$Int_type?|NULL|false)\b}; our $zero_initializer = qr{(?:(?:0[xX])?0+$Int_type?|NULL|false)\b};
@@ -621,6 +650,22 @@ our $signature_tags = qr{(?xi:
Cc: Cc:
)}; )};
our @link_tags = qw(Link Closes);
#Create a search and print patterns for all these strings to be used directly below
our $link_tags_search = "";
our $link_tags_print = "";
foreach my $entry (@link_tags) {
if ($link_tags_search ne "") {
$link_tags_search .= '|';
$link_tags_print .= ' or ';
}
$entry .= ':';
$link_tags_search .= $entry;
$link_tags_print .= "'$entry'";
}
$link_tags_search = "(?:${link_tags_search})";
our $tracing_logging_tags = qr{(?xi: our $tracing_logging_tags = qr{(?xi:
[=-]*> | [=-]*> |
<[=-]* | <[=-]* |
@@ -645,6 +690,9 @@ our $tracing_logging_tags = qr{(?xi:
[\.\!:\s]* [\.\!:\s]*
)}; )};
# Device ID types like found in include/linux/mod_devicetable.h.
our $dev_id_types = qr{\b[a-z]\w*_device_id\b};
sub edit_distance_min { sub edit_distance_min {
my (@arr) = @_; my (@arr) = @_;
my $len = scalar @arr; my $len = scalar @arr;
@@ -703,6 +751,17 @@ sub find_standard_signature {
return ""; return "";
} }
our $obsolete_archives = qr{(?xi:
\Qfreedesktop.org/archives/dri-devel\E |
\Qlists.infradead.org\E |
\Qlkml.org\E |
\Qmail-archive.com\E |
\Qmailman.alsa-project.org/pipermail\E |
\Qmarc.info\E |
\Qozlabs.org/pipermail\E |
\Qspinics.net\E
)};
our @typeListMisordered = ( our @typeListMisordered = (
qr{char\s+(?:un)?signed}, qr{char\s+(?:un)?signed},
qr{int\s+(?:(?:un)?signed\s+)?short\s}, qr{int\s+(?:(?:un)?signed\s+)?short\s},
@@ -802,16 +861,10 @@ foreach my $entry (@mode_permission_funcs) {
$mode_perms_search = "(?:${mode_perms_search})"; $mode_perms_search = "(?:${mode_perms_search})";
our %deprecated_apis = ( our %deprecated_apis = (
"synchronize_rcu_bh" => "synchronize_rcu", "kmap" => "kmap_local_page",
"synchronize_rcu_bh_expedited" => "synchronize_rcu_expedited", "kunmap" => "kunmap_local",
"call_rcu_bh" => "call_rcu", "kmap_atomic" => "kmap_local_page",
"rcu_barrier_bh" => "rcu_barrier", "kunmap_atomic" => "kunmap_local",
"synchronize_sched" => "synchronize_rcu",
"synchronize_sched_expedited" => "synchronize_rcu_expedited",
"call_rcu_sched" => "call_rcu",
"rcu_barrier_sched" => "rcu_barrier",
"get_state_synchronize_sched" => "get_state_synchronize_rcu",
"cond_synchronize_sched" => "cond_synchronize_rcu",
); );
#Create a search pattern for all these strings to speed up a loop below #Create a search pattern for all these strings to speed up a loop below
@@ -1047,7 +1100,8 @@ our $FuncArg = qr{$Typecast{0,1}($LvalOrFunc|$Constant|$String)};
our $declaration_macros = qr{(?x: our $declaration_macros = qr{(?x:
(?:$Storage\s+)?(?:[A-Z_][A-Z0-9]*_){0,2}(?:DEFINE|DECLARE)(?:_[A-Z0-9]+){1,6}\s*\(| (?:$Storage\s+)?(?:[A-Z_][A-Z0-9]*_){0,2}(?:DEFINE|DECLARE)(?:_[A-Z0-9]+){1,6}\s*\(|
(?:$Storage\s+)?[HLP]?LIST_HEAD\s*\(| (?:$Storage\s+)?[HLP]?LIST_HEAD\s*\(|
(?:SKCIPHER_REQUEST|SHASH_DESC|AHASH_REQUEST)_ON_STACK\s*\( (?:SKCIPHER_REQUEST|SHASH_DESC|AHASH_REQUEST)_ON_STACK\s*\(|
(?:$Storage\s+)?(?:XA_STATE|XA_STATE_ORDER)\s*\(
)}; )};
our %allow_repeated_words = ( our %allow_repeated_words = (
@@ -1223,6 +1277,7 @@ sub git_commit_info {
} }
$chk_signoff = 0 if ($file); $chk_signoff = 0 if ($file);
$chk_fixes_tag = 0 if ($file);
my @rawlines = (); my @rawlines = ();
my @lines = (); my @lines = ();
@@ -2725,6 +2780,9 @@ sub process {
our $clean = 1; our $clean = 1;
my $signoff = 0; my $signoff = 0;
my $fixes_tag = 0;
my $is_revert = 0;
my $needs_fixes_tag = "";
my $author = ''; my $author = '';
my $authorsignoff = 0; my $authorsignoff = 0;
my $author_sob = ''; my $author_sob = '';
@@ -2957,7 +3015,7 @@ sub process {
if ($realfile =~ m@^include/asm/@) { if ($realfile =~ m@^include/asm/@) {
ERROR("MODIFIED_INCLUDE_ASM", ERROR("MODIFIED_INCLUDE_ASM",
"do not modify files in include/asm, change architecture specific files in include/asm-<architecture>\n" . "$here$rawline\n"); "do not modify files in include/asm, change architecture specific files in arch/<architecture>/include/asm\n" . "$here$rawline\n");
} }
$found_file = 1; $found_file = 1;
} }
@@ -3252,17 +3310,79 @@ sub process {
if ($sign_off =~ /^co-developed-by:$/i) { if ($sign_off =~ /^co-developed-by:$/i) {
if ($email eq $author) { if ($email eq $author) {
WARN("BAD_SIGN_OFF", WARN("BAD_SIGN_OFF",
"Co-developed-by: should not be used to attribute nominal patch author '$author'\n" . "$here\n" . $rawline); "Co-developed-by: should not be used to attribute nominal patch author '$author'\n" . $herecurr);
} }
if (!defined $lines[$linenr]) { if (!defined $lines[$linenr]) {
WARN("BAD_SIGN_OFF", WARN("BAD_SIGN_OFF",
"Co-developed-by: must be immediately followed by Signed-off-by:\n" . "$here\n" . $rawline); "Co-developed-by: must be immediately followed by Signed-off-by:\n" . $herecurr);
} elsif ($rawlines[$linenr] !~ /^\s*signed-off-by:\s*(.*)/i) { } elsif ($rawlines[$linenr] !~ /^signed-off-by:\s*(.*)/i) {
WARN("BAD_SIGN_OFF", WARN("BAD_SIGN_OFF",
"Co-developed-by: must be immediately followed by Signed-off-by:\n" . "$here\n" . $rawline . "\n" .$rawlines[$linenr]); "Co-developed-by: must be immediately followed by Signed-off-by:\n" . $herecurr . $rawlines[$linenr] . "\n");
} elsif ($1 ne $email) { } elsif ($1 ne $email) {
WARN("BAD_SIGN_OFF", WARN("BAD_SIGN_OFF",
"Co-developed-by and Signed-off-by: name/email do not match \n" . "$here\n" . $rawline . "\n" .$rawlines[$linenr]); "Co-developed-by and Signed-off-by: name/email do not match\n" . $herecurr . $rawlines[$linenr] . "\n");
}
}
# check if Reported-by: is followed by a Closes: tag
if ($sign_off =~ /^reported(?:|-and-tested)-by:$/i) {
if (!defined $lines[$linenr]) {
WARN("BAD_REPORTED_BY_LINK",
"Reported-by: should be immediately followed by Closes: with a URL to the report\n" . $herecurr . "\n");
} elsif ($rawlines[$linenr] !~ /^closes:\s*/i) {
WARN("BAD_REPORTED_BY_LINK",
"Reported-by: should be immediately followed by Closes: with a URL to the report\n" . $herecurr . $rawlines[$linenr] . "\n");
}
}
}
# 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 =~ /((?:(?:BUG: K.|UB)SAN: |Call Trace:|stable\@|syzkaller))/) {
$needs_fixes_tag = $1;
}
# Check Fixes: styles is correct
if (!$in_header_lines &&
$line =~ /^\s*(fixes:?)\s*(?:commit\s*)?([0-9a-f]{5,40})(?:\s*($balanced_parens))?/i) {
my $tag = $1;
my $orig_commit = $2;
my $title;
my $title_has_quotes = 0;
$fixes_tag = 1;
if (defined $3) {
# Always strip leading/trailing parens then double quotes if existing
$title = substr($3, 1, -1);
if ($title =~ /^".*"$/) {
$title = substr($title, 1, -1);
$title_has_quotes = 1;
}
} else {
$title = "commit title"
}
my $tag_case = not ($tag eq "Fixes:");
my $tag_space = not ($line =~ /^fixes:? [0-9a-f]{5,40} ($balanced_parens)/i);
my $id_length = not ($orig_commit =~ /^[0-9a-f]{12,40}$/i);
my $id_case = not ($orig_commit !~ /[A-F]/);
my $id = "0123456789ab";
my ($cid, $ctitle) = git_commit_info($orig_commit, $id,
$title);
if (defined($cid) && ($ctitle ne $title || $tag_case || $tag_space || $id_length || $id_case || !$title_has_quotes)) {
my $fixed = "Fixes: $cid (\"$ctitle\")";
if (WARN("BAD_FIXES_TAG",
"Please use correct Fixes: style 'Fixes: <12+ chars of sha1> (\"<title line>\")' - ie: '$fixed'\n" . $herecurr) &&
$fix) {
$fixed[$fixlinenr] = $fixed;
} }
} }
} }
@@ -3300,13 +3420,13 @@ sub process {
length($line) > 75 && length($line) > 75 &&
!($line =~ /^\s*[a-zA-Z0-9_\/\.]+\s+\|\s+\d+/ || !($line =~ /^\s*[a-zA-Z0-9_\/\.]+\s+\|\s+\d+/ ||
# file delta changes # file delta changes
$line =~ /^\s*(?:[\w\.\-]+\/)++[\w\.\-]+:/ || $line =~ /^\s*(?:[\w\.\-\+]*\/)++[\w\.\-\+]+:/ ||
# filename then : # filename then :
$line =~ /^\s*(?:Fixes:|Link:|$signature_tags)/i || $line =~ /^\s*(?:Fixes:|$link_tags_search|$signature_tags)/i ||
# A Fixes: or Link: line or signature tag line # A Fixes:, link or signature tag line
$commit_log_possible_stack_dump)) { $commit_log_possible_stack_dump)) {
WARN("COMMIT_LOG_LONG_LINE", WARN("COMMIT_LOG_LONG_LINE",
"Possible unwrapped commit description (prefer a maximum 75 chars per line)\n" . $herecurr); "Prefer a maximum 75 chars per line (possible unwrapped commit description?)\n" . $herecurr);
$commit_log_long_line = 1; $commit_log_long_line = 1;
} }
@@ -3316,6 +3436,29 @@ sub process {
$commit_log_possible_stack_dump = 0; $commit_log_possible_stack_dump = 0;
} }
# Check for odd tags before a URI/URL
if ($in_commit_log &&
$line =~ /^\s*(\w+:)\s*http/ && $1 !~ /^$link_tags_search$/) {
if ($1 =~ /^v(?:ersion)?\d+/i) {
WARN("COMMIT_LOG_VERSIONING",
"Patch version information should be after the --- line\n" . $herecurr);
} else {
WARN("COMMIT_LOG_USE_LINK",
"Unknown link reference '$1', use $link_tags_print instead\n" . $herecurr);
}
}
# Check for misuse of the link tags
if ($in_commit_log &&
$line =~ /^\s*(\w+:)\s*(\S+)/) {
my $tag = $1;
my $value = $2;
if ($tag =~ /^$link_tags_search$/ && $value !~ m{^https?://}) {
WARN("COMMIT_LOG_WRONG_LINK",
"'$tag' should be followed by a public http(s) link\n" . $herecurr);
}
}
# Check for lines starting with a # # Check for lines starting with a #
if ($in_commit_log && $line =~ /^#/) { if ($in_commit_log && $line =~ /^#/) {
if (WARN("COMMIT_COMMENT_SYMBOL", if (WARN("COMMIT_COMMENT_SYMBOL",
@@ -3401,6 +3544,12 @@ sub process {
$last_git_commit_id_linenr = $linenr if ($line =~ /\bcommit\s*$/i); $last_git_commit_id_linenr = $linenr if ($line =~ /\bcommit\s*$/i);
} }
# Check for mailing list archives other than lore.kernel.org
if ($rawline =~ m{http.*\b$obsolete_archives}) {
WARN("PREFER_LORE_ARCHIVE",
"Use lore.kernel.org archive links when possible - see https://lore.kernel.org/lists.html\n" . $herecurr);
}
# Check for added, moved or deleted files # Check for added, moved or deleted files
if (!$reported_maintainer_file && !$in_commit_log && if (!$reported_maintainer_file && !$in_commit_log &&
($line =~ /^(?:new|deleted) file mode\s*\d+\s*$/ || ($line =~ /^(?:new|deleted) file mode\s*\d+\s*$/ ||
@@ -3482,9 +3631,10 @@ sub process {
# Check for various typo / spelling mistakes # Check for various typo / spelling mistakes
if (defined($misspellings) && if (defined($misspellings) &&
($in_commit_log || $line =~ /^(?:\+|Subject:)/i)) { ($in_commit_log || $line =~ /^(?:\+|Subject:)/i)) {
while ($rawline =~ /(?:^|[^\w\-'`])($misspellings)(?:[^\w\-'`]|$)/gi) { my $rawline_utf8 = decode("utf8", $rawline);
while ($rawline_utf8 =~ /(?:^|[^\w\-'`])($misspellings)(?:[^\w\-'`]|$)/gi) {
my $typo = $1; my $typo = $1;
my $blank = copy_spacing($rawline); my $blank = copy_spacing($rawline_utf8);
my $ptr = substr($blank, 0, $-[1]) . "^" x length($typo); my $ptr = substr($blank, 0, $-[1]) . "^" x length($typo);
my $hereptr = "$hereline$ptr\n"; my $hereptr = "$hereline$ptr\n";
my $typo_fix = $spelling_fix{lc($typo)}; my $typo_fix = $spelling_fix{lc($typo)};
@@ -3607,47 +3757,47 @@ sub process {
# Kconfig supports named choices), so use a word boundary # Kconfig supports named choices), so use a word boundary
# (\b) rather than a whitespace character (\s) # (\b) rather than a whitespace character (\s)
$line =~ /^\+\s*(?:config|menuconfig|choice)\b/) { $line =~ /^\+\s*(?:config|menuconfig|choice)\b/) {
my $length = 0; my $ln = $linenr;
my $cnt = $realcnt; my $needs_help = 0;
my $ln = $linenr + 1; my $has_help = 0;
my $f; my $help_length = 0;
my $is_start = 0; while (defined $lines[$ln]) {
my $is_end = 0; my $f = $lines[$ln++];
for (; $cnt > 0 && defined $lines[$ln - 1]; $ln++) {
$f = $lines[$ln - 1];
$cnt-- if ($lines[$ln - 1] !~ /^-/);
$is_end = $lines[$ln - 1] =~ /^\+/;
next if ($f =~ /^-/); next if ($f =~ /^-/);
last if (!$file && $f =~ /^\@\@/); last if ($f !~ /^[\+ ]/); # !patch context
if ($lines[$ln - 1] =~ /^\+\s*(?:bool|tristate|prompt)\s*["']/) { if ($f =~ /^\+\s*(?:bool|tristate|prompt)\s*["']/) {
$is_start = 1; $needs_help = 1;
} elsif ($lines[$ln - 1] =~ /^\+\s*(?:---)?help(?:---)?$/) { next;
$length = -1; }
if ($f =~ /^\+\s*help\s*$/) {
$has_help = 1;
next;
} }
$f =~ s/^.//; $f =~ s/^.//; # strip patch context [+ ]
$f =~ s/#.*//; $f =~ s/#.*//; # strip # directives
$f =~ s/^\s+//; $f =~ s/^\s+//; # strip leading blanks
next if ($f =~ /^$/); next if ($f =~ /^$/); # skip blank lines
# At the end of this Kconfig block:
# This only checks context lines in the patch # This only checks context lines in the patch
# and so hopefully shouldn't trigger false # and so hopefully shouldn't trigger false
# positives, even though some of these are # positives, even though some of these are
# common words in help texts # common words in help texts
if ($f =~ /^\s*(?:config|menuconfig|choice|endchoice| if ($f =~ /^(?:config|menuconfig|choice|endchoice|
if|endif|menu|endmenu|source)\b/x) { if|endif|menu|endmenu|source)\b/x) {
$is_end = 1;
last; last;
} }
$length++; $help_length++ if ($has_help);
} }
if ($is_start && $is_end && $length < $min_conf_desc_length) { if ($needs_help &&
$help_length < $min_conf_desc_length) {
my $stat_real = get_stat_real($linenr, $ln - 1);
WARN("CONFIG_DESCRIPTION", WARN("CONFIG_DESCRIPTION",
"please write a paragraph that describes the config symbol fully\n" . $herecurr); "please write a help paragraph that fully describes the config symbol with at least $min_conf_desc_length lines\n" . "$here\n$stat_real\n");
} }
#print "is_start<$is_start> is_end<$is_end> length<$length>\n";
} }
# check MAINTAINERS entries # check MAINTAINERS entries
@@ -3690,20 +3840,6 @@ sub process {
} }
} }
if (($realfile =~ /Makefile.*/ || $realfile =~ /Kbuild.*/) &&
($line =~ /\+(EXTRA_[A-Z]+FLAGS).*/)) {
my $flag = $1;
my $replacement = {
'EXTRA_AFLAGS' => 'asflags-y',
'EXTRA_CFLAGS' => 'ccflags-y',
'EXTRA_CPPFLAGS' => 'cppflags-y',
'EXTRA_LDFLAGS' => 'ldflags-y',
};
WARN("DEPRECATED_VARIABLE",
"Use of $flag is deprecated, please use \`$replacement->{$flag} instead.\n" . $herecurr) if ($replacement->{$flag});
}
# check for DT compatible documentation # check for DT compatible documentation
if (defined $root && if (defined $root &&
(($realfile =~ /\.dtsi?$/ && $line =~ /^\+\s*compatible\s*=\s*\"/) || (($realfile =~ /\.dtsi?$/ && $line =~ /^\+\s*compatible\s*=\s*\"/) ||
@@ -3735,6 +3871,18 @@ sub process {
} }
} }
# Check for RGMII phy-mode with delay on PCB
if ($realfile =~ /\.(dts|dtsi|dtso)$/ &&
$line =~ /^\+\s*(phy-mode|phy-connection-type)\s*=\s*"/ &&
!ctx_has_comment($first_line, $linenr)) {
my $prop = $1;
my $mode = get_quoted_string($line, $rawline);
if ($mode =~ /^"rgmii(?:|-rxid|-txid)"$/) {
WARN("UNCOMMENTED_RGMII_MODE",
"$prop $mode without comment -- delays on the PCB should be described, otherwise use \"rgmii-id\"\n" . $herecurr);
}
}
# check for using SPDX license tag at beginning of files # check for using SPDX license tag at beginning of files
if ($realline == $checklicenseline) { if ($realline == $checklicenseline) {
if ($rawline =~ /^[ \+]\s*\#\!\s*\//) { if ($rawline =~ /^[ \+]\s*\#\!\s*\//) {
@@ -3743,7 +3891,7 @@ sub process {
my $comment = ""; my $comment = "";
if ($realfile =~ /\.(h|s|S)$/) { if ($realfile =~ /\.(h|s|S)$/) {
$comment = '/*'; $comment = '/*';
} elsif ($realfile =~ /\.(c|dts|dtsi)$/) { } elsif ($realfile =~ /\.(c|rs|dts|dtsi)$/) {
$comment = '//'; $comment = '//';
} elsif (($checklicenseline == 2) || $realfile =~ /\.(sh|pl|py|awk|tc|yaml)$/) { } elsif (($checklicenseline == 2) || $realfile =~ /\.(sh|pl|py|awk|tc|yaml)$/) {
$comment = '#'; $comment = '#';
@@ -3770,7 +3918,7 @@ sub process {
"'$spdx_license' is not supported in LICENSES/...\n" . $herecurr); "'$spdx_license' is not supported in LICENSES/...\n" . $herecurr);
} }
if ($realfile =~ m@^Documentation/devicetree/bindings/@ && if ($realfile =~ m@^Documentation/devicetree/bindings/@ &&
not $spdx_license =~ /GPL-2\.0.*BSD-2-Clause/) { $spdx_license !~ /GPL-2\.0(?:-only)? OR BSD-2-Clause/) {
my $msg_level = \&WARN; my $msg_level = \&WARN;
$msg_level = \&CHK if ($file); $msg_level = \&CHK if ($file);
if (&{$msg_level}("SPDX_LICENSE_TAG", if (&{$msg_level}("SPDX_LICENSE_TAG",
@@ -3780,18 +3928,23 @@ sub process {
$fixed[$fixlinenr] =~ s/SPDX-License-Identifier: .*/SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)/; $fixed[$fixlinenr] =~ s/SPDX-License-Identifier: .*/SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)/;
} }
} }
if ($realfile =~ m@^include/dt-bindings/@ &&
$spdx_license !~ /GPL-2\.0(?:-only)? OR \S+/) {
WARN("SPDX_LICENSE_TAG",
"DT binding headers should be licensed (GPL-2.0-only OR .*)\n" . $herecurr);
}
} }
} }
} }
# check for embedded filenames # check for embedded filenames
if ($rawline =~ /^\+.*\Q$realfile\E/) { if ($rawline =~ /^\+.*\b\Q$realfile\E\b/) {
WARN("EMBEDDED_FILENAME", WARN("EMBEDDED_FILENAME",
"It's generally not useful to have the filename in the file\n" . $herecurr); "It's generally not useful to have the filename in the file\n" . $herecurr);
} }
# check we are in a valid source file if not then ignore this hunk # check we are in a valid source file if not then ignore this hunk
next if ($realfile !~ /\.(h|c|s|S|sh|dtsi|dts|env)$/); next if ($realfile !~ /\.(h|c|rs|s|S|sh|dtsi|dts|env)$/);
# check for using SPDX-License-Identifier on the wrong line number # check for using SPDX-License-Identifier on the wrong line number
if ($realline != $checklicenseline && if ($realline != $checklicenseline &&
@@ -3857,7 +4010,7 @@ sub process {
} }
if ($msg_type ne "" && if ($msg_type ne "" &&
(show_type("LONG_LINE") || show_type($msg_type))) { show_type("LONG_LINE") && show_type($msg_type)) {
my $msg_level = \&WARN; my $msg_level = \&WARN;
$msg_level = \&CHK if ($file); $msg_level = \&CHK if ($file);
&{$msg_level}($msg_type, &{$msg_level}($msg_type,
@@ -3878,7 +4031,7 @@ sub process {
if ($realfile =~ /\.S$/ && if ($realfile =~ /\.S$/ &&
$line =~ /^\+\s*(?:[A-Z]+_)?SYM_[A-Z]+_(?:START|END)(?:_[A-Z_]+)?\s*\(\s*\.L/) { $line =~ /^\+\s*(?:[A-Z]+_)?SYM_[A-Z]+_(?:START|END)(?:_[A-Z_]+)?\s*\(\s*\.L/) {
WARN("AVOID_L_PREFIX", WARN("AVOID_L_PREFIX",
"Avoid using '.L' prefixed local symbol names for denoting a range of code via 'SYM_*_START/END' annotations; see Documentation/asm-annotations.rst\n" . $herecurr); "Avoid using '.L' prefixed local symbol names for denoting a range of code via 'SYM_*_START/END' annotations; see Documentation/core-api/asm-annotations.rst\n" . $herecurr);
} }
if ($u_boot) { if ($u_boot) {
@@ -4000,16 +4153,6 @@ sub process {
} }
} }
# Block comment styles
# Networking with an initial /*
if ($realfile =~ m@^(drivers/net/|net/)@ &&
$prevrawline =~ /^\+[ \t]*\/\*[ \t]*$/ &&
$rawline =~ /^\+[ \t]*\*/ &&
$realline > 3) { # Do not warn about the initial copyright comment block after SPDX-License-Identifier
WARN("NETWORKING_BLOCK_COMMENT_STYLE",
"networking block comments don't use an empty /* line, use /* Comment...\n" . $hereprev);
}
# Block comments use * on subsequent lines # Block comments use * on subsequent lines
if ($prevline =~ /$;[ \t]*$/ && #ends in comment if ($prevline =~ /$;[ \t]*$/ && #ends in comment
$prevrawline =~ /^\+.*?\/\*/ && #starting /* $prevrawline =~ /^\+.*?\/\*/ && #starting /*
@@ -4058,7 +4201,7 @@ sub process {
if ($prevline =~ /^[\+ ]};?\s*$/ && if ($prevline =~ /^[\+ ]};?\s*$/ &&
$line =~ /^\+/ && $line =~ /^\+/ &&
!($line =~ /^\+\s*$/ || !($line =~ /^\+\s*$/ ||
$line =~ /^\+\s*EXPORT_SYMBOL/ || $line =~ /^\+\s*(?:EXPORT_SYMBOL|early_param|ALLOW_ERROR_INJECTION)/ ||
$line =~ /^\+\s*MODULE_/i || $line =~ /^\+\s*MODULE_/i ||
$line =~ /^\+\s*\#\s*(?:end|elif|else)/ || $line =~ /^\+\s*\#\s*(?:end|elif|else)/ ||
$line =~ /^\+[a-z_]*init/ || $line =~ /^\+[a-z_]*init/ ||
@@ -4826,12 +4969,12 @@ sub process {
} }
} }
# avoid BUG() or BUG_ON() # do not use BUG() or variants
if ($line =~ /\b(?:BUG|BUG_ON)\b/) { if ($line =~ /\b(?!AA_|BUILD_|IDA_|KVM_|RWLOCK_|snd_|SPIN_)(?:[a-zA-Z_]*_)?BUG(?:_ON)?(?:_[A-Z_]+)?\s*\(/) {
my $msg_level = \&WARN; my $msg_level = \&WARN;
$msg_level = \&CHK if ($file); $msg_level = \&CHK if ($file);
&{$msg_level}("AVOID_BUG", &{$msg_level}("AVOID_BUG",
"Avoid crashing the kernel - try using WARN_ON & recovery code rather than BUG() or BUG_ON()\n" . $herecurr); "Do not crash the kernel unless it is absolutely unavoidable--use WARN_ON_ONCE() plus recovery code (if feasible) instead of BUG() or variants\n" . $herecurr);
} }
# avoid LINUX_VERSION_CODE # avoid LINUX_VERSION_CODE
@@ -5052,7 +5195,7 @@ sub process {
if|for|while|switch|return|case| if|for|while|switch|return|case|
volatile|__volatile__| volatile|__volatile__|
__attribute__|format|__extension__| __attribute__|format|__extension__|
asm|__asm__)$/x) asm|__asm__|scoped_guard)$/x)
{ {
# cpp #define statements have non-optional spaces, ie # cpp #define statements have non-optional spaces, ie
# if there is a space between the name and the open # if there is a space between the name and the open
@@ -5513,9 +5656,9 @@ sub process {
} }
} }
# check for unnecessary parentheses around comparisons in if uses # check for unnecessary parentheses around comparisons
# when !drivers/staging or command-line uses --strict # except in drivers/staging
if (($realfile !~ m@^(?:drivers/staging/)@ || $check_orig) && if (($realfile !~ m@^(?:drivers/staging/)@) &&
$perl_version_ok && defined($stat) && $perl_version_ok && defined($stat) &&
$stat =~ /(^.\s*if\s*($balanced_parens))/) { $stat =~ /(^.\s*if\s*($balanced_parens))/) {
my $if_stat = $1; my $if_stat = $1;
@@ -5683,6 +5826,7 @@ sub process {
defined($stat) && defined($cond) && defined($stat) && defined($cond) &&
$line =~ /\b(?:if|while|for)\s*\(/ && $line !~ /^.\s*#/) { $line =~ /\b(?:if|while|for)\s*\(/ && $line !~ /^.\s*#/) {
my ($s, $c) = ($stat, $cond); my ($s, $c) = ($stat, $cond);
my $fixed_assign_in_if = 0;
if ($c =~ /\bif\s*\(.*[^<>!=]=[^=].*/s) { if ($c =~ /\bif\s*\(.*[^<>!=]=[^=].*/s) {
if (ERROR("ASSIGN_IN_IF", if (ERROR("ASSIGN_IN_IF",
@@ -5707,6 +5851,7 @@ sub process {
$newline .= ')'; $newline .= ')';
$newline .= " {" if (defined($brace)); $newline .= " {" if (defined($brace));
fix_insert_line($fixlinenr + 1, $newline); fix_insert_line($fixlinenr + 1, $newline);
$fixed_assign_in_if = 1;
} }
} }
} }
@@ -5730,8 +5875,20 @@ sub process {
$stat_real = "[...]\n$stat_real"; $stat_real = "[...]\n$stat_real";
} }
ERROR("TRAILING_STATEMENTS", if (ERROR("TRAILING_STATEMENTS",
"trailing statements should be on next line\n" . $herecurr . $stat_real); "trailing statements should be on next line\n" . $herecurr . $stat_real) &&
!$fixed_assign_in_if &&
$cond_lines == 0 &&
$fix && $perl_version_ok &&
$fixed[$fixlinenr] =~ /^\+(\s*)((?:if|while|for)\s*$balanced_parens)\s*(.*)$/) {
my $indent = $1;
my $test = $2;
my $rest = rtrim($4);
if ($rest =~ /;$/) {
$fixed[$fixlinenr] = "\+$indent$test";
fix_insert_line($fixlinenr + 1, "$indent\t$rest");
}
}
} }
} }
@@ -5829,16 +5986,20 @@ sub process {
#CamelCase #CamelCase
if ($var !~ /^$Constant$/ && if ($var !~ /^$Constant$/ &&
$var =~ /[A-Z][a-z]|[a-z][A-Z]/ && $var =~ /[A-Z][a-z]|[a-z][A-Z]/ &&
#Ignore C keywords
$var !~ /^_Generic$/ &&
#Ignore some autogenerated defines and enum values #Ignore some autogenerated defines and enum values
$var !~ /^(?:[A-Z]+_){1,5}[A-Z]{1,3}[a-z]/ && $var !~ /^(?:[A-Z]+_){1,5}[A-Z]{1,3}[a-z]/ &&
#Ignore Page<foo> variants #Ignore Page<foo> variants
$var !~ /^(?:Clear|Set|TestClear|TestSet|)Page[A-Z]/ && $var !~ /^(?:Clear|Set|TestClear|TestSet|)Page[A-Z]/ &&
#Ignore ETHTOOL_LINK_MODE_<foo> variants
$var !~ /^ETHTOOL_LINK_MODE_/ &&
#Ignore SI style variants like nS, mV and dB #Ignore SI style variants like nS, mV and dB
#(ie: max_uV, regulator_min_uA_show, RANGE_mA_VALUE) #(ie: max_uV, regulator_min_uA_show, RANGE_mA_VALUE)
$var !~ /^(?:[a-z0-9_]*|[A-Z0-9_]*)?_?[a-z][A-Z](?:_[a-z0-9_]+|_[A-Z0-9_]+)?$/ && $var !~ /^(?:[a-z0-9_]*|[A-Z0-9_]*)?_?[a-z][A-Z](?:_[a-z0-9_]+|_[A-Z0-9_]+)?$/ &&
#Ignore some three character SI units explicitly, like MiB and KHz #Ignore some three character SI units explicitly, like MiB and KHz
$var !~ /^(?:[a-z_]*?)_?(?:[KMGT]iB|[KMGT]?Hz)(?:_[a-z_]+)?$/) { $var !~ /^(?:[a-z_]*?)_?(?:[KMGT]iB|[KMGT]?Hz)(?:_[a-z_]+)?$/) {
while ($var =~ m{($Ident)}g) { while ($var =~ m{\b($Ident)}g) {
my $word = $1; my $word = $1;
next if ($word !~ /[A-Z][a-z]|[a-z][A-Z]/); next if ($word !~ /[A-Z][a-z]|[a-z][A-Z]/);
if ($check) { if ($check) {
@@ -5888,9 +6049,9 @@ sub process {
} }
} }
# multi-statement macros should be enclosed in a do while loop, grab the # Usually multi-statement macros should be enclosed in a do {} while
# first statement and ensure its the whole macro if its not enclosed # (0) loop. Grab the first statement and ensure its the whole macro
# in a known good container # if its not enclosed in a known good container
if ($realfile !~ m@/vmlinux.lds.h$@ && if ($realfile !~ m@/vmlinux.lds.h$@ &&
$line =~ /^.\s*\#\s*define\s*$Ident(\()?/) { $line =~ /^.\s*\#\s*define\s*$Ident(\()?/) {
my $ln = $linenr; my $ln = $linenr;
@@ -5943,10 +6104,13 @@ sub process {
my $exceptions = qr{ my $exceptions = qr{
$Declare| $Declare|
# named exceptions
module_param_named| module_param_named|
MODULE_PARM_DESC| MODULE_PARM_DESC|
DECLARE_PER_CPU| DECLARE_PER_CPU|
DEFINE_PER_CPU| DEFINE_PER_CPU|
static_assert|
# declaration primitives
__typeof__\(| __typeof__\(|
union| union|
struct| struct|
@@ -5968,6 +6132,7 @@ sub process {
$dstat !~ /$exceptions/ && $dstat !~ /$exceptions/ &&
$dstat !~ /^\.$Ident\s*=/ && # .foo = $dstat !~ /^\.$Ident\s*=/ && # .foo =
$dstat !~ /^(?:\#\s*$Ident|\#\s*$Constant)\s*$/ && # stringification #foo $dstat !~ /^(?:\#\s*$Ident|\#\s*$Constant)\s*$/ && # stringification #foo
$dstat !~ /^case\b/ && # case ...
$dstat !~ /^do\s*$Constant\s*while\s*$Constant;?$/ && # do {...} while (...); // do {...} while (...) $dstat !~ /^do\s*$Constant\s*while\s*$Constant;?$/ && # do {...} while (...); // do {...} while (...)
$dstat !~ /^while\s*$Constant\s*$Constant\s*$/ && # while (...) {...} $dstat !~ /^while\s*$Constant\s*$Constant\s*$/ && # while (...) {...}
$dstat !~ /^for\s*$Constant$/ && # for (...) $dstat !~ /^for\s*$Constant$/ && # for (...)
@@ -5980,11 +6145,11 @@ sub process {
ERROR("MULTISTATEMENT_MACRO_USE_DO_WHILE", ERROR("MULTISTATEMENT_MACRO_USE_DO_WHILE",
"Macros starting with if should be enclosed by a do - while loop to avoid possible if/else logic defects\n" . "$herectx"); "Macros starting with if should be enclosed by a do - while loop to avoid possible if/else logic defects\n" . "$herectx");
} elsif ($dstat =~ /;/) { } elsif ($dstat =~ /;/) {
ERROR("MULTISTATEMENT_MACRO_USE_DO_WHILE", WARN("MULTISTATEMENT_MACRO_USE_DO_WHILE",
"Macros with multiple statements should be enclosed in a do - while loop\n" . "$herectx"); "Non-declarative macros with multiple statements should be enclosed in a do - while loop\n" . "$herectx\nBUT SEE:\n$DO_WHILE_0_ADVICE");
} else { } else {
ERROR("COMPLEX_MACRO", ERROR("COMPLEX_MACRO",
"Macros with complex values should be enclosed in parentheses\n" . "$herectx"); "Macros with complex values should be enclosed in parentheses\n" . "$herectx\nBUT SEE:\n$DO_WHILE_0_ADVICE");
} }
} }
@@ -6026,6 +6191,12 @@ sub process {
CHK("MACRO_ARG_PRECEDENCE", CHK("MACRO_ARG_PRECEDENCE",
"Macro argument '$arg' may be better as '($arg)' to avoid precedence issues\n" . "$herectx"); "Macro argument '$arg' may be better as '($arg)' to avoid precedence issues\n" . "$herectx");
} }
# check if this is an unused argument
if ($define_stmt !~ /\b$arg\b/ && $define_stmt) {
WARN("MACRO_ARG_UNUSED",
"Argument '$arg' is not used in function-like macro\n" . "$herectx");
}
} }
# check for macros with flow control, but without ## concatenation # check for macros with flow control, but without ## concatenation
@@ -6040,6 +6211,9 @@ sub process {
# check for line continuations outside of #defines, preprocessor #, and asm # check for line continuations outside of #defines, preprocessor #, and asm
} elsif ($realfile =~ m@/vmlinux.lds.h$@) {
$line =~ s/(\w+)/$maybe_linker_symbol{$1}++/ge;
#print "REAL: $realfile\nln: $line\nkeys:", sort keys %maybe_linker_symbol;
} else { } else {
if ($prevline !~ /^..*\\$/ && if ($prevline !~ /^..*\\$/ &&
$line !~ /^\+\s*\#.*\\$/ && # preprocessor $line !~ /^\+\s*\#.*\\$/ && # preprocessor
@@ -6566,11 +6740,11 @@ sub process {
# ignore udelay's < 10, however # ignore udelay's < 10, however
if (! ($delay < 10) ) { if (! ($delay < 10) ) {
CHK("USLEEP_RANGE", CHK("USLEEP_RANGE",
"usleep_range is preferred over udelay; see Documentation/timers/timers-howto.rst\n" . $herecurr); "usleep_range is preferred over udelay; see function description of usleep_range() and udelay().\n" . $herecurr);
} }
if ($delay > 2000) { if ($delay > 2000) {
WARN("LONG_UDELAY", WARN("LONG_UDELAY",
"long udelay - prefer mdelay; see arch/arm/include/asm/delay.h\n" . $herecurr); "long udelay - prefer mdelay; see function description of mdelay().\n" . $herecurr);
} }
} }
@@ -6578,7 +6752,7 @@ sub process {
if ($line =~ /\bmsleep\s*\((\d+)\);/) { if ($line =~ /\bmsleep\s*\((\d+)\);/) {
if ($1 < 20) { if ($1 < 20) {
WARN("MSLEEP", WARN("MSLEEP",
"msleep < 20ms can sleep for up to 20ms; see Documentation/timers/timers-howto.rst\n" . $herecurr); "msleep < 20ms can sleep for up to 20ms; see function description of msleep().\n" . $herecurr);
} }
} }
@@ -6886,7 +7060,7 @@ sub process {
($extension eq "f" && ($extension eq "f" &&
defined $qualifier && $qualifier !~ /^w/) || defined $qualifier && $qualifier !~ /^w/) ||
($extension eq "4" && ($extension eq "4" &&
defined $qualifier && $qualifier !~ /^cc/)) { defined $qualifier && $qualifier !~ /^c(?:[hlbc]|hR)$/)) {
$bad_specifier = $specifier; $bad_specifier = $specifier;
last; last;
} }
@@ -6900,15 +7074,19 @@ sub process {
} }
if ($bad_specifier ne "") { if ($bad_specifier ne "") {
my $stat_real = get_stat_real($linenr, $lc); my $stat_real = get_stat_real($linenr, $lc);
my $msg_level = \&WARN;
my $ext_type = "Invalid"; my $ext_type = "Invalid";
my $use = ""; my $use = "";
if ($bad_specifier =~ /p[Ff]/) { if ($bad_specifier =~ /p[Ff]/) {
$use = " - use %pS instead"; $use = " - use %pS instead";
$use =~ s/pS/ps/ if ($bad_specifier =~ /pf/); $use =~ s/pS/ps/ if ($bad_specifier =~ /pf/);
} elsif ($bad_specifier =~ /pA/) {
$use = " - '%pA' is only intended to be used from Rust code";
$msg_level = \&ERROR;
} }
WARN("VSPRINTF_POINTER_EXTENSION", &{$msg_level}("VSPRINTF_POINTER_EXTENSION",
"$ext_type vsprintf pointer extension '$bad_specifier'$use\n" . "$here\n$stat_real\n"); "$ext_type vsprintf pointer extension '$bad_specifier'$use\n" . "$here\n$stat_real\n");
} }
} }
} }
@@ -6973,6 +7151,25 @@ sub process {
# } # }
# } # }
# ethtool_sprintf uses that should likely be ethtool_puts
if ($line =~ /\bethtool_sprintf\s*\(\s*$FuncArg\s*,\s*$FuncArg\s*\)/) {
if (WARN("PREFER_ETHTOOL_PUTS",
"Prefer ethtool_puts over ethtool_sprintf with only two arguments\n" . $herecurr) &&
$fix) {
$fixed[$fixlinenr] =~ s/\bethtool_sprintf\s*\(\s*($FuncArg)\s*,\s*($FuncArg)/ethtool_puts($1, $7)/;
}
}
# use $rawline because $line loses %s via sanitization and thus we can't match against it.
if ($rawline =~ /\bethtool_sprintf\s*\(\s*$FuncArg\s*,\s*\"\%s\"\s*,\s*$FuncArg\s*\)/) {
if (WARN("PREFER_ETHTOOL_PUTS",
"Prefer ethtool_puts over ethtool_sprintf with standalone \"%s\" specifier\n" . $herecurr) &&
$fix) {
$fixed[$fixlinenr] =~ s/\bethtool_sprintf\s*\(\s*($FuncArg)\s*,\s*"\%s"\s*,\s*($FuncArg)/ethtool_puts($1, $7)/;
}
}
# typecasts on min/max could be min_t/max_t # typecasts on min/max could be min_t/max_t
if ($perl_version_ok && if ($perl_version_ok &&
defined $stat && defined $stat &&
@@ -7005,11 +7202,11 @@ sub process {
my $max = $7; my $max = $7;
if ($min eq $max) { if ($min eq $max) {
WARN("USLEEP_RANGE", WARN("USLEEP_RANGE",
"usleep_range should not use min == max args; see Documentation/timers/timers-howto.rst\n" . "$here\n$stat\n"); "usleep_range should not use min == max args; see function description of usleep_range().\n" . "$here\n$stat\n");
} elsif ($min =~ /^\d+$/ && $max =~ /^\d+$/ && } elsif ($min =~ /^\d+$/ && $max =~ /^\d+$/ &&
$min > $max) { $min > $max) {
WARN("USLEEP_RANGE", WARN("USLEEP_RANGE",
"usleep_range args reversed, use min then max; see Documentation/timers/timers-howto.rst\n" . "$here\n$stat\n"); "usleep_range args reversed, use min then max; see function description of usleep_range().\n" . "$here\n$stat\n");
} }
} }
@@ -7077,6 +7274,21 @@ sub process {
"arguments for function declarations should follow identifier\n" . $herecurr); "arguments for function declarations should follow identifier\n" . $herecurr);
} }
} elsif ($realfile =~ /\.c$/ && defined $stat &&
$stat =~ /^\+extern struct\s+(\w+)\s+(\w+)\[\];/)
{
my ($st_type, $st_name) = ($1, $2);
for my $s (keys %maybe_linker_symbol) {
#print "Linker symbol? $st_name : $s\n";
goto LIKELY_LINKER_SYMBOL
if $st_name =~ /$s/;
}
WARN("AVOID_EXTERNS",
"found a file-scoped extern type:$st_type name:$st_name in .c file\n"
. "is this a linker symbol ?\n" . $herecurr);
LIKELY_LINKER_SYMBOL:
} elsif ($realfile =~ /\.c$/ && defined $stat && } elsif ($realfile =~ /\.c$/ && defined $stat &&
$stat =~ /^.\s*extern\s+/) $stat =~ /^.\s*extern\s+/)
{ {
@@ -7145,14 +7357,16 @@ sub process {
"Prefer $3(sizeof(*$1)...) over $3($4...)\n" . $herecurr); "Prefer $3(sizeof(*$1)...) over $3($4...)\n" . $herecurr);
} }
# check for k[mz]alloc with multiplies that could be kmalloc_array/kcalloc # check for (kv|k)[mz]alloc with multiplies that could be kmalloc_array/kvmalloc_array/kvcalloc/kcalloc
if ($perl_version_ok && if ($perl_version_ok &&
defined $stat && defined $stat &&
$stat =~ /^\+\s*($Lval)\s*\=\s*(?:$balanced_parens)?\s*(k[mz]alloc)\s*\(\s*($FuncArg)\s*\*\s*($FuncArg)\s*,/) { $stat =~ /^\+\s*($Lval)\s*\=\s*(?:$balanced_parens)?\s*((?:kv|k)[mz]alloc)\s*\(\s*($FuncArg)\s*\*\s*($FuncArg)\s*,/) {
my $oldfunc = $3; my $oldfunc = $3;
my $a1 = $4; my $a1 = $4;
my $a2 = $10; my $a2 = $10;
my $newfunc = "kmalloc_array"; my $newfunc = "kmalloc_array";
$newfunc = "kvmalloc_array" if ($oldfunc eq "kvmalloc");
$newfunc = "kvcalloc" if ($oldfunc eq "kvzalloc");
$newfunc = "kcalloc" if ($oldfunc eq "kzalloc"); $newfunc = "kcalloc" if ($oldfunc eq "kzalloc");
my $r1 = $a1; my $r1 = $a1;
my $r2 = $a2; my $r2 = $a2;
@@ -7169,7 +7383,7 @@ sub process {
"Prefer $newfunc over $oldfunc with multiply\n" . $herectx) && "Prefer $newfunc over $oldfunc with multiply\n" . $herectx) &&
$cnt == 1 && $cnt == 1 &&
$fix) { $fix) {
$fixed[$fixlinenr] =~ s/\b($Lval)\s*\=\s*(?:$balanced_parens)?\s*(k[mz]alloc)\s*\(\s*($FuncArg)\s*\*\s*($FuncArg)/$1 . ' = ' . "$newfunc(" . trim($r1) . ', ' . trim($r2)/e; $fixed[$fixlinenr] =~ s/\b($Lval)\s*\=\s*(?:$balanced_parens)?\s*((?:kv|k)[mz]alloc)\s*\(\s*($FuncArg)\s*\*\s*($FuncArg)/$1 . ' = ' . "$newfunc(" . trim($r1) . ', ' . trim($r2)/e;
} }
} }
} }
@@ -7183,7 +7397,7 @@ sub process {
} }
# check for alloc argument mismatch # check for alloc argument mismatch
if ($line =~ /\b((?:devm_)?(?:kcalloc|kmalloc_array))\s*\(\s*sizeof\b/) { if ($line =~ /\b((?:devm_)?((?:k|kv)?(calloc|malloc_array)(?:_node)?))\s*\(\s*sizeof\b/) {
WARN("ALLOC_ARRAY_ARGS", WARN("ALLOC_ARRAY_ARGS",
"$1 uses number as first arg, sizeof is generally wrong\n" . $herecurr); "$1 uses number as first arg, sizeof is generally wrong\n" . $herecurr);
} }
@@ -7211,8 +7425,8 @@ sub process {
# check for IS_ENABLED() without CONFIG_<FOO> ($rawline for comments too) # check for IS_ENABLED() without CONFIG_<FOO> ($rawline for comments too)
if ($rawline =~ /\bIS_ENABLED\s*\(\s*(\w+)\s*\)/ && $1 !~ /^${CONFIG_}/) { if ($rawline =~ /\bIS_ENABLED\s*\(\s*(\w+)\s*\)/ && $1 !~ /^${CONFIG_}/) {
ERROR("IS_ENABLED_CONFIG", WARN("IS_ENABLED_CONFIG",
"IS_ENABLED($1) must be used as IS_ENABLED(${CONFIG_}$1)\n" . $herecurr); "IS_ENABLED($1) is normally used as IS_ENABLED(${CONFIG_}$1)\n" . $herecurr);
} }
# check for #if defined CONFIG_<FOO> || defined CONFIG_<FOO>_MODULE # check for #if defined CONFIG_<FOO> || defined CONFIG_<FOO>_MODULE
@@ -7386,6 +7600,16 @@ sub process {
} }
} }
# check for array definition/declarations that should use flexible arrays instead
if ($sline =~ /^[\+ ]\s*\}(?:\s*__packed)?\s*;\s*$/ &&
$prevline =~ /^\+\s*(?:\}(?:\s*__packed\s*)?|$Type)\s*$Ident\s*\[\s*(0|1)\s*\]\s*;\s*$/) {
if (ERROR("FLEXIBLE_ARRAY",
"Use C99 flexible arrays - see https://docs.kernel.org/process/deprecated.html#zero-length-and-one-element-arrays\n" . $hereprev) &&
$1 == '0' && $fix) {
$fixed[$fixlinenr - 1] =~ s/\[\s*0\s*\]/[]/;
}
}
# nested likely/unlikely calls # nested likely/unlikely calls
if ($line =~ /\b(?:(?:un)?likely)\s*\(\s*!?\s*(IS_ERR(?:_OR_NULL|_VALUE)?|WARN)/) { if ($line =~ /\b(?:(?:un)?likely)\s*\(\s*!?\s*(IS_ERR(?:_OR_NULL|_VALUE)?|WARN)/) {
WARN("LIKELY_MISUSE", WARN("LIKELY_MISUSE",
@@ -7403,6 +7627,30 @@ sub process {
} }
} }
# Complain about RCU Tasks Trace used outside of BPF (and of course, RCU).
our $rcu_trace_funcs = qr{(?x:
rcu_read_lock_trace |
rcu_read_lock_trace_held |
rcu_read_unlock_trace |
call_rcu_tasks_trace |
synchronize_rcu_tasks_trace |
rcu_barrier_tasks_trace |
rcu_request_urgent_qs_task
)};
our $rcu_trace_paths = qr{(?x:
kernel/bpf/ |
include/linux/bpf |
net/bpf/ |
kernel/rcu/ |
include/linux/rcu
)};
if ($line =~ /\b($rcu_trace_funcs)\s*\(/) {
if ($realfile !~ m{^$rcu_trace_paths}) {
WARN("RCU_TASKS_TRACE",
"use of RCU tasks trace is incorrect outside BPF or core RCU code\n" . $herecurr);
}
}
# check for lockdep_set_novalidate_class # check for lockdep_set_novalidate_class
if ($line =~ /^.\s*lockdep_set_novalidate_class\s*\(/ || if ($line =~ /^.\s*lockdep_set_novalidate_class\s*\(/ ||
$line =~ /__lockdep_no_validate__\s*\)/ ) { $line =~ /__lockdep_no_validate__\s*\)/ ) {
@@ -7544,6 +7792,13 @@ sub process {
WARN("MODULE_LICENSE", WARN("MODULE_LICENSE",
"unknown module license " . $extracted_string . "\n" . $herecurr); "unknown module license " . $extracted_string . "\n" . $herecurr);
} }
if (!$file && $extracted_string eq '"GPL v2"') {
if (WARN("MODULE_LICENSE",
"Prefer \"GPL\" over \"GPL v2\" - see commit bf7fbeeae6db (\"module: Cure the MODULE_LICENSE \"GPL\" vs. \"GPL v2\" bogosity\")\n" . $herecurr) &&
$fix) {
$fixed[$fixlinenr] =~ s/\bMODULE_LICENSE\s*\(\s*"GPL v2"\s*\)/MODULE_LICENSE("GPL")/;
}
}
} }
# check for sysctl duplicate constants # check for sysctl duplicate constants
@@ -7551,6 +7806,31 @@ sub process {
WARN("DUPLICATED_SYSCTL_CONST", WARN("DUPLICATED_SYSCTL_CONST",
"duplicated sysctl range checking value '$1', consider using the shared one in include/linux/sysctl.h\n" . $herecurr); "duplicated sysctl range checking value '$1', consider using the shared one in include/linux/sysctl.h\n" . $herecurr);
} }
# Check that *_device_id tables have sentinel entries.
if (defined $stat && $line =~ /struct\s+$dev_id_types\s+\w+\s*\[\s*\]\s*=\s*\{/) {
my $stripped = $stat;
# Strip diff line prefixes.
$stripped =~ s/(^|\n)./$1/g;
# Line continuations.
$stripped =~ s/\\\n/\n/g;
# Strip whitespace, empty strings, zeroes, and commas.
$stripped =~ s/""//g;
$stripped =~ s/0x0//g;
$stripped =~ s/[\s$;,0]//g;
# Strip field assignments.
$stripped =~ s/\.$Ident=//g;
if (!(substr($stripped, -4) eq "{}};" ||
substr($stripped, -6) eq "{{}}};" ||
$stripped =~ /ISAPNP_DEVICE_SINGLE_END}};$/ ||
$stripped =~ /ISAPNP_CARD_END}};$/ ||
$stripped =~ /NULL};$/ ||
$stripped =~ /PCMCIA_DEVICE_NULL};$/)) {
ERROR("MISSING_SENTINEL", "missing sentinel in ID array\n" . "$here\n$stat\n");
}
}
} }
# If we have no input at all, then there is nothing to report on # If we have no input at all, then there is nothing to report on
@@ -7575,6 +7855,12 @@ sub process {
ERROR("NOT_UNIFIED_DIFF", ERROR("NOT_UNIFIED_DIFF",
"Does not appear to be a unified-diff format patch\n"); "Does not appear to be a unified-diff format patch\n");
} }
if ($is_patch && $has_commit_log && $chk_fixes_tag) {
if ($needs_fixes_tag ne "" && !$is_revert && !$fixes_tag) {
WARN("MISSING_FIXES_TAG",
"The commit message has '$needs_fixes_tag', perhaps it also needs a 'Fixes:' tag?\n");
}
}
if ($is_patch && $has_commit_log && $chk_signoff) { if ($is_patch && $has_commit_log && $chk_signoff) {
if ($signoff == 0) { if ($signoff == 0) {
ERROR("MISSING_SIGN_OFF", ERROR("MISSING_SIGN_OFF",

View File

@@ -6,6 +6,7 @@ from argparse import ArgumentParser
from ply import lex, yacc from ply import lex, yacc
import locale import locale
import traceback import traceback
import fnmatch
import sys import sys
import git import git
import re import re
@@ -28,6 +29,21 @@ class SPDXdata(object):
self.licenses = [ ] self.licenses = [ ]
self.exceptions = { } self.exceptions = { }
class dirinfo(object):
def __init__(self):
self.missing = 0
self.total = 0
self.files = []
def update(self, fname, basedir, miss):
self.total += 1
self.missing += miss
if miss:
fname = './' + fname
bdir = os.path.dirname(fname)
if bdir == basedir.rstrip('/'):
self.files.append(fname)
# Read the spdx data from the LICENSES directory # Read the spdx data from the LICENSES directory
def read_spdxdata(repo): def read_spdxdata(repo):
@@ -91,11 +107,25 @@ class id_parser(object):
self.parser = yacc.yacc(module = self, write_tables = False, debug = False) self.parser = yacc.yacc(module = self, write_tables = False, debug = False)
self.lines_checked = 0 self.lines_checked = 0
self.checked = 0 self.checked = 0
self.excluded = 0
self.spdx_valid = 0 self.spdx_valid = 0
self.spdx_errors = 0 self.spdx_errors = 0
self.spdx_dirs = {}
self.dirdepth = -1
self.basedir = '.'
self.curline = 0 self.curline = 0
self.deepest = 0 self.deepest = 0
def set_dirinfo(self, basedir, dirdepth):
if dirdepth >= 0:
self.basedir = basedir
bdir = basedir.lstrip('./').rstrip('/')
if bdir != '':
parts = bdir.split('/')
else:
parts = []
self.dirdepth = dirdepth + len(parts)
# Validate License and Exception IDs # Validate License and Exception IDs
def validate(self, tok): def validate(self, tok):
id = tok.value.upper() id = tok.value.upper()
@@ -167,6 +197,7 @@ class id_parser(object):
def parse_lines(self, fd, maxlines, fname): def parse_lines(self, fd, maxlines, fname):
self.checked += 1 self.checked += 1
self.curline = 0 self.curline = 0
fail = 1
try: try:
for line in fd: for line in fd:
line = line.decode(locale.getpreferredencoding(False), errors='ignore') line = line.decode(locale.getpreferredencoding(False), errors='ignore')
@@ -183,15 +214,22 @@ class id_parser(object):
# Remove trailing xml comment closure # Remove trailing xml comment closure
if line.strip().endswith('-->'): if line.strip().endswith('-->'):
expr = expr.rstrip('-->').strip() expr = expr.rstrip('-->').strip()
# Remove trailing Jinja2 comment closure
if line.strip().endswith('#}'):
expr = expr.rstrip('#}').strip()
# Special case for SH magic boot code files # Special case for SH magic boot code files
if line.startswith('LIST \"'): if line.startswith('LIST \"'):
expr = expr.rstrip('\"').strip() expr = expr.rstrip('\"').strip()
# Remove j2 comment closure
if line.startswith('{#'):
expr = expr.rstrip('#}').strip()
self.parse(expr) self.parse(expr)
self.spdx_valid += 1 self.spdx_valid += 1
# #
# Should we check for more SPDX ids in the same file and # Should we check for more SPDX ids in the same file and
# complain if there are any? # complain if there are any?
# #
fail = 0
break break
except ParserException as pe: except ParserException as pe:
@@ -200,31 +238,105 @@ class id_parser(object):
tok = pe.tok.value tok = pe.tok.value
sys.stdout.write('%s: %d:%d %s: %s\n' %(fname, self.curline, col, pe.txt, tok)) sys.stdout.write('%s: %d:%d %s: %s\n' %(fname, self.curline, col, pe.txt, tok))
else: else:
sys.stdout.write('%s: %d:0 %s\n' %(fname, self.curline, col, pe.txt)) sys.stdout.write('%s: %d:0 %s\n' %(fname, self.curline, pe.txt))
self.spdx_errors += 1 self.spdx_errors += 1
def scan_git_tree(tree): if fname == '-':
return
base = os.path.dirname(fname)
if self.dirdepth > 0:
parts = base.split('/')
i = 0
base = '.'
while i < self.dirdepth and i < len(parts) and len(parts[i]):
base += '/' + parts[i]
i += 1
elif self.dirdepth == 0:
base = self.basedir
else:
base = './' + base.rstrip('/')
base += '/'
di = self.spdx_dirs.get(base, dirinfo())
di.update(fname, base, fail)
self.spdx_dirs[base] = di
class pattern(object):
def __init__(self, line):
self.pattern = line
self.match = self.match_file
if line == '.*':
self.match = self.match_dot
elif line.endswith('/'):
self.pattern = line[:-1]
self.match = self.match_dir
elif line.startswith('/'):
self.pattern = line[1:]
self.match = self.match_fn
def match_dot(self, fpath):
return os.path.basename(fpath).startswith('.')
def match_file(self, fpath):
return os.path.basename(fpath) == self.pattern
def match_fn(self, fpath):
return fnmatch.fnmatchcase(fpath, self.pattern)
def match_dir(self, fpath):
if self.match_fn(os.path.dirname(fpath)):
return True
return fpath.startswith(self.pattern)
def exclude_file(fpath):
for rule in exclude_rules:
if rule.match(fpath):
return True
return False
def scan_git_tree(tree, basedir, dirdepth):
parser.set_dirinfo(basedir, dirdepth)
for el in tree.traverse(): for el in tree.traverse():
# Exclude stuff which would make pointless noise
# FIXME: Put this somewhere more sensible
if el.path.startswith("LICENSES"):
continue
if el.path.find("license-rules.rst") >= 0:
continue
if not os.path.isfile(el.path): if not os.path.isfile(el.path):
continue continue
if exclude_file(el.path):
parser.excluded += 1
continue
with open(el.path, 'rb') as fd: with open(el.path, 'rb') as fd:
parser.parse_lines(fd, args.maxlines, el.path) parser.parse_lines(fd, args.maxlines, el.path)
def scan_git_subtree(tree, path): def scan_git_subtree(tree, path, dirdepth):
for p in path.strip('/').split('/'): for p in path.strip('/').split('/'):
tree = tree[p] tree = tree[p]
scan_git_tree(tree) scan_git_tree(tree, path.strip('/'), dirdepth)
def read_exclude_file(fname):
rules = []
if not fname:
return rules
with open(fname) as fd:
for line in fd:
line = line.strip()
if line.startswith('#'):
continue
if not len(line):
continue
rules.append(pattern(line))
return rules
if __name__ == '__main__': if __name__ == '__main__':
ap = ArgumentParser(description='SPDX expression checker') ap = ArgumentParser(description='SPDX expression checker')
ap.add_argument('path', nargs='*', help='Check path or file. If not given full git tree scan. For stdin use "-"') ap.add_argument('path', nargs='*', help='Check path or file. If not given full git tree scan. For stdin use "-"')
ap.add_argument('-d', '--dirs', action='store_true',
help='Show [sub]directory statistics.')
ap.add_argument('-D', '--depth', type=int, default=-1,
help='Directory depth for -d statistics. Default: unlimited')
ap.add_argument('-e', '--exclude',
help='File containing file patterns to exclude. Default: scripts/spdxexclude')
ap.add_argument('-f', '--files', action='store_true',
help='Show files without SPDX.')
ap.add_argument('-m', '--maxlines', type=int, default=15, ap.add_argument('-m', '--maxlines', type=int, default=15,
help='Maximum number of lines to scan in a file. Default 15') help='Maximum number of lines to scan in a file. Default 15')
ap.add_argument('-v', '--verbose', action='store_true', help='Verbose statistics output') ap.add_argument('-v', '--verbose', action='store_true', help='Verbose statistics output')
@@ -258,6 +370,15 @@ if __name__ == '__main__':
sys.stderr.write('%s\n' %traceback.format_exc()) sys.stderr.write('%s\n' %traceback.format_exc())
sys.exit(1) sys.exit(1)
try:
fname = args.exclude
if not fname:
fname = os.path.join(os.path.dirname(__file__), 'spdxexclude')
exclude_rules = read_exclude_file(fname)
except Exception as ex:
sys.stderr.write('FAIL: Reading exclude file %s: %s\n' %(fname, ex))
sys.exit(1)
try: try:
if len(args.path) and args.path[0] == '-': if len(args.path) and args.path[0] == '-':
stdin = os.fdopen(sys.stdin.fileno(), 'rb') stdin = os.fdopen(sys.stdin.fileno(), 'rb')
@@ -268,13 +389,21 @@ if __name__ == '__main__':
if os.path.isfile(p): if os.path.isfile(p):
parser.parse_lines(open(p, 'rb'), args.maxlines, p) parser.parse_lines(open(p, 'rb'), args.maxlines, p)
elif os.path.isdir(p): elif os.path.isdir(p):
scan_git_subtree(repo.head.reference.commit.tree, p) scan_git_subtree(repo.head.reference.commit.tree, p,
args.depth)
else: else:
sys.stderr.write('path %s does not exist\n' %p) sys.stderr.write('path %s does not exist\n' %p)
sys.exit(1) sys.exit(1)
else: else:
# Full git tree scan # Full git tree scan
scan_git_tree(repo.head.commit.tree) scan_git_tree(repo.head.commit.tree, '.', args.depth)
ndirs = len(parser.spdx_dirs)
dirsok = 0
if ndirs:
for di in parser.spdx_dirs.values():
if not di.missing:
dirsok += 1
if args.verbose: if args.verbose:
sys.stderr.write('\n') sys.stderr.write('\n')
@@ -283,10 +412,41 @@ if __name__ == '__main__':
sys.stderr.write('License IDs %12d\n' %len(spdx.licenses)) sys.stderr.write('License IDs %12d\n' %len(spdx.licenses))
sys.stderr.write('Exception IDs %12d\n' %len(spdx.exceptions)) sys.stderr.write('Exception IDs %12d\n' %len(spdx.exceptions))
sys.stderr.write('\n') sys.stderr.write('\n')
sys.stderr.write('Files excluded: %12d\n' %parser.excluded)
sys.stderr.write('Files checked: %12d\n' %parser.checked) sys.stderr.write('Files checked: %12d\n' %parser.checked)
sys.stderr.write('Lines checked: %12d\n' %parser.lines_checked) sys.stderr.write('Lines checked: %12d\n' %parser.lines_checked)
sys.stderr.write('Files with SPDX: %12d\n' %parser.spdx_valid) if parser.checked:
pc = int(100 * parser.spdx_valid / parser.checked)
sys.stderr.write('Files with SPDX: %12d %3d%%\n' %(parser.spdx_valid, pc))
missing = parser.checked - parser.spdx_valid
mpc = int(100 * missing / parser.checked)
sys.stderr.write('Files without SPDX:%12d %3d%%\n' %(missing, mpc))
sys.stderr.write('Files with errors: %12d\n' %parser.spdx_errors) sys.stderr.write('Files with errors: %12d\n' %parser.spdx_errors)
if ndirs:
sys.stderr.write('\n')
sys.stderr.write('Directories accounted: %8d\n' %ndirs)
pc = int(100 * dirsok / ndirs)
sys.stderr.write('Directories complete: %8d %3d%%\n' %(dirsok, pc))
if ndirs and ndirs != dirsok and args.dirs:
if args.verbose:
sys.stderr.write('\n')
sys.stderr.write('Incomplete directories: SPDX in Files\n')
for f in sorted(parser.spdx_dirs.keys()):
di = parser.spdx_dirs[f]
if di.missing:
valid = di.total - di.missing
pc = int(100 * valid / di.total)
sys.stderr.write(' %-80s: %5d of %5d %3d%%\n' %(f, valid, di.total, pc))
if ndirs and ndirs != dirsok and args.files:
if args.verbose or args.dirs:
sys.stderr.write('\n')
sys.stderr.write('Files without SPDX:\n')
for f in sorted(parser.spdx_dirs.keys()):
di = parser.spdx_dirs[f]
for f in sorted(di.files):
sys.stderr.write(' %s\n' %f)
sys.exit(0) sys.exit(0)