Path: blob/main/sys/contrib/openzfs/scripts/cstyle.pl
48261 views
#!/usr/bin/env perl1# SPDX-License-Identifier: CDDL-1.02#3# CDDL HEADER START4#5# The contents of this file are subject to the terms of the6# Common Development and Distribution License (the "License").7# You may not use this file except in compliance with the License.8#9# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE10# or https://opensource.org/licenses/CDDL-1.0.11# See the License for the specific language governing permissions12# and limitations under the License.13#14# When distributing Covered Code, include this CDDL HEADER in each15# file and include the License file at usr/src/OPENSOLARIS.LICENSE.16# If applicable, add the following below this CDDL HEADER, with the17# fields enclosed by brackets "[]" replaced with your own identifying18# information: Portions Copyright [yyyy] [name of copyright owner]19#20# CDDL HEADER END21#22# Copyright 2016 Nexenta Systems, Inc.23#24# Copyright 2008 Sun Microsystems, Inc. All rights reserved.25# Use is subject to license terms.26#27# @(#)cstyle 1.58 98/09/09 (from shannon)28#ident "%Z%%M% %I% %E% SMI"29#30# cstyle - check for some common stylistic errors.31#32# cstyle is a sort of "lint" for C coding style.33# It attempts to check for the style used in the34# kernel, sometimes known as "Bill Joy Normal Form".35#36# There's a lot this can't check for, like proper indentation37# of code blocks. There's also a lot more this could check for.38#39# A note to the non perl literate:40#41# perl regular expressions are pretty much like egrep42# regular expressions, with the following special symbols43#44# \s any space character45# \S any non-space character46# \w any "word" character [a-zA-Z0-9_]47# \W any non-word character48# \d a digit [0-9]49# \D a non-digit50# \b word boundary (between \w and \W)51# \B non-word boundary52#5354require 5.0;55use warnings;56use IO::File;57use Getopt::Std;58use strict;5960my $usage =61"usage: cstyle [-cgpvP] file...62-c check continuation indentation inside functions63-g print github actions' workflow commands64-p perform some of the more picky checks65-v verbose66-P check for use of non-POSIX types67";6869my %opts;7071if (!getopts("cghpvCP", \%opts)) {72print $usage;73exit 2;74}7576my $check_continuation = $opts{'c'};77my $github_workflow = $opts{'g'} || $ENV{'CI'};78my $picky = $opts{'p'};79my $verbose = $opts{'v'};80my $check_posix_types = $opts{'P'};8182my ($filename, $line, $prev); # shared globals8384my $fmt;85my $hdr_comment_start;8687if ($verbose) {88$fmt = "%s: %d: %s\n%s\n";89} else {90$fmt = "%s: %d: %s\n";91}9293$hdr_comment_start = qr/^\s*\/\*$/;9495# Note, following must be in single quotes so that \s and \w work right.96my $typename = '(int|char|short|long|unsigned|float|double' .97'|\w+_t|struct\s+\w+|union\s+\w+|FILE)';9899# mapping of old types to POSIX compatible types100my %old2posix = (101'unchar' => 'uchar_t',102'ushort' => 'ushort_t',103'uint' => 'uint_t',104'ulong' => 'ulong_t',105'u_int' => 'uint_t',106'u_short' => 'ushort_t',107'u_long' => 'ulong_t',108'u_char' => 'uchar_t',109'quad' => 'quad_t'110);111112my $lint_re = qr/\/\*(?:113NOTREACHED|LINTLIBRARY|VARARGS[0-9]*|114CONSTCOND|CONSTANTCOND|CONSTANTCONDITION|EMPTY|115FALLTHRU|FALLTHROUGH|LINTED.*?|PRINTFLIKE[0-9]*|116PROTOLIB[0-9]*|SCANFLIKE[0-9]*|CSTYLED.*?117)\*\//x;118119my $warlock_re = qr/\/\*\s*(?:120VARIABLES\ PROTECTED\ BY|121MEMBERS\ PROTECTED\ BY|122ALL\ MEMBERS\ PROTECTED\ BY|123READ-ONLY\ VARIABLES:|124READ-ONLY\ MEMBERS:|125VARIABLES\ READABLE\ WITHOUT\ LOCK:|126MEMBERS\ READABLE\ WITHOUT\ LOCK:|127LOCKS\ COVERED\ BY|128LOCK\ UNNEEDED\ BECAUSE|129LOCK\ NEEDED:|130LOCK\ HELD\ ON\ ENTRY:|131READ\ LOCK\ HELD\ ON\ ENTRY:|132WRITE\ LOCK\ HELD\ ON\ ENTRY:|133LOCK\ ACQUIRED\ AS\ SIDE\ EFFECT:|134READ\ LOCK\ ACQUIRED\ AS\ SIDE\ EFFECT:|135WRITE\ LOCK\ ACQUIRED\ AS\ SIDE\ EFFECT:|136LOCK\ RELEASED\ AS\ SIDE\ EFFECT:|137LOCK\ UPGRADED\ AS\ SIDE\ EFFECT:|138LOCK\ DOWNGRADED\ AS\ SIDE\ EFFECT:|139FUNCTIONS\ CALLED\ THROUGH\ POINTER|140FUNCTIONS\ CALLED\ THROUGH\ MEMBER|141LOCK\ ORDER:142)/x;143144my $err_stat = 0; # exit status145146if ($#ARGV >= 0) {147foreach my $arg (@ARGV) {148my $fh = new IO::File $arg, "r";149if (!defined($fh)) {150printf "%s: can not open\n", $arg;151} else {152&cstyle($arg, $fh);153close $fh;154}155}156} else {157&cstyle("<stdin>", *STDIN);158}159exit $err_stat;160161my $no_errs = 0; # set for CSTYLED-protected lines162163sub err($) {164my ($error) = @_;165unless ($no_errs) {166if ($verbose) {167printf $fmt, $filename, $., $error, $line;168} else {169printf $fmt, $filename, $., $error;170}171if ($github_workflow) {172printf "::error file=%s,line=%s::%s\n", $filename, $., $error;173}174$err_stat = 1;175}176}177178sub err_prefix($$) {179my ($prevline, $error) = @_;180my $out = $prevline."\n".$line;181unless ($no_errs) {182if ($verbose) {183printf $fmt, $filename, $., $error, $out;184} else {185printf $fmt, $filename, $., $error;186}187$err_stat = 1;188}189}190191sub err_prev($) {192my ($error) = @_;193unless ($no_errs) {194if ($verbose) {195printf $fmt, $filename, $. - 1, $error, $prev;196} else {197printf $fmt, $filename, $. - 1, $error;198}199$err_stat = 1;200}201}202203sub cstyle($$) {204205my ($fn, $filehandle) = @_;206$filename = $fn; # share it globally207208my $in_cpp = 0;209my $next_in_cpp = 0;210211my $in_comment = 0;212my $comment_done = 0;213my $in_warlock_comment = 0;214my $in_macro_call = 0;215my $in_function = 0;216my $in_function_header = 0;217my $function_header_full_indent = 0;218my $in_declaration = 0;219my $note_level = 0;220my $nextok = 0;221my $nocheck = 0;222223my $in_string = 0;224225my ($okmsg, $comment_prefix);226227$line = '';228$prev = '';229reset_indent();230231line: while (<$filehandle>) {232s/\r?\n$//; # strip return and newline233234# save the original line, then remove all text from within235# double or single quotes, we do not want to check such text.236237$line = $_;238239#240# C allows strings to be continued with a backslash at the end of241# the line. We translate that into a quoted string on the previous242# line followed by an initial quote on the next line.243#244# (we assume that no-one will use backslash-continuation with character245# constants)246#247$_ = '"' . $_ if ($in_string && !$nocheck && !$in_comment);248249#250# normal strings and characters251#252s/'([^\\']|\\[^xX0]|\\0[0-9]*|\\[xX][0-9a-fA-F]*)'/''/g;253s/"([^\\"]|\\.)*"/\"\"/g;254255#256# detect string continuation257#258if ($nocheck || $in_comment) {259$in_string = 0;260} else {261#262# Now that all full strings are replaced with "", we check263# for unfinished strings continuing onto the next line.264#265$in_string =266(s/([^"](?:"")*)"([^\\"]|\\.)*\\$/$1""/ ||267s/^("")*"([^\\"]|\\.)*\\$/""/);268}269270#271# figure out if we are in a cpp directive272#273$in_cpp = $next_in_cpp || /^\s*#/; # continued or started274$next_in_cpp = $in_cpp && /\\$/; # only if continued275276# strip off trailing backslashes, which appear in long macros277s/\s*\\$//;278279# an /* END CSTYLED */ comment ends a no-check block.280if ($nocheck) {281if (/\/\* *END *CSTYLED *\*\//) {282$nocheck = 0;283} else {284reset_indent();285next line;286}287}288289# a /*CSTYLED*/ comment indicates that the next line is ok.290if ($nextok) {291if ($okmsg) {292err($okmsg);293}294$nextok = 0;295$okmsg = 0;296if (/\/\* *CSTYLED.*\*\//) {297/^.*\/\* *CSTYLED *(.*) *\*\/.*$/;298$okmsg = $1;299$nextok = 1;300}301$no_errs = 1;302} elsif ($no_errs) {303$no_errs = 0;304}305306# check length of line.307# first, a quick check to see if there is any chance of being too long.308if (($line =~ tr/\t/\t/) * 7 + length($line) > 80) {309# yes, there is a chance.310# replace tabs with spaces and check again.311my $eline = $line;3121 while $eline =~313s/\t+/' ' x (length($&) * 8 - length($`) % 8)/e;314if (length($eline) > 80) {315err("line > 80 characters");316}317}318319# ignore NOTE(...) annotations (assumes NOTE is on lines by itself).320if ($note_level || /\b_?NOTE\s*\(/) { # if in NOTE or this is NOTE321s/[^()]//g; # eliminate all non-parens322$note_level += s/\(//g - length; # update paren nest level323next;324}325326# a /* BEGIN CSTYLED */ comment starts a no-check block.327if (/\/\* *BEGIN *CSTYLED *\*\//) {328$nocheck = 1;329}330331# a /*CSTYLED*/ comment indicates that the next line is ok.332if (/\/\* *CSTYLED.*\*\//) {333/^.*\/\* *CSTYLED *(.*) *\*\/.*$/;334$okmsg = $1;335$nextok = 1;336}337if (/\/\/ *CSTYLED/) {338/^.*\/\/ *CSTYLED *(.*)$/;339$okmsg = $1;340$nextok = 1;341}342343# universal checks; apply to everything344if (/\t +\t/) {345err("spaces between tabs");346}347if (/ \t+ /) {348err("tabs between spaces");349}350if (/\s$/) {351err("space or tab at end of line");352}353if (/[^ \t(]\/\*/ && !/\w\(\/\*.*\*\/\);/) {354err("comment preceded by non-blank");355}356if (/ARGSUSED/) {357err("ARGSUSED directive");358}359360# is this the beginning or ending of a function?361# (not if "struct foo\n{\n")362if (/^\{$/ && $prev =~ /\)\s*(const\s*)?(\/\*.*\*\/\s*)?\\?$/) {363$in_function = 1;364$in_declaration = 1;365$in_function_header = 0;366$function_header_full_indent = 0;367$prev = $line;368next line;369}370if (/^\}\s*(\/\*.*\*\/\s*)*$/) {371if ($prev =~ /^\s*return\s*;/) {372err_prev("unneeded return at end of function");373}374$in_function = 0;375reset_indent(); # we don't check between functions376$prev = $line;377next line;378}379if ($in_function_header && ! /^ (\w|\.)/ ) {380if (/^\{\}$/ # empty functions381|| /;/ #run function with multiline arguments382|| /#/ #preprocessor commands383|| /^[^\s\\]*\(.*\)$/ #functions without ; at the end384|| /^$/ #function declaration can't have empty line385) {386$in_function_header = 0;387$function_header_full_indent = 0;388} elsif ($prev =~ /^__attribute__/) { #__attribute__((*))389$in_function_header = 0;390$function_header_full_indent = 0;391$prev = $line;392next line;393} elsif ($picky && ! (/^\t/ && $function_header_full_indent != 0)) {394395err("continuation line should be indented by 4 spaces");396}397}398399# If this looks like a top-level macro invocation, remember it so we400# don't mistake it for a function declaration below.401if (/^[A-Za-z_][A-Za-z_0-9]*\(/) {402$in_macro_call = 1;403}404405#406# If this matches something of form "foo(", it's probably a function407# definition, unless it ends with ") bar;", in which case it's a declaration408# that uses a macro to generate the type.409#410if (!$in_macro_call && /^\w+\(/ && !/\) \w+;/) {411$in_function_header = 1;412if (/\($/) {413$function_header_full_indent = 1;414}415}416if ($in_function_header && /^\{$/) {417$in_function_header = 0;418$function_header_full_indent = 0;419$in_function = 1;420}421if ($in_function_header && /\);$/) {422$in_function_header = 0;423$function_header_full_indent = 0;424}425if ($in_function_header && /\{$/ ) {426if ($picky) {427err("opening brace on same line as function header");428}429$in_function_header = 0;430$function_header_full_indent = 0;431$in_function = 1;432next line;433}434435if ($in_warlock_comment && /\*\//) {436$in_warlock_comment = 0;437$prev = $line;438next line;439}440441# a blank line terminates the declarations within a function.442# XXX - but still a problem in sub-blocks.443if ($in_declaration && /^$/) {444$in_declaration = 0;445}446447if ($comment_done) {448$in_comment = 0;449$comment_done = 0;450}451# does this looks like the start of a block comment?452if (/$hdr_comment_start/) {453if (!/^\t*\/\*/) {454err("block comment not indented by tabs");455}456$in_comment = 1;457/^(\s*)\//;458$comment_prefix = $1;459$prev = $line;460next line;461}462# are we still in the block comment?463if ($in_comment) {464if (/^$comment_prefix \*\/$/) {465$comment_done = 1;466} elsif (/\*\//) {467$comment_done = 1;468err("improper block comment close");469} elsif (!/^$comment_prefix \*[ \t]/ &&470!/^$comment_prefix \*$/) {471err("improper block comment");472}473}474475# check for errors that might occur in comments and in code.476477# allow spaces to be used to draw pictures in all comments.478if (/[^ ] / && !/".* .*"/ && !$in_comment) {479err("spaces instead of tabs");480}481if (/^ / && !/^ \*[ \t\/]/ && !/^ \*$/ &&482(!/^ (\w|\.)/ || $in_function != 0)) {483err("indent by spaces instead of tabs");484}485if (/^\t+ [^ \t\*]/ || /^\t+ \S/ || /^\t+ \S/) {486err("continuation line not indented by 4 spaces");487}488if (/$warlock_re/ && !/\*\//) {489$in_warlock_comment = 1;490$prev = $line;491next line;492}493if (/^\s*\/\*./ && !/^\s*\/\*.*\*\// && !/$hdr_comment_start/) {494err("improper first line of block comment");495}496497if ($in_comment) { # still in comment, don't do further checks498$prev = $line;499next line;500}501502if ((/[^(]\/\*\S/ || /^\/\*\S/) && !/$lint_re/) {503err("missing blank after open comment");504}505if (/\S\*\/[^)]|\S\*\/$/ && !/$lint_re/) {506err("missing blank before close comment");507}508# check for unterminated single line comments, but allow them when509# they are used to comment out the argument list of a function510# declaration.511if (/\S.*\/\*/ && !/\S.*\/\*.*\*\// && !/\(\/\*/) {512err("unterminated single line comment");513}514515if (/^(#else|#endif|#include)(.*)$/) {516$prev = $line;517if ($picky) {518my $directive = $1;519my $clause = $2;520# Enforce ANSI rules for #else and #endif: no noncomment521# identifiers are allowed after #endif or #else. Allow522# C++ comments since they seem to be a fact of life.523if ((($1 eq "#endif") || ($1 eq "#else")) &&524($clause ne "") &&525(!($clause =~ /^\s+\/\*.*\*\/$/)) &&526(!($clause =~ /^\s+\/\/.*$/))) {527err("non-comment text following " .528"$directive (or malformed $directive " .529"directive)");530}531}532next line;533}534535#536# delete any comments and check everything else. Note that537# ".*?" is a non-greedy match, so that we don't get confused by538# multiple comments on the same line.539#540s/\/\*.*?\*\///g;541s/\/\/(?:\s.*)?$//; # Valid C++ comments542543# After stripping correctly spaced comments, check for (and strip) comments544# without a blank. By checking this after clearing out C++ comments that545# correctly have a blank, we guarantee URIs in a C++ comment will not cause546# an error.547if (s!//.*$!!) { # C++ comments548err("missing blank after start comment");549}550551# delete any trailing whitespace; we have already checked for that.552s/\s*$//;553554# following checks do not apply to text in comments.555556if (/[^<>\s][!<>=]=/ || /[^<>][!<>=]=[^\s,]/ ||557(/[^->]>[^,=>\s]/ && !/[^->]>$/) ||558(/[^<]<[^,=<\s]/ && !/[^<]<$/) ||559/[^<\s]<[^<]/ || /[^->\s]>[^>]/) {560err("missing space around relational operator");561}562if (/\S>>=/ || /\S<<=/ || />>=\S/ || /<<=\S/ || /\S[-+*\/&|^%]=/ ||563(/[^-+*\/&|^%!<>=\s]=[^=]/ && !/[^-+*\/&|^%!<>=\s]=$/) ||564(/[^!<>=]=[^=\s]/ && !/[^!<>=]=$/)) {565# XXX - should only check this for C++ code566# XXX - there are probably other forms that should be allowed567if (!/\soperator=/) {568err("missing space around assignment operator");569}570}571if (/[,;]\S/ && !/\bfor \(;;\)/) {572err("comma or semicolon followed by non-blank");573}574# allow "for" statements to have empty "while" clauses575# allow macro invocations to have empty parameters576if (/\s[,;]/ && !/^[\t]+;$/ &&577!($in_macro_call || /^\s*for \([^;]*; ;[^;]*\)/)) {578err("comma or semicolon preceded by blank");579}580if (/^\s*(&&|\|\|)/) {581err("improper boolean continuation");582}583if (/\S *(&&|\|\|)/ || /(&&|\|\|) *\S/) {584err("more than one space around boolean operator");585}586if (/\b(for|if|while|switch|sizeof|return|case)\(/) {587err("missing space between keyword and paren");588}589if (/(\b(for|if|while|switch|return)\b.*){2,}/ && !/^#define/) {590# multiple "case" and "sizeof" allowed591err("more than one keyword on line");592}593if (/\b(for|if|while|switch|sizeof|return|case)\s\s+\(/ &&594!/^#if\s+\(/) {595err("extra space between keyword and paren");596}597# try to detect "func (x)" but not "if (x)" or598# "#define foo (x)" or "int (*func)();"599if (/\w\s\(/) {600my $s = $_;601# strip off all keywords on the line602s/\b(for|if|while|switch|return|case|sizeof)\s\(/XXX(/g;603s/#elif\s\(/XXX(/g;604s/^#define\s+\w+\s+\(/XXX(/;605# do not match things like "void (*f)();"606# or "typedef void (func_t)();"607s/\w\s\(+\*/XXX(*/g;608s/\b($typename|void)\s+\(+/XXX(/og;609if (/\w\s\(/) {610err("extra space between function name and left paren");611}612$_ = $s;613}614# try to detect "int foo(x)", but not "extern int foo(x);"615# XXX - this still trips over too many legitimate things,616# like "int foo(x,\n\ty);"617# if (/^(\w+(\s|\*)+)+\w+\(/ && !/\)[;,](\s|)*$/ &&618# !/^(extern|static)\b/) {619# err("return type of function not on separate line");620# }621# this is a close approximation622if (/^(\w+(\s|\*)+)+\w+\(.*\)(\s|)*$/ &&623!/^(extern|static)\b/) {624err("return type of function not on separate line");625}626if (/^#define /) {627err("#define followed by space instead of tab");628}629if (/^\s*return\W[^;]*;/ && !/^\s*return\s*\(.*\);/) {630err("unparenthesized return expression");631}632if (/\bsizeof\b/ && !/\bsizeof\s*\(.*\)/) {633err("unparenthesized sizeof expression");634}635if (/\(\s/) {636err("whitespace after left paren");637}638# Allow "for" statements to have empty "continue" clauses.639# Allow right paren on its own line unless we're being picky (-p).640if (/\s\)/ && !/^\s*for \([^;]*;[^;]*; \)/ && ($picky || !/^\s*\)/)) {641err("whitespace before right paren");642}643if (/^\s*\(void\)[^ ]/) {644err("missing space after (void) cast");645}646if (/\S\{/ && !/\{\{/) {647err("missing space before left brace");648}649if ($in_function && /^\s+\{/ &&650($prev =~ /\)\s*$/ || $prev =~ /\bstruct\s+\w+$/)) {651err("left brace starting a line");652}653if (/\}(else|while)/) {654err("missing space after right brace");655}656if (/\}\s\s+(else|while)/) {657err("extra space after right brace");658}659if (/\b_VOID\b|\bVOID\b|\bSTATIC\b/) {660err("obsolete use of VOID or STATIC");661}662if (/\b$typename\*/o) {663err("missing space between type name and *");664}665if (/^\s+#/) {666err("preprocessor statement not in column 1");667}668if (/^#\s/) {669err("blank after preprocessor #");670}671if (/!\s*(strcmp|strncmp|bcmp)\s*\(/) {672err("don't use boolean ! with comparison functions");673}674675#676# We completely ignore, for purposes of indentation:677# * lines outside of functions678# * preprocessor lines679#680if ($check_continuation && $in_function && !$in_cpp) {681process_indent($_);682}683if ($picky) {684# try to detect spaces after casts, but allow (e.g.)685# "sizeof (int) + 1", "void (*funcptr)(int) = foo;", and686# "int foo(int) __NORETURN;"687if ((/^\($typename( \*+)?\)\s/o ||688/\W\($typename( \*+)?\)\s/o) &&689!/sizeof\s*\($typename( \*)?\)\s/o &&690!/\($typename( \*+)?\)\s+=[^=]/o) {691err("space after cast");692}693if (/\b$typename\s*\*\s/o &&694!/\b$typename\s*\*\s+const\b/o) {695err("unary * followed by space");696}697}698if ($check_posix_types && !$in_macro_call) {699# try to detect old non-POSIX types.700# POSIX requires all non-standard typedefs to end in _t,701# but historically these have been used.702#703# We don't check inside macro invocations because macros have704# legitmate uses for these names in function generators.705if (/\b(unchar|ushort|uint|ulong|u_int|u_short|u_long|u_char|quad)\b/) {706err("non-POSIX typedef $1 used: use $old2posix{$1} instead");707}708}709if (/^\s*else\W/) {710if ($prev =~ /^\s*\}$/) {711err_prefix($prev,712"else and right brace should be on same line");713}714}715716# Macro invocations end with a closing paren, and possibly a semicolon.717# We do this check down here to make sure all the regular checks are718# applied to calls that appear entirely on a single line.719if ($in_macro_call && /\);?$/) {720$in_macro_call = 0;721}722723$prev = $line;724}725726if ($prev eq "") {727err("last line in file is blank");728}729730}731732#733# Continuation-line checking734#735# The rest of this file contains the code for the continuation checking736# engine. It's a pretty simple state machine which tracks the expression737# depth (unmatched '('s and '['s).738#739# Keep in mind that the argument to process_indent() has already been heavily740# processed; all comments have been replaced by control-A, and the contents of741# strings and character constants have been elided.742#743744my $cont_in; # currently inside of a continuation745my $cont_off; # skipping an initializer or definition746my $cont_noerr; # suppress cascading errors747my $cont_start; # the line being continued748my $cont_base; # the base indentation749my $cont_first; # this is the first line of a statement750my $cont_multiseg; # this continuation has multiple segments751752my $cont_special; # this is a C statement (if, for, etc.)753my $cont_macro; # this is a macro754my $cont_case; # this is a multi-line case755756my @cont_paren; # the stack of unmatched ( and [s we've seen757758sub759reset_indent()760{761$cont_in = 0;762$cont_off = 0;763}764765sub766delabel($)767{768#769# replace labels with tabs. Note that there may be multiple770# labels on a line.771#772local $_ = $_[0];773774while (/^(\t*)( *(?:(?:\w+\s*)|(?:case\b[^:]*)): *)(.*)$/) {775my ($pre_tabs, $label, $rest) = ($1, $2, $3);776$_ = $pre_tabs;777while ($label =~ s/^([^\t]*)(\t+)//) {778$_ .= "\t" x (length($2) + length($1) / 8);779}780$_ .= ("\t" x (length($label) / 8)).$rest;781}782783return ($_);784}785786sub787process_indent($)788{789require strict;790local $_ = $_[0]; # preserve the global $_791792s///g; # No comments793s/\s+$//; # Strip trailing whitespace794795return if (/^$/); # skip empty lines796797# regexps used below; keywords taking (), macros, and continued cases798my $special = '(?:(?:\}\s*)?else\s+)?(?:if|for|while|switch)\b';799my $macro = '[A-Z_][A-Z_0-9]*\(';800my $case = 'case\b[^:]*$';801802# skip over enumerations, array definitions, initializers, etc.803if ($cont_off <= 0 && !/^\s*$special/ &&804(/(?:(?:\b(?:enum|struct|union)\s*[^\{]*)|(?:\s+=\s*))\{/ ||805(/^\s*\{/ && $prev =~ /=\s*(?:\/\*.*\*\/\s*)*$/))) {806$cont_in = 0;807$cont_off = tr/{/{/ - tr/}/}/;808return;809}810if ($cont_off) {811$cont_off += tr/{/{/ - tr/}/}/;812return;813}814815if (!$cont_in) {816$cont_start = $line;817818if (/^\t* /) {819err("non-continuation indented 4 spaces");820$cont_noerr = 1; # stop reporting821}822$_ = delabel($_); # replace labels with tabs823824# check if the statement is complete825return if (/^\s*\}?$/);826return if (/^\s*\}?\s*else\s*\{?$/);827return if (/^\s*do\s*\{?$/);828return if (/\{$/);829return if (/\}[,;]?$/);830831# Allow macros on their own lines832return if (/^\s*[A-Z_][A-Z_0-9]*$/);833834# cases we don't deal with, generally non-kosher835if (/\{/) {836err("stuff after {");837return;838}839840# Get the base line, and set up the state machine841/^(\t*)/;842$cont_base = $1;843$cont_in = 1;844@cont_paren = ();845$cont_first = 1;846$cont_multiseg = 0;847848# certain things need special processing849$cont_special = /^\s*$special/? 1 : 0;850$cont_macro = /^\s*$macro/? 1 : 0;851$cont_case = /^\s*$case/? 1 : 0;852} else {853$cont_first = 0;854855# Strings may be pulled back to an earlier (half-)tabstop856unless ($cont_noerr || /^$cont_base / ||857(/^\t*(?: )?(?:gettext\()?\"/ && !/^$cont_base\t/)) {858err_prefix($cont_start,859"continuation should be indented 4 spaces");860}861}862863my $rest = $_; # keeps the remainder of the line864865#866# The split matches 0 characters, so that each 'special' character867# is processed separately. Parens and brackets are pushed and868# popped off the @cont_paren stack. For normal processing, we wait869# until a ; or { terminates the statement. "special" processing870# (if/for/while/switch) is allowed to stop when the stack empties,871# as is macro processing. Case statements are terminated with a :872# and an empty paren stack.873#874foreach $_ (split /[^\(\)\[\]\{\}\;\:]*/) {875next if (length($_) == 0);876877# rest contains the remainder of the line878my $rxp = "[^\Q$_\E]*\Q$_\E";879$rest =~ s/^$rxp//;880881if (/\(/ || /\[/) {882push @cont_paren, $_;883} elsif (/\)/ || /\]/) {884my $cur = $_;885tr/\)\]/\(\[/;886887my $old = (pop @cont_paren);888if (!defined($old)) {889err("unexpected '$cur'");890$cont_in = 0;891last;892} elsif ($old ne $_) {893err("'$cur' mismatched with '$old'");894$cont_in = 0;895last;896}897898#899# If the stack is now empty, do special processing900# for if/for/while/switch and macro statements.901#902next if (@cont_paren != 0);903if ($cont_special) {904if ($rest =~ /^\s*\{?$/) {905$cont_in = 0;906last;907}908if ($rest =~ /^\s*;$/) {909err("empty if/for/while body ".910"not on its own line");911$cont_in = 0;912last;913}914if (!$cont_first && $cont_multiseg == 1) {915err_prefix($cont_start,916"multiple statements continued ".917"over multiple lines");918$cont_multiseg = 2;919} elsif ($cont_multiseg == 0) {920$cont_multiseg = 1;921}922# We've finished this section, start923# processing the next.924goto section_ended;925}926if ($cont_macro) {927if ($rest =~ /^$/) {928$cont_in = 0;929last;930}931}932} elsif (/\;/) {933if ($cont_case) {934err("unexpected ;");935} elsif (!$cont_special) {936err("unexpected ;") if (@cont_paren != 0);937if (!$cont_first && $cont_multiseg == 1) {938err_prefix($cont_start,939"multiple statements continued ".940"over multiple lines");941$cont_multiseg = 2;942} elsif ($cont_multiseg == 0) {943$cont_multiseg = 1;944}945if ($rest =~ /^$/) {946$cont_in = 0;947last;948}949if ($rest =~ /^\s*special/) {950err("if/for/while/switch not started ".951"on its own line");952}953goto section_ended;954}955} elsif (/\{/) {956err("{ while in parens/brackets") if (@cont_paren != 0);957err("stuff after {") if ($rest =~ /[^\s}]/);958$cont_in = 0;959last;960} elsif (/\}/) {961err("} while in parens/brackets") if (@cont_paren != 0);962if (!$cont_special && $rest !~ /^\s*(while|else)\b/) {963if ($rest =~ /^$/) {964err("unexpected }");965} else {966err("stuff after }");967}968$cont_in = 0;969last;970}971} elsif (/\:/ && $cont_case && @cont_paren == 0) {972err("stuff after multi-line case") if ($rest !~ /$^/);973$cont_in = 0;974last;975}976next;977section_ended:978# End of a statement or if/while/for loop. Reset979# cont_special and cont_macro based on the rest of the980# line.981$cont_special = ($rest =~ /^\s*$special/)? 1 : 0;982$cont_macro = ($rest =~ /^\s*$macro/)? 1 : 0;983$cont_case = 0;984next;985}986$cont_noerr = 0 if (!$cont_in);987}988989990