| #!/usr/bin/perl -w |
| |
| # Copyright 1999-2000 Patrik Stridvall |
| |
| # Note that winapi_check are using heuristics quite heavily. |
| # So always remember that: |
| # |
| # "Heuristics are bug ridden by definition. |
| # If they didn't have bugs, then they'd be algorithms." |
| # |
| # In other words, reported bugs are only potential bugs not |
| # real bugs, so they are called issues rather than bugs. |
| # |
| |
| use strict; |
| |
| my $wine_dir; |
| my $winapi_check_dir; |
| |
| BEGIN { |
| |
| if($0 =~ /^((.*?)\/?tools\/winapi_check)\/winapi_check$/) |
| { |
| $winapi_check_dir = $1; |
| if($2 ne "") |
| { |
| $wine_dir = $2; |
| } else { |
| $wine_dir = "."; |
| } |
| } |
| @INC = ($winapi_check_dir); |
| |
| require "modules.pm"; |
| require "nativeapi.pm"; |
| require "output.pm"; |
| require "preprocessor.pm"; |
| require "winapi.pm"; |
| require "winapi_documentation.pm"; |
| require "winapi_function.pm"; |
| require "winapi_local.pm"; |
| require "winapi_global.pm"; |
| require "winapi_options.pm"; |
| require "winapi_parser.pm"; |
| |
| import modules; |
| import nativeapi; |
| import output; |
| import preprocessor; |
| import winapi; |
| import winapi_documentation; |
| import winapi_function; |
| import winapi_local; |
| import winapi_global; |
| import winapi_options; |
| import winapi_parser; |
| } |
| |
| my $current_dir = "."; |
| if(length($wine_dir) != 1) { |
| my $pwd; chomp($pwd = `pwd`); |
| foreach my $n (1..((length($wine_dir) + 1) / 3)) { |
| $pwd =~ s/\/([^\/]*)$//; |
| $current_dir = "$1/$current_dir"; |
| } |
| $current_dir =~ s/\/.$//; |
| } |
| |
| my $output = 'output'->new; |
| |
| my $options = winapi_options->new($output, \@ARGV, $wine_dir); |
| if(!defined($options)) { |
| $output->write("usage: winapi_check [--help] [<files>]\n"); |
| |
| exit 1; |
| } elsif($options->help) { |
| $options->show_help; |
| exit; |
| } |
| |
| sub file_type { |
| my $file = shift; |
| |
| my $file_dir = $file; |
| if(!($file_dir =~ s/^(.*?)\/[^\/]*$/$1/)) { |
| $file_dir = "."; |
| } |
| |
| $file_dir =~ s/^$wine_dir\///; |
| |
| if($file_dir =~ /^(libtest|program|rc|tests|tools)/ || |
| $file =~ /dbgmain\.c$/ || |
| $file =~ /wineclipsrv\.c$/) # FIXME: Kludge |
| { |
| return "application"; |
| } elsif($file_dir =~ /^(debug|miscemu)/) { |
| return "emulator"; |
| } else { |
| return "library"; |
| } |
| } |
| |
| sub file_skip { |
| local $_ = shift; |
| |
| if(/agl.c$/) { |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| my $modules = 'modules'->new($options, $output, $wine_dir, $current_dir, \&file_type, "$winapi_check_dir/modules.dat"); |
| |
| my $win16api = 'winapi'->new($options, $output, "win16", "$winapi_check_dir/win16"); |
| my $win32api = 'winapi'->new($options, $output, "win32", "$winapi_check_dir/win32"); |
| my @winapis = ($win16api, $win32api); |
| |
| if($options->global) { |
| 'winapi'->read_all_spec_files($modules, $wine_dir, $current_dir, \&file_type, $win16api, $win32api); |
| } else { |
| my @spec_files = $modules->allowed_spec_files($wine_dir, $current_dir); |
| 'winapi'->read_spec_files($modules, $wine_dir, $current_dir, \@spec_files, $win16api, $win32api); |
| } |
| |
| my $nativeapi = 'nativeapi'->new($options, $output, "$winapi_check_dir/nativeapi.dat", "$wine_dir/configure.in", "$wine_dir/include/config.h.in"); |
| |
| for my $name ($win32api->all_functions) { |
| my $module16 = $win16api->function_module($name); |
| my $module32 = $win32api->function_module($name); |
| |
| if(defined($module16)) { |
| $win16api->found_shared_function($name); |
| $win32api->found_shared_function($name); |
| |
| if($options->shared) { |
| $output->write("*.spec: $name: is shared between $module16 (Win16) and $module32 (Win32)\n"); |
| } |
| } |
| } |
| |
| my %includes; |
| { |
| my @files = map { |
| s/^.\/(.*)$/$1/; |
| $_; |
| } split(/\n/, `find . -name \\*.h`); |
| |
| foreach my $file (@files) { |
| my $file_dir = $file; |
| if(!($file_dir =~ s/(.*?)\/[^\/]*$/$1/)) { |
| $file_dir = "."; |
| } |
| |
| $includes{$file} = { name => $file }; |
| |
| open(IN, "< $file"); |
| while(<IN>) { |
| if(/^\s*\#\s*include\s*\"(.*?)\"/) { |
| my $header = $1; |
| if(-e "$file_dir/$header") { |
| $includes{$file}{includes}{"$file_dir/$header"}++; |
| } elsif(-e "$file_dir/../$header") { # FIXME: This is not correct |
| $includes{$file}{includes}{"$file_dir/../$header"}++; # FIXME: This is not correct |
| } elsif(-e "$wine_dir/include/$header") { |
| $includes{$file}{includes}{"include/$header"}++; |
| } else { |
| $output->write("$file: #include \"$header\" is not a local include\n"); |
| } |
| } |
| } |
| close(IN); |
| } |
| |
| my @files2 = ("acconfig.h", "poppack.h", "pshpack1.h", "pshpack2.h", "pshpack4.h", "pshpack8.h", |
| "storage.h", "ver.h"); |
| foreach my $file2 (@files2) { |
| $includes{"include/$file2"}{used}++; |
| } |
| } |
| |
| my %declared_functions; |
| |
| my $progress_output; |
| my $progress_current=0; |
| my $progress_max=scalar($options->c_files); |
| |
| if($options->headers) { |
| $progress_max += scalar($options->h_files); |
| |
| foreach my $file ($options->h_files) { |
| my %functions; |
| |
| $progress_current++; |
| if($options->progress) { |
| $output->progress("$file: file $progress_current of $progress_max"); |
| } |
| |
| my $found_function = sub { |
| my $documentation = shift; |
| my $linkage = shift; |
| my $return_type = shift; |
| my $calling_convention = shift; |
| my $internal_name = shift; |
| my $refargument_types = shift; |
| my @argument_types = @$refargument_types; |
| my $refargument_names = shift; |
| my @argument_names = @$refargument_names; |
| my $refargument_documentations = shift; |
| my @argument_documentations = @$refargument_documentations; |
| my $statements = shift; |
| |
| foreach my $winapi (@winapis) { |
| my $module = $winapi->function_module($internal_name); |
| if(!defined($module)) { next } |
| |
| my $external_name = $winapi->function_external_name($internal_name); |
| # FIXME: Kludge because of the THUNK variants |
| if(!defined($external_name)) { |
| next; |
| } |
| |
| my $output_function = sub { |
| my $message = shift; |
| |
| $output->write("$file: $module: $return_type "); |
| $output->write("$calling_convention ") if $calling_convention; |
| $output->write("$internal_name(" . join(",", @argument_types) . "): $message\n"); |
| }; |
| |
| if(!defined($declared_functions{$winapi->name}{$external_name})) { |
| $declared_functions{$winapi->name}{$external_name} = "$file"; |
| } elsif($options->headers_duplicated) { |
| my $message = "declared more than once"; |
| if($file ne $declared_functions{$winapi->name}{$external_name}) { |
| $message .= ", first declaration in '" . $declared_functions{$winapi->name}{$external_name} . "'"; |
| } |
| &$output_function("$message"); |
| } |
| |
| if($options->headers_misplaced) { |
| if($file =~ /^include\/[^\/]*$/ && $winapi->name eq "win16") { |
| &$output_function("declaration misplaced"); |
| } elsif($file =~ /^include\/wine\/[^\/]*$/ && $winapi->name eq "win32") { |
| &$output_function("declaration misplaced"); |
| } |
| } |
| } |
| }; |
| |
| my $found_preprocessor = sub { |
| my $directive = shift; |
| my $argument = shift; |
| }; |
| |
| winapi_parser::parse_c_file $options, $output, $file, $found_function, $found_preprocessor; |
| } |
| } |
| |
| my %module_pseudo_stub_count16; |
| my %module_pseudo_stub_count32; |
| |
| foreach my $file ($options->c_files) { |
| my %functions = (); |
| |
| my $file_module16 = $modules->allowed_modules_in_file("$current_dir/$file"); |
| my $file_module32 = $modules->allowed_modules_in_file("$current_dir/$file"); |
| |
| $progress_current++; |
| if($options->progress) { |
| $output->progress("$file: file $progress_current of $progress_max"); |
| } |
| |
| my $file_dir = $file; |
| if(!($file_dir =~ s/(.*?)\/[^\/]*$/$1/)) { |
| $file_dir = "."; |
| } |
| |
| my $file_type = file_type($file); |
| |
| if(file_skip($file)) { |
| next; |
| } |
| |
| my $found_function = sub { |
| my $documentation = shift; |
| my $linkage = shift; |
| my $return_type = shift; |
| my $calling_convention = shift; |
| my $internal_name = shift; |
| my $refargument_types = shift; |
| my @argument_types = @$refargument_types; |
| my $refargument_names = shift; |
| my @argument_names = @$refargument_names; |
| my $refargument_documentations = shift; |
| my @argument_documentations = @$refargument_documentations; |
| my $statements = shift; |
| |
| my $external_name16 = $win16api->function_external_name($internal_name); |
| my $external_name32 = $win32api->function_external_name($internal_name); |
| |
| if($options->global) { |
| $win16api->found_type($return_type) if $options->win16; |
| $win32api->found_type($return_type) if $options->win32; |
| for my $argument (@argument_types) { |
| $win16api->found_type($argument) if $options->win16; |
| $win32api->found_type($argument) if $options->win32; |
| } |
| |
| $win16api->found_function($internal_name) if $options->win16; |
| $win32api->found_function($internal_name) if $options->win32; |
| } |
| |
| if($file_type ne "application") { |
| my $module16 = $win16api->function_module($internal_name); |
| my $module32 = $win32api->function_module($internal_name); |
| |
| if(defined($module16)) { |
| $modules->found_module_in_dir($module16, $file_dir); |
| } |
| if(defined($module32)) { |
| $modules->found_module_in_dir($module32, $file_dir); |
| } |
| |
| my $previous_function; |
| if(defined($functions{$internal_name})) { |
| $previous_function = $functions{$internal_name}; |
| } |
| |
| my $function = 'winapi_function'->new; |
| $functions{$internal_name} = $function; |
| |
| $function->documentation($documentation); |
| $function->linkage($linkage); |
| $function->file($file); |
| $function->return_type($return_type); |
| $function->calling_convention($calling_convention); |
| $function->external_name16($external_name16); |
| $function->external_name32($external_name32); |
| $function->internal_name($internal_name); |
| $function->argument_types([@argument_types]); |
| $function->argument_names([@argument_names]); |
| $function->argument_documentations([@argument_documentations]); |
| $function->statements($statements); |
| $function->module16($module16); |
| $function->module32($module32); |
| |
| my $prefix = ""; |
| $prefix .= "$file: "; |
| if(defined($module16) && !defined($module32)) { |
| $prefix .= "$module16: "; |
| } elsif(!defined($module16) && defined($module32)) { |
| $prefix .= "$module32: "; |
| } elsif(defined($module16) && defined($module32)) { |
| $prefix .= "$module16 & $module32: "; |
| } else { |
| $prefix .= "<>: "; |
| } |
| $prefix .= "$return_type "; |
| $prefix .= "$calling_convention " if $calling_convention; |
| $prefix .= "$internal_name(" . join(",", @argument_types) . "): "; |
| $output->prefix($prefix); |
| |
| if($options->local && $options->misplaced && |
| $linkage ne "extern" && $statements) |
| { |
| if($options->win16 && $options->report_module($module16)) { |
| my $match = 0; |
| foreach my $module (split(/ & /, $module16)) { |
| foreach my $file_module (split(/ & /, $file_module16)) { |
| if($module eq $file_module) { |
| $match++; |
| } |
| } |
| } |
| if(!$match && $file ne "library/port.c" && !$nativeapi->is_function($internal_name)) { |
| $output->write("is misplaced\n"); |
| } |
| } |
| |
| if($options->win32 && $options->report_module($module32)) { |
| my $match = 0; |
| foreach my $module (split(/ & /, $module32)) { |
| foreach my $file_module (split(/ & /, $file_module32)) { |
| if($module eq $file_module) { |
| $match++; |
| } |
| } |
| } |
| if(!$match && $file ne "library/port.c" && !$nativeapi->is_function($internal_name)) { |
| $output->write("is misplaced\n"); |
| } |
| } |
| } |
| |
| if($options->local && $options->headers && $options->prototype) { |
| if($options->win16 && $options->report_module($module16)) { |
| if(!defined($external_name16) || (!$nativeapi->is_function($external_name16) && |
| !defined($declared_functions{$win16api->name}{$external_name16}))) |
| { |
| if(!defined($external_name16) || ($external_name16 !~ /^DllEntryPoint$/ && |
| $internal_name !~ /^I(?:Malloc|Storage)16_fn/ && |
| $internal_name !~ /^(?:\Q$module16\E|THUNK|WIN16)_\Q$external_name16\E(?:16)?$/)) |
| { |
| $output->write("no prototype\n"); |
| } |
| } |
| } |
| |
| if($options->win32 && $options->report_module($module32)) { |
| if(!defined($external_name32) || (!$nativeapi->is_function($external_name32) && !defined($declared_functions{$win32api->name}{$external_name32}))) |
| { |
| if(!defined($external_name32) || ($external_name32 !~ /^Dll(?: |
| Install|CanUnloadNow|GetClassObject|GetVersion| |
| RegisterServer|RegisterServerEx|UnregisterServer)|DriverProc$/x && |
| $internal_name !~ /^COMCTL32_Str/ && |
| $internal_name !~ /^(?:\Q$module32\E|wine)_(?:\Q$external_name32\E|\d+)$/)) |
| { |
| $output->write("no prototype\n"); |
| } |
| } |
| } |
| } |
| |
| if($options->local && $options->argument) { |
| if($options->win16 && $options->report_module($module16)) { |
| winapi_local::check_function $options, $output, |
| $return_type, $calling_convention, $external_name16, $internal_name, [@argument_types], $nativeapi, $win16api; |
| } |
| if($options->win32 && $options->report_module($module32)) { |
| winapi_local::check_function $options, $output, |
| $return_type, $calling_convention, $external_name32, $internal_name, [@argument_types], $nativeapi, $win32api; |
| } |
| } |
| |
| if($options->local && $options->statements) { |
| if($options->win16 && $options->report_module($module16)) { |
| winapi_local::check_statements $options, $output, $win16api, \%functions, $function; |
| } |
| |
| if($options->win32 && $options->report_module($module32)) { |
| winapi_local::check_statements $options, $output, $win32api, \%functions, $function; |
| } |
| } |
| |
| if($options->stubs) { |
| if(defined($statements) && $statements =~ /FIXME[^;]*stub/) { |
| if($options->win16 && $options->report_module($module16)) { |
| foreach my $module (split(/ \& /, $module16)) { |
| $module_pseudo_stub_count16{$module}++; |
| } |
| } |
| if($options->win32 && $options->report_module($module32)) { |
| foreach my $module (split(/ \& /, $module32)) { |
| $module_pseudo_stub_count32{$module}++; |
| } |
| } |
| } |
| } |
| |
| if($options->local && $options->documentation && |
| !defined($previous_function) && |
| (defined($module16) || defined($module32)) && |
| $linkage ne "extern" && $statements) |
| { |
| winapi_documentation::check_documentation $options, $output, $win16api, $win32api, $function; |
| } |
| $output->prefix(""); |
| } |
| }; |
| |
| my $config = 0; |
| my $conditional = 0; |
| my $found_include = sub { |
| local $_ = shift; |
| if(/^\"config\.h\"/) { |
| $config++; |
| } |
| }; |
| my $found_conditional = sub { |
| local $_ = shift; |
| |
| $nativeapi->found_conditional($_); |
| |
| if($options->config) { |
| if($file_type ne "application") { |
| if(!$nativeapi->is_conditional($_)) { |
| if(/^HAVE_/ && !/^HAVE_(IPX|MESAGL|BUGGY_MESAGL|WINE_CONSTRUCTOR)$/) |
| { |
| $output->write("$file: $_ is not declared as a conditional\n"); |
| } |
| } else { |
| $conditional++; |
| if(!$config) { |
| $output->write("$file: conditional $_ used but config.h is not included\n"); |
| } |
| } |
| } |
| } |
| }; |
| my $preprocessor = 'preprocessor'->new($found_include, $found_conditional); |
| my $found_preprocessor = sub { |
| my $directive = shift; |
| my $argument = shift; |
| |
| $preprocessor->directive($directive, $argument); |
| |
| if($options->config) { |
| if($directive eq "include") { |
| my $header; |
| my $check_protection; |
| my $check_local; |
| if($argument =~ /^<(.*?)>$/) { |
| $header = $1; |
| if($file_type ne "application") { |
| $check_protection = 1; |
| } else { |
| $check_protection = 0; |
| } |
| $check_local = 0; |
| } elsif($argument =~ /^"(.*?)"$/) { |
| $header = $1; |
| $check_protection = 0; |
| $check_local = 1; |
| } |
| |
| if($check_protection) { |
| if((-e "$wine_dir/include/$header" || -e "$file_dir/$header")) { |
| if($header !~ /^ctype.h$/) { |
| $output->write("$file: #include \<$header\> is a local include\n"); |
| } |
| } |
| |
| my $macro = uc($header); |
| $macro =~ y/\.\//__/; |
| $macro = "HAVE_" . $macro; |
| |
| if($nativeapi->is_conditional_header($header)) { |
| if(!$preprocessor->is_def($macro)) { |
| if($macro =~ /^HAVE_X11/) { |
| # Do nothing X Windows is handled differently |
| } elsif($macro =~ /^HAVE_(.*?)_H$/) { |
| if($header ne "alloca.h" && !$preprocessor->is_def("STATFS_DEFINED_BY_$1")) { |
| $output->write("$file: #$directive $argument: is a conditional include, " . |
| "but is not protected\n"); |
| } |
| } |
| } |
| } elsif($preprocessor->is_def($macro)) { |
| $output->write("$file: #$directive $argument: is protected, " . |
| "but is not a conditional include\n"); |
| } |
| } |
| |
| if($check_local) { |
| if(-e "$file_dir/$header") { |
| $includes{"$file_dir/$header"}{used}++; |
| foreach my $name (keys(%{$includes{"$file_dir/$header"}{includes}})) { |
| $includes{$name}{used}++; |
| } |
| } elsif(-e "$file_dir/../$header") { # FIXME: Kludge |
| $includes{"$file_dir/../$header"}{used}++; # FIXME: This is not correct |
| foreach my $name (keys(%{$includes{"$file_dir/../$header"}{includes}})) { # FIXME: This is not correct |
| $includes{$name}{used}++; |
| } |
| } elsif($header eq "controls.h") { # FIXME: Kludge |
| $includes{"dlls/user/$header"}{used}++; |
| foreach my $name (keys(%{$includes{"dlls/user/$header"}{includes}})) { |
| $includes{$name}{used}++; |
| } |
| } elsif(-e "$wine_dir/include/$header") { |
| $includes{"include/$header"}{used}++; |
| foreach my $name (keys(%{$includes{"include/$header"}{includes}})) { |
| $includes{$name}{used}++; |
| } |
| } else { |
| $output->write("$file: #include \"$header\" is not a local include\n"); |
| } |
| } |
| } |
| } |
| }; |
| |
| winapi_parser::parse_c_file $options, $output, $file, $found_function, $found_preprocessor; |
| |
| if($options->config_unnessary) { |
| if($config && $conditional == 0) { |
| $output->write("$file: includes config.h but do not use any conditionals\n"); |
| } |
| } |
| |
| winapi_local::check_file $options, $output, $file, \%functions; |
| } |
| |
| $output->hide_progress; |
| |
| if($options->global) { |
| winapi_documentation::report_documentation $options, $output; |
| |
| if($options->stubs) { |
| if($options->win16) { |
| my %module_stub_count16; |
| my %module_total_count16; |
| |
| foreach my $name ($win16api->all_functions,$win16api->all_functions_stub) { |
| foreach my $module (split(/ \& /, $win16api->function_module($name))) { |
| if($win16api->function_stub($name)) { |
| $module_stub_count16{$module}++; |
| } |
| $module_total_count16{$module}++; |
| } |
| } |
| |
| foreach my $module ($win16api->all_modules) { |
| if($options->report_module($module)) { |
| my $real_stubs = $module_stub_count16{$module}; |
| my $pseudo_stubs = $module_pseudo_stub_count16{$module}; |
| |
| if(!defined($real_stubs)) { $real_stubs = 0; } |
| if(!defined($pseudo_stubs)) { $pseudo_stubs = 0; } |
| |
| my $stubs = $real_stubs + $pseudo_stubs; |
| my $total = $module_total_count16{$module}; |
| |
| if(!defined($total)) { $total = 0;} |
| |
| $output->write("*.c: $module: "); |
| $output->write("$stubs of $total functions are stubs ($real_stubs real, $pseudo_stubs pseudo)\n"); |
| } |
| } |
| } |
| |
| if($options->win32) { |
| my %module_stub_count32; |
| my %module_total_count32; |
| |
| foreach my $name ($win32api->all_functions,$win32api->all_functions_stub) { |
| foreach my $module (split(/ \& /, $win32api->function_module($name))) { |
| if($win32api->function_stub($name)) { |
| $module_stub_count32{$module}++; |
| } |
| $module_total_count32{$module}++; |
| } |
| } |
| |
| foreach my $module ($win32api->all_modules) { |
| if($options->report_module($module)) { |
| my $real_stubs = $module_stub_count32{$module}; |
| my $pseudo_stubs = $module_pseudo_stub_count32{$module}; |
| |
| if(!defined($real_stubs)) { $real_stubs = 0; } |
| if(!defined($pseudo_stubs)) { $pseudo_stubs = 0; } |
| |
| my $stubs = $real_stubs + $pseudo_stubs; |
| my $total = $module_total_count32{$module}; |
| |
| if(!defined($total)) { $total = 0;} |
| |
| $output->write("*.c: $module: "); |
| $output->write("$stubs of $total functions are stubs ($real_stubs real, $pseudo_stubs pseudo)\n"); |
| } |
| } |
| } |
| } |
| |
| if($options->headers) { |
| foreach my $name (sort(keys(%includes))) { |
| if(!$includes{$name}{used}) { |
| if($options->include) { |
| $output->write("*.c: $name: include file is never used\n"); |
| } |
| } |
| } |
| } |
| |
| winapi_global::check $options, $output, $win16api, $nativeapi if $options->win16; |
| winapi_global::check $options, $output, $win32api, $nativeapi if $options->win32; |
| |
| $modules->global_report; |
| $nativeapi->global_report; |
| } |