#!/usr/bin/perl -w # Copyright 1999-2001 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; BEGIN { $0 =~ m%^(.*?/?tools)/winapi_check/winapi_check$%; require "$1/winapi/setup.pm"; } use config qw( &file_type &files_filter &files_skip &get_h_files $current_dir $wine_dir $winapi_dir $winapi_check_dir ); use modules; use nativeapi; use output; use preprocessor; use util qw(&is_subset); use winapi; use winapi_documentation; use winapi_function; use winapi_local; use winapi_global; use winapi_options; use winapi_parser; my $output = 'output'->new; my $options = winapi_options->new($output, \@ARGV, $wine_dir); if(!defined($options)) { $output->write("usage: winapi_check [--help] []\n"); exit 1; } elsif($options->help) { $options->show_help; exit; } 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"); my %declared_functions; my %include2info; { my @files = get_h_files("winelib"); my $progress_current = 0; my $progress_max = scalar(@files); foreach my $file (@files) { $progress_current++; if($options->progress) { $output->lazy_progress("$file: file $progress_current of $progress_max"); } my $file_dir = $file; if(!($file_dir =~ s%(.*?)/[^/]+$%$1%)) { $file_dir = "."; } $include2info{$file} = { name => $file }; open(IN, "< $wine_dir/$file"); while() { if(/^\s*\#\s*include\s*\"(.*?)\"/) { my $header = $1; if(-e "$wine_dir/$file_dir/$header") { $include2info{$file}{includes}{"$file_dir/$header"}++; } elsif(-e "$wine_dir/$file_dir/../$header") { if($file_dir =~ m%^(.*?)/[^/]+$%) { $include2info{$file}{includes}{"$1/$header"}++; } else { $include2info{$file}{includes}{"$header"}++; } } elsif(-e "$wine_dir/include/$header") { $include2info{$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) { $include2info{"include/$file2"}{used}++; } } my @c_files = $options->c_files; @c_files = files_skip(@c_files); @c_files = files_filter("winelib", @c_files); my @h_files = $options->h_files; @h_files = files_skip(@h_files); @h_files = files_filter("winelib", @h_files); my $progress_current = 0; my $progress_max = scalar(@c_files); if($options->headers) { $progress_max += scalar(@h_files); foreach my $file (@h_files) { my %functions; $progress_current++; if($options->progress) { $output->progress("$file: file $progress_current of $progress_max"); } my $found_function = sub { my $function = shift; $output->prefix($function->prefix); my $function_line = $function->function_line; my $internal_name = $function->internal_name; my $statements = $function->statements; if($options->headers_misplaced && !($function->is_win16 && $function->is_win32) && (($function->is_win16 && $file =~ /^include\/[^\/]*$/) || ($function->is_win32 && $file =~ /^include\/wine\/[^\/]*$/))) { $output->write("declaration misplaced\n"); } if(!defined($statements)) { my $previous_function = $declared_functions{$internal_name}; if(!defined($previous_function)) { $declared_functions{$internal_name} = $function; } elsif($options->headers_duplicated) { my $file = $previous_function->file; my $function_line = $previous_function->function_line; $output->write("duplicate declaration (first declaration at $file:$function_line)\n"); } } }; my $found_preprocessor = sub { my $directive = shift; my $argument = shift; }; winapi_parser::parse_c_file $options, $output, $file, $found_function, $found_preprocessor; } } my %module2functions = (); my %type_found = (); foreach my $file (@c_files) { my %functions = (); my @includes = (); my %needed_includes = (); 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 $found_function = sub { my $function = shift; $output->prefix($function->prefix); my $internal_name = $function->internal_name; $functions{$internal_name} = $function; my $declared_function = $declared_functions{$internal_name}; my $documentation_line = $function->documentation_line; my $documentation = $function->documentation; my $linkage = $function->linkage; my $return_type = $function->return_type; my $calling_convention = $function->calling_convention; my @argument_types = @{$function->argument_types}; my @argument_names = @{$function->argument_names}; my @argument_documentations = @{$function->argument_documentations}; my $statements = $function->statements; my $module16 = $function->module16; my $module32 = $function->module32; my $external_name16 = $function->external_name16; my $external_name32 = $function->external_name32; foreach my $module ($function->modules) { $module2functions{$module}{$internal_name} = $function; for my $type ($return_type, @argument_types) { $type_found{$module}{$type}++; } } foreach my $module ($function->modules) { $modules->found_module_in_dir($module, $file_dir); } if($options->shared) { if($win16api->is_shared_internal_function($internal_name) || $win32api->is_shared_internal_function($internal_name)) { $output->write("is shared between Win16 and Win32\n"); } } if(defined($declared_function)) { $needed_includes{$declared_function->file}++; } if(1) { # FIXME: Not correct if(defined($external_name16)) { $external_name16 = (split(/\s*&\s*/, $external_name16))[0]; } # FIXME: Not correct if(defined($external_name32)) { $external_name32 = (split(/\s*&\s*/, $external_name32))[0]; } if($options->local && $options->misplaced && $linkage ne "extern" && defined($statements)) { if($options->win16 && $options->report_module($module16)) { if($file ne "library/port.c" && !$nativeapi->is_function($internal_name) && !is_subset($module16, $file_module16)) { foreach my $module16 (split(/\s*&\s*/, $module16)) { if(!$win16api->is_function_stub($module16, $internal_name)) { $output->write("is misplaced ($module16)\n"); } } } } if($options->win32 && $options->report_module($module32)) { if($file ne "library/port.c" && !$nativeapi->is_function($internal_name) && !is_subset($module32, $file_module32)) { foreach my $module32 (split(/\s*&\s*/, $module32)) { if(!$win32api->is_function_stub($module32, $internal_name)) { $output->write("is misplaced ($module32)\n"); } } } } } if($options->local && $options->headers && $options->prototype) { if($options->win16 && $options->report_module($module16)) { if(!$nativeapi->is_function($internal_name) && !defined($declared_functions{$internal_name})) { $output->write("no prototype\n"); } } if($options->win32 && $options->report_module($module32)) { if(!defined($external_name32) || (!$nativeapi->is_function($external_name32) && !defined($declared_functions{$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->local && $options->documentation && (defined($module16) || defined($module32)) && $linkage ne "static" && ($linkage ne "" || defined($statements))) { winapi_documentation::check_documentation $options, $output, $win16api, $win32api, $modules, $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(!$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; $check_protection = 1; $check_local = 0; } elsif($argument =~ /^\"(.*?)\"$/) { $header = $1; $check_protection = 0; $check_local = 1; } else { $output->write("$file: #$directive $argument: is unparsable\n"); $header = undef; $check_protection = 0; $check_local = 0; } if(defined($header)) { if(-e "$wine_dir/include/$header") { push @includes, "include/$header"; } elsif(-e "$file_dir/$header") { push @includes, "$file_dir/$header"; } elsif(-e "$file_dir/../$header") { if($file_dir =~ m%^(.*?)/[^/]+$%) { push @includes, "$1/$header"; } else { push @includes, "$header"; } } elsif($header eq "controls.h") { # FIXME: Kludge push @includes, "dlls/user/controls.h"; } elsif($check_local) { $output->write("$file: #include \"$header\": file not found\n"); } } if($check_protection && $header) { if((-e "$wine_dir/include/$header" || -e "$wine_dir/$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 && $header) { if(-e "$file_dir/$header") { if($file_dir ne ".") { $include2info{"$file_dir/$header"}{used}++; foreach my $name (keys(%{$include2info{"$file_dir/$header"}{includes}})) { $include2info{$name}{used}++; } } else { $include2info{"$header"}{used}++; foreach my $name (keys(%{$include2info{"$header"}{includes}})) { $include2info{$name}{used}++; } } } elsif(-e "$wine_dir/$file_dir/../$header") { if($file_dir =~ m%^(.*?)/[^/]+$%) { $include2info{"$1/$header"}{used}++; foreach my $name (keys(%{$include2info{"$1/$header"}{includes}})) { $include2info{$name}{used}++; } } else { $include2info{"$header"}{used}++; foreach my $name (keys(%{$include2info{"$header"}{includes}})) { $include2info{$name}{used}++; } } } elsif($header eq "controls.h") { # FIXME: Kludge $include2info{"dlls/user/$header"}{used}++; foreach my $name (keys(%{$include2info{"dlls/user/$header"}{includes}})) { $include2info{$name}{used}++; } } elsif(-e "$wine_dir/include/$header") { $include2info{"include/$header"}{used}++; foreach my $name (keys(%{$include2info{"include/$header"}{includes}})) { $include2info{$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: include2info config.h but do not use any conditionals\n"); } } if($options->headers_needed) { my %includes2; foreach my $include (@includes) { $includes2{$include}++; foreach my $include (keys(%{$include2info{$include}{includes}})) { $includes2{$include}++; } } foreach my $needed_include (sort(keys(%needed_includes))) { my $found = 0; foreach my $include (sort(keys(%includes2))) { if($needed_include eq $include) { $found = 1; } } if(!$found) { $output->write("$file: file '$needed_include' needed but not included\n"); } } } winapi_local::check_file $options, $output, $file, \%functions; } $output->hide_progress; if($options->declared) { my %dirs; foreach my $file (@c_files) { my $dir = $file; $dir =~ s%/?[^/]*$%%; if($dir) { if($current_dir ne ".") { $dir = "$current_dir/$dir"; } } else { $dir = "$current_dir"; } $dirs{$dir}++; } foreach my $module ($modules->all_modules) { my $incomplete = 0; foreach my $module_dir ($modules->allowed_dirs_for_module($module)) { my $found = 0; foreach my $dir (sort(keys(%dirs))) { if($module_dir eq $dir) { $found = 1; last; } } if(!$found) { $incomplete = 1; } } if(!$incomplete) { if($options->declared) { foreach my $winapi (@winapis) { if(!$winapi->is_module($module)) { next; } my $functions = $module2functions{$module}; foreach my $internal_name ($winapi->all_internal_functions_in_module($module)) { my $function = $functions->{$internal_name}; if(!defined($function) && !$nativeapi->is_function($internal_name) && !($module eq "user" && $internal_name =~ /^(?:GlobalAddAtomA|GlobalDeleteAtom|GlobalFindAtomA| GlobalGetAtomNameA|lstrcmpiA)$/x)) { $output->write("*.c: $module: $internal_name: " . "function declared but not implemented or declared external\n"); } } } } } } } if($options->global) { winapi_documentation::report_documentation $options, $output; if($options->headers_unused) { foreach my $name (sort(keys(%include2info))) { if(!$include2info{$name}{used}) { if($options->include) { $output->write("*.c: $name: include file is never used\n"); } } } } winapi_global::check $options, $output, $win16api, $nativeapi, \%type_found if $options->win16; winapi_global::check $options, $output, $win32api, $nativeapi, \%type_found if $options->win32; $modules->global_report; $nativeapi->global_report; }