NetworkManager/contrib/scripts/checkpatch.pl

320 lines
10 KiB
Perl
Executable file

#!/usr/bin/perl -n
# SPDX-License-Identifier: GPL-2.0-or-later
#
# Copyright (C) 2018,2021 Red Hat, Inc.
#
# $ perldoc checkpatch.pl for eye-pleasing view of the manual:
=head1 NAME
checkpatch.pl - check for common mistakes
=head1 SYNOPSIS
checkpatch.pl [<file> ...]
=head1 DESCRIPTION
B<checkpatch.pl> checks source files or patches for common mistakes.
=head1 OPTIONS
=over 4
=item B<< <file> >>
A C source file or an unified diff.
=back
=cut
use strict;
use warnings;
chomp;
our $is_patch;
our $is_file;
our $is_commit_message;
our $seen_error;
our $line; # Current line
our $check_line; # Complain if errors are found on this line
our @functions_seen;
our $type;
our $filename;
our $line_no;
our $indent;
our $check_is_todo;
our $expect_spdx;
our $subdir;
sub new_hunk
{
$type = undef;
$indent = undef;
}
sub new_file
{
$expect_spdx = 0;
$check_is_todo = 1;
$filename = $subdir // '';
$filename .= shift;
@functions_seen = ();
}
my $header = $ENV{'NM_CHECKPATCH_HEADER'};
sub complain
{
my $message = shift;
my $plain_message = shift;
return unless $check_line;
if (defined($header)) {
warn "$header\n";
undef $header;
}
if ($plain_message) {
warn "$message\n";
} else {
warn "$filename:$line_no: $message:\n";
warn "> $line\n\n";
}
$seen_error = 1;
}
sub check_commit
{
my $commit = shift;
my $required = shift;
my $commit_id;
my $commit_message;
if ($commit =~ /^([0-9a-f]{5,})\b/) {
$commit_id = $1;
} else {
return unless $required;
}
if ($commit_id and not system 'git rev-parse --git-dir >/dev/null 2>/dev/null') {
$commit_message = `git log --abbrev=12 --pretty=format:"%h ('%s')" -1 "$commit_id" 2>/dev/null`;
complain "Commit '$commit_id' does not seem to exist" unless $commit_message;
}
$commit_message //= "<12 hex digits> ('<commit subject>')";
complain "Refer to the commit id properly: $commit_message" unless $commit =~ /^[0-9a-f]{12} \('/;
}
if ($is_patch) {
# This is a line of an unified diff
if (/^@@.*\+(\d+)/) {
$line_no = $1 - 1;
new_hunk;
next;
}
if (/^\+\+\+ (b\/)?(.*)/) {
new_file ($2);
next;
}
s/^([ \+])(.*)/$2/ or next;
$line_no++;
$check_line = $1 eq '+';
$line = $2;
} elsif ($is_file) {
$line_no = $.;
$. = 0 if eof;
# This is a line from full C file
$check_line = 1;
$line = $_;
} elsif ($is_commit_message) {
$line_no++;
$filename = '(commit message)';
$check_line = 1;
$line = $_;
/^---$/ and $is_commit_message = 0;
/^(Reverts|Fixes): *(.*)/ and check_commit ($2, 1);
/This reverts commit/ and next;
/cherry picked from/ and next;
/^git-subtree-dir: (.*)/ and $subdir = "$1/";
/\bcommit (.*)/ and check_commit ($1, 0);
next;
} else {
# We don't handle these yet
/^diff --cc/ and exit 0;
$filename = '';
$line_no = 1;
# We don't know if we're dealing with a patch or a C file yet
$is_commit_message = 1 if /^From \S/;
$is_file = 1 if /^#/;
$is_patch = 1 if /^---/;
next;
}
if ($is_file and $filename ne $ARGV) {
new_file ($ARGV);
new_hunk;
}
if ($filename !~ /\.[ch]$/) {
if ($check_is_todo) {
complain("Resolve todo list \"$filename\" first\n", 1) if $filename =~ /^TODO.txt$/;
$check_is_todo = 0;
}
next;
}
next if $filename =~ /\/nm-[^\/]+-enum-types\.[ch]$/;
next if $filename =~ /\b(shared|src)\/systemd\//
and not $filename =~ /\/sd-adapt\//
and not $filename =~ /\/nm-/;
next if $filename =~ /\/(n-acd|c-list|c-siphash|n-dhcp4)\//;
$expect_spdx = 1 if $line_no == 1;
$expect_spdx = 0 if $line =~ /SPDX-License-Identifier/;
complain ('Missing a SPDX-License-Identifier') if $line_no == 2 and $expect_spdx;
complain ('Tabs are only allowed at the beginning of a line') if $line =~ /[^\t]\t/;
complain ('Trailing whitespace') if $line =~ /[ \t]$/;
complain ('Don\'t use glib typedefs for char/short/int/long/float/double') if $line =~ /\bg(char|short|int|long|float|double)\b/;
complain ("Don't use \"$1 $2\" instead of \"$2 $1\"") if $line =~ /\b(char|short|int|long) +(unsigned|signed)\b/;
complain ("Don't use \"unsigned int\" but just use \"unsigned\"") if $line =~ /\b(unsigned) +(int)\b/;
complain ("Please use LGPL-2.1-or-later SPDX tag for new files") if $is_patch and $line =~ /SPDX-License-Identifier/ and not /LGPL-2.1-or-later/;
complain ("Use a SPDX-License-Identifier instead of Licensing boilerplate") if $is_patch and $line =~ /under the terms of/;
complain ("Don't use space inside elvis operator ?:") if $line =~ /\?[\t ]+:/;
complain ("Don't add Emacs editor formatting hints to source files") if $line_no == 1 and $line =~ /-\*-.+-\*-/;
complain ("XXX marker are reserved for development while work-in-progress. Use TODO or FIXME comment instead?") if $line =~ /\bXXX\b/;
complain ("This gtk-doc annotation looks wrong") if $line =~ /\*.*\( *(transfer-(none|container|full)|allow none) *\) *(:|\()/;
complain ("The gtk-doc annotation (allow-none) is deprecated. Use either (nullable) and/or (optional). See https://gi.readthedocs.io/en/latest/annotations/giannotations.html#deprecated-gobject-introspection-annotations") if $line =~ /\*.*\( *(allow-none) *\) *(:|\()/;
complain ("Prefer nm_assert() or g_return*() to g_assert*()") if $line =~ /g_assert/ and (not $filename =~ /\/tests\//) and (not $filename =~ /\/nm-test-/);
complain ("Use gs_free_error with GError variables") if $line =~ /\bgs_free\b +GError *\*/;
complain ("Initialize GError variables to NULL, if you pass them on") if $line =~ /\bGError +\*([a-z0-9_]+);/;
complain ("Don't use strcmp/g_strcmp0 unless you need to sort. Consider nm_streq()/nm_streq0(),NM_IN_STRSET() for testing equality") if $line =~ /\b(strcmp|g_strcmp0)\b/;
complain ("Don't use API that uses the numeric source id. Instead, use GSource and API like nm_g_idle_add(), nm_g_idle_add_source(), nm_clear_g_source_inst(), etc.") if $line =~ /\b(g_idle_add|g_idle_add_full|g_timeout_add|g_timeout_add_seconds|g_source_remove|nm_clear_g_source)\b/;
complain ("Prefer g_snprintf() over snprintf() (for consistency)") if $line =~ /\b(snprintf)\b/;
complain ("Prefer nm_str_hash()/nm_direct_hash() over g_str_hash()/g_direct_hash(). Those use siphash24") if $line =~ /\b(g_str_hash|g_direct_hash)\b/;
complain ("Don't use g_direct_equal() for hash tables, pass NULL for pointer equality which avoids the function call") if $line =~ /\b(g_direct_equal)\b/;
complain ("Prefer nm_pint_hash()/nm_pint64_hash()/nm_pdouble_hash() over g_int_hash()/g_int64_hash()/g_double_hash(). Those use siphash24") if $line =~ /\b(g_int_hash|g_int64_hash|g_double_hash)\b/;
complain ("Prefer nm_pint_equal()/nm_pint64_equal()/nm_pdouble_equal() over g_int_equal()/g_int64_equal()/g_double_equal(). Those names mirror our nm_p*_hash() functions") if $line =~ /\b(g_int_equal|g_int64_equal|g_double_equal)\b/;
complain ("Avoid g_clear_pointer() and use nm_clear_pointer() (or nm_clear_g_free(), g_clear_object(), etc.)") if $line =~ /\b(g_clear_pointer)\b/;
complain ("Define setting properties with _nm_setting_property_define_direct_*() API") if $line =~ /g_param_spec_/ and $filename =~ /\/libnm-core-impl\/nm-setting/;
complain ("Use nm_g_array_{index,first,last,index_p}() instead of g_array_index(), as it nm_assert()s for valid element size and out-of-bound access") if $line =~ /\bg_array_index\b/;
complain ("Use spaces instead of tabs") if $line =~ /\t/;
complain ("Prefer implementing private pointers via _NM_GET_PRIVATE() or _NM_GET_PRIVATE_PTR() (the latter, if the private data has an opqaue pointer in the header file)") if $line =~ /\b(g_type_class_add_private|G_TYPE_INSTANCE_GET_PRIVATE)\b/;
complain ("Don't use close()/g_close(). Instead, use nm_close() (or nm_close_with_error()).") if $line =~ /\b(close|g_close)\b *\(/;
complain ("Use nm_memdup() instead of g_memdup(). The latter has a size argument of type guint") if $line =~ /\bg_memdup\b/;
# Further on we process stuff without comments.
$_ = $line;
s/\s*\/\*.*\*\///;
s/\s*\/\*.*//;
s/\s*\/\/.*//;
/^\s* \* / and next;
if (/^typedef*/) {
# We expect the { on the same line as the typedef. Otherwise it
# looks too much like a function declaration
complain ('Unexpected line break following a typedef') unless /[;{,]$/;
next;
} elsif (/^[A-Za-z_][A-Za-z0-9_ ]*\*?$/ and /[a-z]/) {
# A function type
$type = $_;
next;
} elsif ($type and /^([A-Za-z_][A-Za-z0-9_]*)(\s*)\(/) {
my @order = qw/^get_property$ ^set_property$ (?<!_iface|_class)_init$ ^constructor$
^constructed$ _new$ ^dispose$ ^finalize$ _class_init$/;
my @following = ();
my @tmp = ();
# A function name
my $name = $1;
complain ('No space between function name and arguments') unless $2 eq '';
# Determine which function must not be preceding this one
foreach my $func (reverse @order) {
if ($name =~ /$func/) {
@following = @tmp;
last;
}
push @tmp, $func;
}
# Check if an out-of-order function was seen
foreach my $func (@following) {
my @wrong = grep { /$func/ } @functions_seen;
complain (join (', ', map { "'$_'" } @wrong)." should follow '$name'") if @wrong;
}
push @functions_seen, $1;
$type = undef;
next;
}
if ($type) {
# We've seen what looked like a type in a function declaration,
# but the function declaration didn't follow.
if ($type =~ /^(struct|union)/ and $line eq '{') {
complain ("Brace should be one the same line as the '$type' declaration");
} else {
complain ("Expected a function declaration following '$type', but found something else");
}
$type = undef;
}
END {
if ($seen_error) {
warn "The patch does not validate.\n" if $is_patch;
warn "The file does not validate.\n" if $is_file;
$? = 1
}
};
=head1 EXAMPLES
=over
=item B<checkpatch.pl hello.c>
Check a single file.
=item B<git diff --cached |checkpatch.pl>
Check the currently staged changes.
=item B<git format-patch -U65535 --stdout -1 |contrib/scripts/checkpatch.pl || :>
A F<.git/hooks/post-commit> oneliner that, wisely, tolerates failures while
still providing advice. The large line context allows helps checkpatch.pl
get a better idea about the changes in context of code that does not change.
=back
=head1 BUGS
Proabably too many.
=head1 SEE ALSO
F<CONTRIBUTING>
=head1 COPYRIGHT
Copyright (C) 2018,2021 Red Hat
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
=head1 AUTHOR
Lubomir Rintel C<lkundrak@v3.sk>
=cut