mirror of
https://gitlab.freedesktop.org/NetworkManager/NetworkManager
synced 2024-10-14 20:18:39 +00:00
320 lines
10 KiB
Perl
Executable file
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
|