#!/usr/bin/perl -w use strict; my $name0=$0; $name0 =~ s%^.*/%%; my $IDENTIFIER="[a-zA-Z_][a-zA-Z0-9_]*"; my $MANGLEDIDENT="[a-zA-Z0-9_?@\$]+"; my $SPECIDENT="$IDENTIFIER(?:\.$MANGLEDIDENT)*"; my $SPECOPT="-[a-zA-Z0-9_=,]+\\s+"; ### List of linefeed issues to ignore my %lf_blacklist=( "dlls/comctl32/rebar.c" => {"\"band # \%u: xHeader=\%u\"" => 1}, "dlls/d3d8/device.c" => {"\"Searching for declaration for fvf \%08x... \"" => 1}, "dlls/d3d9/device.c" => {"\"Searching for declaration for fvf \%08x... \"" => 1}, "dlls/ddraw/ddraw.c" => {"\"Searching for declaration for fvf \%08x... \"" => 1}, "dlls/ddraw/utils.c" => {"\"\%s \"" => 1, "\" R \"" => 1, "\" - \"" => 1}, "dlls/dinput/effect_linuxinput.c" => {"\"\%[ds] \"" => 1}, "dlls/dinput/mouse.c" => {"\"\\(X: \%d Y: \%d Z: \%d\"" => 1}, "dlls/dsound/dsound.c" => {"\"\%s \",flags\\[i\\]\\.name" => 1}, "dlls/gdi32/freetype.c" => {"\"\\\\t\%s\\\\t\%08x\"" => 1}, "dlls/ntdll/loader.c" => {"\"No implementation for \%s\\.%[ds]\"" => 1}, "dlls/ntdll/rtl.c" => {"\"\%s\%x: \%s\"" => 1}, "dlls/ntdll/time.c" => {"\"starting date isdst \%d, \%s\"" => 1, "\"year_start: \%s\"" => 1, "\"std: \%s\"" => 1, "\"dlt gmtime: \%s\"" => 1, "\"std gmtime: \%s\"" => 1}, "dlls/ntdll/virtual.c" => {"\"View: \%p - \%p\"" => 1}, "dlls/odbc32/proxyodbc.c" => {"\"returns: \%d \\\\t\"" => 1}, "dlls/ole32/marshal.c" => {"\" MSHLFLAGS_TABLESTRONG\"" => 1}, "dlls/oleaut32/tmarshal.c" => {"\"*\"" => 1}, "dlls/oleaut32/typelib.c" => {"\"\%p->\\{\%s\%s\"" => 1, "\"\\\\t\\\\tu\\.paramdesc\\.wParamFlags\"" => 1}, "dlls/oleaut32/varformat.c" => {"\"\%s0x%02x\"" => 1}, "dlls/quartz/dsoundrender.c" => {"\"%02x \"" => 1}, "dlls/quartz/videorenderer.c" => {"\"%02x \"" => 1}, "dlls/rpcrt4/ndr_marshall.c" => {"\" RPC_FC_P_ALLOCALLNODES\"" => 1}, "dlls/rpcrt4/ndr_stubless.c" => {"\" MustSize\"" => 1, "\" ServerMustSize\"" => 1}, "dlls/rpcrt4/rpc_binding.c" => {"\"SecurityQos { Version=\%d, Capabilities=0x\%x, IdentityTracking=\%d, ImpersonationLevel=\%d\"" => 1}, "dlls/secur32/dispatcher.c" => {"\"\%s \"" => 1}, "dlls/secur32/schannel.c" => {"\"<\%d> \%s\"" => 1}, "dlls/setupapi/parser.c" => {"\"\%p/\%p/\%d/\%d index \%d returning\"" => 1}, "dlls/snmpapi/main.c" => {"\"\%u\"" => 1, "\"String \"" => 1, "\"IpAddress \"" => 1, "\"Bits \"" => 1, "\"Opaque \"" => 1, "\"ObjectID \"" => 1}, "dlls/user32/listbox.c" => {"\\[\%p\\]: settabstops \"" => 1}, "dlls/user32/menu.c" => {"\"\%s\%s\"" => 1, "\"\%s \"" => 1}, "dlls/user32/painting.c" => {"\"\%p region \%p box \%s \"" => 1}, "dlls/user32/scroll.c" => {"\"hwnd=\%p bar=\%d\"" => 1}, "dlls/usp10/usp10.c" => {"\"New_Script=\%d, eScript=\%d \"" => 1, "\"Item \%d, Glyphs \%d \"" => 1}, "dlls/version/info.c" => {"\"filetype=APP\"" => 1}, "dlls/winecoreaudio.drv/midi.c" => {"\"\%02X \"" => 1}, "dlls/wined3d/baseshader.c" => {"\"GL HW \\(\%u, \%u\\) : \%s\"" => 1, "\"dcl\"" => 1, "\"-\"" => 1, "\"_d8\"" => 1, "\"def c\%u = \%f, \%f, \%f, \%f\"" => 1, "\"%s\"" => 1}, "dlls/wined3d/vertexbuffer.c" => {"\"[[]\%d[]]\"" => 1}, "dlls/winex11.drv/dib.c" => {"\"%04x/%04lx \"" => 1}, "dlls/winex11.drv/opengl.c" => {"\"\\('\%s'\\):\%\\*s\"" => 1, "\"\\*\"" => 1}, "dlls/wininet/internet.c" => {"\" %s\"" => 1}, ); ### List of stdcall equivalents my @stdcall_seeds=qw(__stdcall); my (%macros, $stdcall_re); ### List of 'static stdcall' functions to ignore my %static_ignore=( "dlls/winedos/interrupts.c" => {DOSVM_Int20Handler => 1, DOSVM_Int5cHandler => 1}, "dlls/winmm/message16.c" => {MMDRV_Aux_Callback => 1, MMDRV_Mixer_Callback => 1, MMDRV_MidiIn_Callback => 1, MMDRV_MidiOut_Callback => 1, MMDRV_WaveIn_Callback => 1, MMDRV_WaveOut_Callback => 1}, "programs/cmdlgtst/cmdlgtst.c" => {dummyfnHook => 1}, ); ### Debug channel checker my %dbg_ignore=( "dlls/kernel32/virtual.c" => {"" => 1}, "dlls/msvcrt/scanf.c" => {"msvcrt" => 1}, "dlls/ntdll/debugtools.c" => {"" => 1}, ); ### List of pointers for the NULL cast checker my (%typedefs, %pointers); ### List of macros that are equal to 0 my %zeroes=(NULL => 1); ### List of undef-ed macros my %undefs; ### List of exported functions to ignore my %export_ignore=( "dlls/kernel32" => {NE_DumpModule => 1, NE_WalkModules => 1, debug_handles => 1}, "dlls/ntdll" => {__wine_process_init => 1, __wine_spec_unimplemented_stub => 1}, "dlls/ole32" => {STORAGE_dump_pps_entry => 1}, "dlls/riched20" => {ME_DumpParaStyleToBuf => 1, ME_DumpStyle => 1}, "dlls/secur32" => {SECUR32_initNegotiateSP => 1, # 2009/01: Till implementation catches up SECUR32_strdupW => 1}, # 2009/01: Till implementation catches up "dlls/wined3d" => {debug_fixup_channel_source => 1, debug_yuv_fixup => 1}, "dlls/wineoss.drv" => {seqbuf_dump => 1}, "dlls/winhttp" => {netconn_set_timeout => 1}, # 2009/01: Till implementation catches up ); ### List of functions implemented or referenced from assembly code, ### sorted by directory my %asm_functions; ### List of standard sublanguages my %standard_sublangs=( SUBLANG_NEUTRAL => 1, SUBLANG_DEFAULT => 1, SUBLANG_SYS_DEFAULT => 1, SUBLANG_CUSTOM_DEFAULT => 1, SUBLANG_CUSTOM_UNSPECIFIED => 1, SUBLANG_UI_CUSTOM_DEFAULT => 1 ); ### Lists of the languages default sublang and of those that have many sub ### languages my (%lang_default, %lang_manysubs); ### List of languages known to have translations for non-default sub languages my %lang_anysub=( LANG_ENGLISH => 1, LANG_PORTUGUESE => 1 ); my $verbose; sub verbose(@) { return print STDERR @_ if ($verbose); } my $debug; sub debug(@) { return print STDERR @_ if ($debug); } sub err(@) { print STDERR "$name0:error: ", @_; } sub dirname($) { my ($path)=@_; return "" if ($path !~ s!/[^/]+$!!); return $path; } my @file; my $l; my @chars; my $len; my $c; sub skip_bracket(); sub skip_parent(); sub skip_string(); sub setup_line($) { return 0 if ($l >= @file); $c=$_[0]; $len=length($file[$l]); @chars=split //, $file[$l]; debug("$l,$c: $file[$l]"); return 1; } sub set_position($$) { return 0 if ($l >= @file); $c=$_[0]; $l=$_[1]; $len=length($file[$l]); @chars=split //, $file[$l]; debug("$l,$c: $file[$l]"); return 1; } sub skip_bracket() { while (1) { while ($c < $len) { debug("skip_bracket: $c -> '$chars[$c]'\n"); if ($chars[$c] eq "]") { debug("skip_bracket returns 1\n"); return 1; } elsif ($chars[$c] eq "\"") { $c++; skip_string(); } elsif ($chars[$c] eq "(") { $c++; skip_parent(); } elsif ($chars[$c] eq "[") { $c++; skip_bracket(); } $c++; } $l++; return 0 if (!setup_line(0)); } return 0; } sub skip_parent() { while (1) { while ($c < $len) { debug("skip_parent: $c -> '$chars[$c]'\n"); if ($chars[$c] eq ")") { debug("skip_parent returns 1\n"); return 1; } elsif ($chars[$c] eq "\"") { $c++; skip_string(); } elsif ($chars[$c] eq "(") { $c++; skip_parent(); } elsif ($chars[$c] eq "[") { $c++; skip_bracket(); } $c++; } $l++; return 0 if (!setup_line(0)); } return 0; } sub skip_string() { my $backslash; while (1) { while ($c < $len) { debug("skip_string: $c -> '$chars[$c]'\n"); if ($chars[$c] eq "\"") { if (!$backslash) { debug("skip_string returns 1\n"); return 1; } $backslash=undef; } elsif ($chars[$c] eq "\\") { $backslash=($backslash?undef:1); } else { $backslash=undef; } $c++; } $l++; return 0 if (!setup_line(0)); } return 0; } sub skip_comment() { my $star; while (1) { while ($c < $len) { debug("skip_comment: $c -> '$chars[$c]'\n"); if ($chars[$c] eq "*") { $star=1; } elsif ($chars[$c] eq "/") { return 1 if ($star); } else { $star=0; } $c++; } $l++; return 0 if (!setup_line(0)); } return 0; } sub skip_arg() { while (1) { while ($c < $len) { debug("skip_arg: $c -> '$chars[$c]'\n"); if ($chars[$c] eq ",") { debug("skip_arg returns 1\n"); return 1; } elsif ($chars[$c] eq "\"") { $c++; skip_string(); } elsif ($chars[$c] eq "(") { $c++; skip_parent(); } elsif ($chars[$c] eq "[") { $c++; skip_bracket(); } $c++; } $l++; return 0 if (!setup_line(0)); } return 0; } sub get_quote() { while (1) { while ($c < $len) { debug("get_quote: $c -> '$chars[$c]'\n"); if ($chars[$c] eq "\"") { debug("get_quote returns 1\n"); return 1; } elsif ($chars[$c] eq "\\") { return 0 if ($c+1==$len or $chars[$c+1] ne "\n"); } elsif ($chars[$c] eq "/" and $c+1 < $len and $chars[$c+1] eq "*") { return 0 if (!skip_comment()); } elsif ($chars[$c] !~ /(\s|\n)/) { return 0; } $c++; } $l++; return 0 if (!setup_line(0)); } return 0; } sub get_comma() { while (1) { while ($c < $len) { debug("get_comma: $c -> '$chars[$c]'\n"); if ($chars[$c] eq ",") { debug("get_comma returns 1\n"); return 1; } elsif ($chars[$c] eq "\\") { return 0 if ($c+1==$len or $chars[$c+1] ne "\n"); } elsif ($chars[$c] !~ /(\s|\n|[a-zA-Z0-9\#_])/) { return 0; } $c++; } $l++; return 0 if (!setup_line(0)); } return 0; } my $lf_set_ignore; sub fix_lf($$$$) { my ($needs_lf, $heuristics, $arg, $pos)=@_; setup_line($pos); verbose("processing:",$l+1," ",substr($file[$l], $pos)); # Locate the format string for (my $i=1; $i < $arg; $i++) { return undef if (!skip_arg()); $c++; } return undef if (!get_quote()); # See if this is a string of the form "xxx" #var "yyy" my ($quote_c, $quote_l); while ($c < $len) { last if ($chars[$c] ne "\""); $c++; return "" if (!skip_string()); $quote_c=$c; $quote_l=$l; $c++; last if (get_comma()); } set_position($quote_c, $quote_l); # Check and modify if ($needs_lf) { if ($c >= 2 and ($chars[$c-2] ne "\\" or $chars[$c-1] ne "n")) { my $start=substr($file[$l], 0, $c); verbose(" start=[$start]\n"); if ($start =~ /\\r$/ or $start =~ /\"\s*$/) { return $file[$l]; } elsif ($heuristics and ($start =~ /(?:->|[=:,|{([])\s*$/ or # For the dm* dlls $start =~ /\": \%s chunk \(size = (?:0x%04x|0x%08X|\%d)\)$/ or $start =~ /\": (?:LIST|RIFF) chunk of type \%s$/ )) { verbose(" matched=[$&]\n"); $lf_set_ignore=1; return $file[$l]; } else { $start=~s/\s*$/\\n/; return $start . substr($file[$l], $c); } } elsif ($c >= 3 and $chars[$c-3] eq " " and $chars[$c-2] eq "\\" and $chars[$c-1] eq "n") { my $space=$c-3; while ($space > 0 and $chars[$space] eq " ") { $space--; } return substr($file[$l], 0, $space+1) . substr($file[$l], $c-2); } } else { my $cut=$c; if ($cut >= 2 and $chars[$cut-2] eq "\\" and $chars[$cut-1] eq "n") { $cut-=2; } while ($cut > 0) { $cut--; last if ($chars[$cut] ne " "); } return substr($file[$l], 0, $cut+1) . substr($file[$l], $c); } return $file[$l]; } sub collect($$) { my ($rootdir, $filename) = @_; debug("***** Processing $filename\n"); if (open(my $fh, "<", "$rootdir$filename")) { my $dir=dirname($filename); while (my $line=<$fh>) { if ($line =~ /^\s*#\s*define\s+($IDENTIFIER)\s+(0(?:x0+)?L?|\(\s*0(?:x0+)?L?\s*\))\s*(\/\*.*)?$/) { debug("zero $1\n"); $macros{$1}=$2; $zeroes{$1}=1; } elsif ($line =~ /^\s*#\s*define\s+($IDENTIFIER)\s+([0-9]+L?|0x[0-9a-fA-F]+L?|\(\s*(?:[0-9]+L?|0x[0-9a-fA-F]+L?)\s*\))\s*(\/\*.*)?$/) { debug("macro $1=$2\n"); $macros{$1}=$2; } elsif ($line =~ /^\s*#\s*define\s+($IDENTIFIER)\s+($IDENTIFIER)\s*(\/\*.*)?$/) { debug("macro $1=$2\n"); $macros{$1}=$2; } elsif ($line =~ /^\s*typedef\s+($IDENTIFIER)\s+($IDENTIFIER)\s*[;,]/) { debug("typedef $1 $2\n"); $typedefs{$2}=$1; } elsif ($line =~ /^\s*typedef\s+[^;,]*\*\s*($IDENTIFIER)\s*[;,]/) { debug("pointer $1: $line"); $pointers{$1}=1; } elsif ($line =~ /^\s*DECLARE_HANDLE\(($IDENTIFIER)\)\s*;/) { debug("handle $1\n"); $pointers{$1}=1; } elsif ($line =~ /^\s*#\s*undef\s+($IDENTIFIER)\s*(\/\*.*)?$/ and $1 ne "YYERROR_VERBOSE") { debug("undef $1\n"); $undefs{$1}=1; } elsif ($line =~ /^\s*__ASM_GLOBAL_FUNC\s*\(\s*($IDENTIFIER)\s*,/) { my $function=$1; $asm_functions{$dir}->{$function}=1; } elsif ($line =~ /^\s*DEFINE_REGS_ENTRYPOINT\s*\(\s*($IDENTIFIER)\s*,/) { my $function=$1; $asm_functions{$dir}->{$function}=1; $asm_functions{$dir}->{"__regs_$function"}=1; } elsif ($line =~ /^\s*DEFINE_(?:FASTCALL[12]|SETJMP)_ENTRYPOINT\s*\(\s*($IDENTIFIER)\s*\)/) { my $function="__regs_$1"; $asm_functions{$dir}->{$function}=1; } elsif ($line =~ /^\s*DEFINE_THISCALL_WRAPPER\s*\(\s*($IDENTIFIER)\s*\)/) { my $function=$1; $asm_functions{$dir}->{$function}=1; $asm_functions{$dir}->{"__thiscall_$function"}=1; } elsif ($line =~ /__ASM_NAME\(\s*\"($IDENTIFIER)\"\s*\)/) { my $function=$1; $asm_functions{$dir}->{$function}=1; } } close($fh); } else { err("unable to open '$filename' for reading: $!\n"); exit 1; } } sub fix_c_file($$) { my ($rootdir, $filename) = @_; debug("***** Processing $filename\n"); if (open(my $fh, "<", "$rootdir$filename")) { @file = <$fh>; close($fh); } else { err("unable to open '$filename' for reading: $!\n"); exit 1; } # Initialize the linefeed checker my $lf_blacklist_extra; if ($lf_blacklist{$filename}) { $lf_blacklist_extra=join("|", keys %{$lf_blacklist{$filename}}); } my $lf_ignore=0; # Initialize the static stdcall checker my (%st_statics, %st_refs); my $st_prefix=""; # Initialize the debug channel checker my ($dbg_default, %dbg_channels); my $modified; $l=0; while ($l < @file) { # Ifdef checker if ($file[$l] =~ /^\s*#\s*if\s+($IDENTIFIER)\s*(\/\*.*)?$/ and $undefs{$1}) { print "$filename:",$l+1,": $1 might be undefined\n"; } # Debug channel checker if ($file[$l] =~ /^\s*WINE_DEFAULT_DEBUG_CHANNEL\s*\(\s*(\w+)\s*\)/) { $dbg_default=$1; $dbg_channels{$1}||=0; } elsif ($file[$l] =~ /^\s*WINE_DECLARE_DEBUG_CHANNEL\s*\(\s*(\w+)\s*\)/) { $dbg_channels{$1}||=0; } elsif ($file[$l] =~ /\b(?:WINE_)?(?:ERR|FIXME|TRACE|WARN)(?:_|_ON)?\b/ and $file[$l] !~ /^\s*\/\*/) { my $line=$file[$l]; while ($line =~ s/^.*?((?:WINE_)?(?:ERR|FIXME|TRACE|WARN)(?:_|_ON)?)\s*\(//) { my $type=$1; if ($type !~ /(?:_|_ON)$/) { if (!defined $dbg_default) { print "$filename:",$l+1,": uses the default channel but none has been defined yet\n"; } else { $dbg_channels{$dbg_default}++; } } elsif ($line =~ /^\s*(\w+)\s*\)/) { my $channel=$1; if (!exists $dbg_channels{$channel}) { print "$filename:",$l+1,": unknown debug channel '$channel'\n"; } else { $dbg_channels{$channel}++; } } else { print "$filename:",$l+1,": unable to parse the trace statement\n"; } } } # Zero-flag checker if ($file[$l] =~ /[^&]&[^&]/) { my $line=$file[$l]; debug("zflags: $line"); my %warned; while ($line =~ s/([^&,({= ]\s*[&]\s*)($IDENTIFIER)\b/$1 1/) { debug("zflags1: $2 -> $line"); if ($zeroes{$2} and !$warned{$2}) { print "$filename:",$l+1,": '&' with zero-flag $2\n"; $warned{$1}=1; } } while ($line =~ s/\b($IDENTIFIER)(\s*&[^&])/1 $2/) { debug("zflags2: $1 -> $line"); if ($zeroes{$1} and !$warned{$1}) { print "$filename:",$l+1,": '&' with zero-flag $1\n"; $warned{$1}=1; } } if (%warned) { $line=$file[$l]; $line =~ s/^\s*//; print " $line"; } } # Static stdcall checker if ($file[$l] =~ /^\s*static(?:\s+$IDENTIFIER\s*\**)+\s+(?:$stdcall_re)\s+($IDENTIFIER)\s*(?:\(|$)/) { debug("$filename: static $1\n"); push @{$st_statics{$1}}, $l; $st_prefix=""; } elsif ("$st_prefix $file[$l]" =~ /^\s*static(?:\s+$IDENTIFIER\s*\**)+\s+(?:$stdcall_re)\s+($IDENTIFIER)\s*(?:\(|$)/) { debug("$filename: static $1\n"); push @{$st_statics{$1}}, $l; $st_prefix=""; } elsif ($file[$l] =~ /^\s*static(?:\s+$IDENTIFIER\s*\**)*\s*$/) { $st_prefix=$file[$l]; chomp $st_prefix; } else { my $s=$file[$l]; while ($s =~ s/(^|[\{\(\)&=?:,])\s*($IDENTIFIER)\s*(?![\(a-zA-Z0-9_])/$1/) { #debug("$filename: & $2\n"); $st_refs{$2}=1; } $st_prefix=""; } # NULL cast checker # Ignore the '#define foo(xxx) NULL' lines if ($file[$l] !~ /^\s*#/) { my $cast=$file[$l]; $cast =~ s/\(\s*$IDENTIFIER(?:\s+$IDENTIFIER|\s*\*)*\s*\*\s*\)\s*(?:NULL|0L?)(?!\w|[0-9])/NULL/g; if ($cast =~ /\(\s*($IDENTIFIER)\s*\)\s*(?:NULL|0L?)(?!\w|[0-9])/) { # There may be others but if that happens # they will be removed manually $cast =~ s/\(\s*$1\s*\)\s*(?:NULL|0L?)(?!\w|[0-9]|\s*\)\s*->)/NULL/g if ($pointers{$1}); } if ($cast ne $file[$l]) { $file[$l]=$cast; $modified=1; } } # Linefeed processing # This may read more lines so this should be done last $lf_set_ignore=0; my $fixed; if ($file[$l] =~ /^(.*?\bok\s*\()/) { $fixed=fix_lf(1, 0, 2, length($1)); } elsif ($file[$l] =~ /^(.*?\btrace\s*\()/) { $fixed=fix_lf(1, 1, 1, length($1)); } elsif ($file[$l] =~ /^(.*?\b(?:WINE_)?(?:ERR|FIXME|TRACE|WARN)(?:_\s*\(\s*\w+\s*\))?\s*\()/) { $fixed=fix_lf(1, 1, 1, length($1)); } # Ignore DPRINTF because it is often intentionally used without a # trailing '\n' elsif ($file[$l] =~ /^(.*?\b(?:WINE_)?MESSAGE\s*\()/) { $fixed=fix_lf(1, 1, 1, length($1)); } elsif ($file[$l] =~ /^(.*?\bshader_addline\s*\()/) { # This is a wined3d trace function $fixed=fix_lf(1, 1, 2, length($1)); } elsif ($file[$l] =~ /^(.*?\b(?:mcy_|parser_|xyy)?(?:chat|warning)\s*\()/) { # Error and warning reporting functions used in tools/ $fixed=fix_lf(1, 0, 1, length($1)); } elsif ($file[$l] =~ /^(.*?\berror\s*\()/) { # Error reporting functions used in tools/ $fixed=fix_lf(1, 0, 1, length($1)); } elsif ($file[$l] =~ /^(.*?\bparser_error\s*\()/) { # Error reporting function used in tools/ that must not have # a trailing '\n' $fixed=fix_lf(0, 0, 1, length($1)); } elsif ($file[$l] =~ /^(.*?\berror_loc\s*\()/) { # Error reporting function used in tools/widl/ $fixed=fix_lf(1, 0, 1, length($1)); } elsif ($file[$l] =~ /^(.*?\b(?:error|warning)_loc_info\s*\()/) { # Error and warning reporting functions used in tools/widl/ $fixed=fix_lf(1, 0, 2, length($1)); } elsif ($file[$l] =~ /^(.*?\binternal_error\s*\()/) { # Error reporting function used in tools/wrc/ $fixed=fix_lf(1, 0, 3, length($1)); } elsif ($lf_ignore and $file[$l] =~ /^[{}]/) { verbose("$filename:",$l+1,": resuming checks\n"); $lf_ignore=0; } if (defined $fixed) { # fix_lf() successfully checked this line if (!$lf_ignore and !$lf_set_ignore and $fixed ne $file[$l] and $lf_blacklist_extra and $file[$l] =~ m!$lf_blacklist_extra!) { # Mark this blacklist entry as used foreach my $re (keys %{$lf_blacklist{$filename}}) { if ($file[$l] =~ /$re/) { delete $lf_blacklist{$filename}->{$re}; verbose("$filename:",$l+1,": matched $re\n"); last; } } $lf_set_ignore=1; } if ($lf_ignore) { if (!$lf_set_ignore and $fixed eq $file[$l]) { # We found a trace that looks ok so resume the checks my $line=$file[$l]; $line =~ s/^\s*//; verbose("$filename:",$l+1,": resuming checks due to: $line"); $lf_ignore=0; } } else { if ($lf_set_ignore) { my $line=$file[$l]; $line =~ s/^\s*//; verbose("$filename:",$l+1,": ignoring further errors due to: $line"); $lf_ignore=1; } elsif ($fixed ne $file[$l]) { $file[$l]=$fixed; $modified=1; $fixed =~ s/^\s*//; verbose("$filename:",$l+1,": fixed $fixed"); } } } $l++; } if ($modified) { if (open(my $fh, ">", "$rootdir$filename")) { print $fh @file; close($fh); } else { err("unable to open '$filename' for writing: $!\n"); } } # Debug channel checker if (!defined $dbg_default and %dbg_channels) { if ($dbg_ignore{$filename}->{""}) { delete $dbg_ignore{$filename}->{""}; } else { printf "$filename: contains debug channels but no default debug channel\n"; } } while (my ($channel, $value)=each %dbg_channels) { if ($dbg_ignore{$filename}->{$channel}) { delete $dbg_ignore{$filename}->{$channel}; } elsif (!$value) { printf "$filename: the '$channel' debug channel is unused\n"; } } delete $dbg_ignore{$filename}; # Check for unused linefeed blacklist entries if ($lf_blacklist{$filename}) { foreach my $re (keys %{$lf_blacklist{$filename}}) { print STDERR "$filename: unused linefeed blacklist: $re\n"; } } delete $lf_blacklist{$filename}; # Static stdcall checker foreach my $func (sort { $st_statics{$a}->[0] <=> $st_statics{$b}->[0] } keys %st_statics) { if (!$st_refs{$func}) { if ($static_ignore{$filename}->{$func}) { verbose("$filename: ignored static stdcall $func\n"); delete $static_ignore{$filename}->{$func}; } else { print "***** $filename ", join(" ", @{$st_statics{$func}}), "\n"; $st_prefix=""; foreach my $line (@file) { if ($line =~ /\b$func\b/) { print " $st_prefix $line"; $st_prefix=""; } elsif ($line =~ /^\s*static(?:\s+$IDENTIFIER\s*\**)*\s*$/) { $st_prefix=$line; chomp $st_prefix; } else { $st_prefix=""; } } print "\n"; } } } foreach my $func (keys %{$static_ignore{$filename}}) { print "$filename:$func not used\n"; } delete $static_ignore{$filename}; } sub fix_rc_file($$) { my ($rootdir, $filename) = @_; debug("***** Processing $filename\n"); if (open(my $fh, "<", "$rootdir$filename")) { @file = <$fh>; close($fh); } else { err("unable to open '$filename' for reading: $!\n"); exit 1; } my $modified; $l=0; while ($l < @file) { my $line=$file[$l]; # Fix extraneous spaces in '...' $line =~ s/\.(?: ?\.){2-5}/.../g; if ($filename =~ m!Si\.rc$!) { # Slovenian requires a space before '...' $line =~ s/([^ [])\.{3}/$1 .../g; } else { # Other languages say there should be no space before '...' # Except English where it's a bit ambiguous but where there # usually is no space in computer programs $line =~ s/([^,*]) *\.{3}/$1.../g; } # Remove spaces before '\n' $line =~ s/ +\\n/\\n/g; # Fix SUBLANGs if ($line !~ /^\s*#/ and $line =~ /\b(LANG_[A-Z]+)\s*,\s*(SUBLANG_[A-Z]+)\b/) { my ($lang, $sublang)=($1, $2); if ($lang eq "LANG_NEUTRAL") { if ($sublang ne "SUBLANG_NEUTRAL") { print "$filename:",$l+1,": expected LANG_NEUTRAL, SUBLANG_NEUTRAL for language-independent resource\n"; } } elsif (!$standard_sublangs{$sublang} and $sublang =~ /^SUB(LANG_[A-Z]+)(?:_|$)/ and $1 ne $lang) { print "$filename:",$l+1,": $sublang does not match $lang\n"; } elsif (!exists $lang_default{$lang}) { print "$filename:",$l+1,": unknown language $lang\n"; } elsif ($lang eq "LANG_RUSSIAN") { # FIXME: Say nothing for now until the SUBLANG_RUSSIAN_MOLDAVIA # situation is resolved } elsif ($lang eq "LANG_GERMAN" and $sublang eq "SUBLANG_DEFAULT" and $filename =~ m!kernel32/tests/resource\.rc$!) { # This is a test resource, presumably the SUBLANG_DEFAULT # is important # FIXME: Though maybe not } elsif (!$lang_manysubs{$lang}) { # There is only one sublanguage if ($lang_default{$lang} eq $sublang and $sublang ne "SUBLANG_DEFAULT") { # So it should be specified with SUBLANG_DEFAULT $line =~ s/\b$lang\s*,\s*$sublang\b/$lang, SUBLANG_DEFAULT/; } } elsif ($lang_anysub{$lang}) { # This language has many sublanguages, and sublanguage-specific # translations if ($lang eq "LANG_ENGLISH") { # FIXME: Leave LANGUAGE_ENGLISH, SUBLANG_DEFAULT alone # for now } elsif ($sublang eq "SUBLANG_DEFAULT") { # It's better to use the explicit name for the default # sublanguage: it's clearer for the next translator. $line =~ s/\b$lang\s*,\s*SUBLANG_DEFAULT\b/$lang, $lang_default{$lang}/; } } else { # Although this language has many sublanguages, we believe # that all the current translations are neutral. if ($sublang eq "SUBLANG_DEFAULT") { $line =~ s/\b$lang\s*,\s*SUBLANG_DEFAULT\b/$lang, SUBLANG_NEUTRAL/; } elsif ($sublang eq $lang_default{$lang}) { $line =~ s/\b$lang\s*,\s*$lang_default{$lang}\b/$lang, SUBLANG_NEUTRAL/; } elsif ($sublang ne "SUBLANG_NEUTRAL") { # Warn for non-neutral, non-default translations. # Maybe we finally have per-sublanguage translations? print "$filename:",$l+1,": $lang has an unexpected non neutral translation: $sublang\n"; } } } if ($line ne $file[$l]) { verbose("$filename:",$l+1,": fixed $line"); $file[$l]=$line; $modified=1; } $l++; } if ($modified) { if (open(my $fh, ">", "$rootdir$filename")) { print $fh @file; close($fh); } else { err("unable to open '$filename' for writing: $!\n"); } } } sub fix_so($$$$) { my ($rootdir, $so_file, $spec_files, $obj_files)=@_; return if (!defined $so_file or !@$obj_files); my ($is_exe, $is_test); $is_exe=1 if ($so_file =~ /\.exe\.so$/); $is_test=1 if ($so_file =~ /_test\.exe\.so$/); if (!@$spec_files and !$is_exe) { # This is not a Wine dll or exe so we have no idea # what should or should not be exported return; } my %spec_apis; foreach my $spec_file (@$spec_files) { if (open(my $fh, "<", "$rootdir$spec_file")) { while (my $line = <$fh>) { next if ($line =~ /^\s*(?:#|$)/); chomp $line; if ($line =~ /^\s*(?:\d+|@)\s+\w+\s+(?:$SPECOPT)*\$?$MANGLEDIDENT\s*\([^)]*\)\s+($SPECIDENT)\s*(?:#.*)?$/) { my $identifier=$1; $spec_apis{$identifier}=1 if ($identifier !~ /\./); } elsif ($line =~ /^\s*(?:\d+|@)\s+\w+\s+(?:$SPECOPT)*($SPECIDENT)\s*\([^)]*\)\s*(?:#.*)?$/) { my $identifier=$1; $spec_apis{$identifier}=1 if ($identifier !~ /\./); } elsif ($line =~ /^\s*(?:\d+|@)\s+stub\s+(?:$SPECOPT)*($SPECIDENT)(?:@\d+)?\s*(?:#.*)?$/) { my $identifier=$1; $spec_apis{$identifier}=1 if ($identifier !~ /\./); } elsif ($line =~ /^\s*(?:\d+|@)\s+stub\s+(?:$SPECOPT)*$MANGLEDIDENT\s*(?:#.*)?$/) { # Stub with only a mangled name -> no use to us } elsif ($line =~ /^\s*\d+\s+stub\s+@\s*(?:#.*)?$/) { # Unnamed stub -> no use to us } elsif ($line =~ /^\s*(?:\d+|@)\s+extern\s+(?:$SPECOPT)*\$?$MANGLEDIDENT\s+($SPECIDENT)\s*(?:#.*)?$/) { my $identifier=$1; $spec_apis{$identifier}=1 if ($identifier !~ /\./); } elsif ($line =~ /^\s*(?:\d+|@)\s+extern\s+(?:$SPECOPT)*($SPECIDENT)\s*(?:#.*)?$/) { my $identifier=$1; $spec_apis{$identifier}=1 if ($identifier !~ /\./); } elsif ($line =~ /^\s*\d+\s+equate\s/) { # No use to us } else { err("$spec_file: unknown line format\n $line\n"); } } close($fh); } else { err("unable to open '$spec_file' for reading: $!\n"); exit 1; } } my (%idl_ignore, %obj_exports, %obj_references); foreach my $obj_file (@$obj_files) { my ($src_file, $bison, $flex, $idl_cs, $idl_proxy); $src_file=$obj_file; if ($src_file =~ s/\.tab\.o$/.y/ and -f "$rootdir$src_file") { $bison=$src_file; $bison =~ s/\.y$/_parse/; $bison =~ s!^.*/!!; } $src_file=$obj_file; if ($src_file =~ s/\.yy\.o$/.l/ and -f "$rootdir$src_file") { # Flex generates some non-static functions which may not be used # in other files. So ignore those. However their prefix is # configurable so we need to scan the Flex file to figure it out. if (open(my $fh, "<", "$rootdir$src_file")) { while (my $line=<$fh>) { if ($line =~ /^\s*\%option\s.*\sprefix=\"($IDENTIFIER)\"/) { $flex=$1; last; } } close($fh); } $flex||="yy"; } $src_file=$obj_file; if ($src_file =~ s/_[csp]\.o$/.idl/ and -f "$rootdir$src_file") { $idl_cs=1 if ($obj_file =~ /_[cs]\.o$/); $idl_proxy=1 if ($obj_file =~ /_p\.o$/); } if (open(my $fh, "objdump -t \"$rootdir$obj_file\" |")) { while (my $line = <$fh>) { if ($line =~ /^.*\s\.text\s+[0-9a-f]+\s+(?:\.hidden\s+)?([A-Za-z_][A-Za-z0-9_]*)$/) { my $symbol=$1; if ($bison and $symbol eq $bison) { $idl_ignore{$symbol}=1; } elsif ($flex and $symbol =~ /^$flex/) { $idl_ignore{$symbol}=1; } elsif ($idl_cs) { $idl_ignore{$symbol}=1; } elsif ($idl_proxy and $symbol =~ /_(?:Proxy|Stub)$/) { $idl_ignore{$symbol}=1; } else { $obj_exports{$symbol}=$obj_file; $obj_references{$symbol}++; } } elsif ($line =~ /^.*\s\*UND\*\s+[0-9a-f]+\s+(?:\.hidden\s+)?([A-Za-z_][A-Za-z0-9_]*)$/) { $obj_references{$1}++; } } close($fh); } else { err("unable to analyze '$obj_file': $!\n"); exit 1; } } my $dir=dirname($so_file); if (open(my $fh, "nm -D \"$rootdir$so_file\" |")) { while (my $line = <$fh>) { if ($line =~ /^[0-9a-f]+\sT\s(\w+)$/) { my $function=$1; if (!$obj_exports{$function}) { # We're importing this function, not exporting it! } elsif (($obj_references{$function} || 0) > 1) { # This function is used in more than one object file # so it cannot be made static. } elsif ($idl_ignore{$function}) { # These are widl-generated functions. They are meant to # be used from other object files, but widl cannot know # whether that will actually be the case or not. # So these functions cannot be made static. } elsif ($asm_functions{$dir}->{$function}) { # Functions implemented in assembly, or referenced from # assembly code, cannot be made static due to gcc # limitations. } elsif ($spec_apis{$function}) { # These are meant to be exported } elsif ($is_exe and $function =~ /^w?(?:main|WinMain)$/) { # Executables are supposed to export these. } elsif ($is_test and $function =~ /^(?:broken$|wine_dbgstr_[aw]n?$|winetest_)/) { # Tests export these because of wine/test.h } elsif ($export_ignore{$dir}->{$function}) { delete $export_ignore{$dir}->{$function}; } else { print "$obj_exports{$function}: $function should be made static\n"; } } } close($fh); } else { err("unable to analyze '$so_file': $!\n"); exit 1; } foreach my $func (keys %{$export_ignore{$dir}}) { print "$dir:$func not used\n"; } delete $export_ignore{$dir}; } sub fix_tree($$$$$$) { my ($rootdir, $filter, $collect, $fix_c, $fix_rc, $fix_so)=@_; my @dirs=(""); while (@dirs) { my $dir=pop @dirs; if (opendir(my $dh, "$rootdir$dir")) { my ($so_file, @spec_files, @obj_files); foreach my $dentry (sort { $b cmp $a } readdir($dh)) { next if ($dentry eq "." or $dentry eq ".."); $dentry="$dir$dentry"; if (-d "$rootdir$dentry") { if (-l "$rootdir$dentry") { verbose("skipping '$dentry' directory symbolic link\n"); next; } push @dirs, "$dentry/"; next; } next if ($filter and $dentry !~ /^$filter/); if ($collect and $dentry =~ /\.[chly]$/) { collect($rootdir, $dentry); } elsif ($collect and $dentry eq "include/config.h.in") { collect($rootdir, $dentry); } elsif ($fix_c and $dentry =~ /\.[cly]$/) { fix_c_file($rootdir, $dentry); } elsif ($fix_rc and $dentry =~ /\.rc$/) { fix_rc_file($rootdir, $dentry); } elsif ($fix_so and $dentry =~ /\.so$/) { $so_file=$dentry; } elsif ($fix_so and $dentry =~ /\.spec$/) { push @spec_files, $dentry; } elsif ($fix_so and $dentry =~ /\.o$/ and $dentry !~ /\.cross\.o$/) { push @obj_files, $dentry; } } closedir($dh); fix_so($rootdir, $so_file, \@spec_files, \@obj_files) if ($fix_so); } else { err("unable to open the '$dir' directory: $!\n"); } } } ##### # # Main # ##### my ($opt_c, $opt_rc, $opt_so, $opt_help, $opt_dir, $opt_filter); use Getopt::Long; my $rc=GetOptions("c" => \$opt_c, "rc" => \$opt_rc, "so" => \$opt_so, "debug" => \$debug, "verbose" => \$verbose, "help" => \$opt_help ); if ($rc) { $opt_dir=shift @ARGV if (@ARGV); $opt_filter=shift @ARGV if (@ARGV); if (@ARGV) { err("only one directory can be specified\n"); $rc=0; } $opt_dir||="."; $opt_dir.="/" if ($opt_dir !~ m!/$!); } if (!$rc) { err("try running $name0 --help\n"); exit 2; } if (!defined $opt_c and !defined $opt_rc and !defined $opt_so) { $opt_c=$opt_rc=$opt_so=1; } $verbose=1 if ($debug); if ($opt_help) { print "Usage: $name0 [--c|--rc|--so] [--verbose] [--debug] [--help] [dir [filter]]\n"; print "\n"; print "Checks for various types of errors in the Wine source.\n"; print "\n"; print "Options:\n"; print " dir The directory containing the Wine sources\n"; print " filter Only handle that specific subdirectory\n"; print " --c Fix the C files\n"; print " --rc Fix the resource files\n"; print " --so Check the API exported by .so files\n"; print " --verbose Print some details and progress information\n"; print " --debug Print lots of debug information\n"; print " --help Prints this help message\n"; exit 0; } ### First pass: Collect information for the second pass verbose("First pass...\n"); fix_tree($opt_dir, $opt_filter, 1, 0, 0, 0); sub set_closure($@) { my $set=shift @_; my $count=0; while (1) { my $c=keys %$set; last if ($count == $c); $count=$c; foreach my $map (@_) { while (my ($name, $value)=each %$map) { $set->{$name}=1 if ($set->{$value}); } } } } # Find all the macros that expand to __stdcall my %stdcalls; map { $stdcalls{$_}=1 } @stdcall_seeds; set_closure(\%stdcalls, \%macros); verbose("stdcall = ", join(" ", sort keys %stdcalls), "\n"); $stdcall_re=join("|", keys %stdcalls); # Round up the macros that expand to 0 set_closure(\%zeroes, \%macros); verbose("zeroes = ", join(" ", sort keys %zeroes), "\n"); # Round up the pointer types set_closure(\%pointers, \%typedefs, \%macros); verbose("pointers = ", join(" ", sort keys %pointers), "\n"); # Find all languages with more than one sublanguage foreach my $name (keys %macros) { next if ($standard_sublangs{$name}); if ($name =~ /^SUB(LANG_[A-Z]+)(?:_[A-Z][A-Z_]+)?$/) { my $lang=$1; if ($name !~ /^SUBLANG_(?:LOWER|UPPER)_SORBIAN_GERMANY$/ and !exists $macros{$lang}) { err("$name has no matching language definition\n"); } my $val=$macros{$name}; my $indirect; if ($val =~ /^SUBLANG_/) { $val=$macros{$val}; $indirect=1; } $val =~ s/^.*\(\s*((?:0x)?[0-9a-fA-F]+)\s*\).*$/$1/; $val =~ s/^0x0*(?=0$|[1-9a-fA-F])//; if ($val !~ /^[0-9a-fA-F]+$/) { err("unknown sublanguage type for $name: $macros{$name}\n"); exit 1; } elsif ($val ne "1") { $lang_manysubs{$lang}=1; } elsif (!$indirect or ($lang_default{$lang} || "SUBLANG_DEFAULT") eq "SUBLANG_DEFAULT") { $lang_default{$lang}=$name; } } elsif ($name =~ /^(LANG_[A-Z]+)$/ and !$lang_default{$name}) { $lang_default{$name}="SUBLANG_DEFAULT"; } } ### Second pass: Actually fix files and report issues verbose("Second pass...\n"); fix_tree($opt_dir, $opt_filter, 0, $opt_c, $opt_rc, $opt_so); ### Check for unused blacklist entries if (-f "$opt_dir/configure.ac" and !$opt_filter) { if ($opt_c) { map { print "linefeed: $_ not found\n"; } sort keys %lf_blacklist; map { print "static: $_ not found\n"; } sort keys %static_ignore; map { print "debug channels: $_ not found\n"; } sort keys %dbg_ignore; } if ($opt_rc) { map { print "$_ should not be in \%lang_anysub\n" if (!$lang_manysubs{$_}) } sort keys %lang_anysub; } if ($opt_so) { map { print "export: $_ not found\n"; } sort keys %export_ignore; } }