3 # Copyright 1999-2000 Patrik Stridvall
5 # Note that winapi_check are using heuristics quite heavily.
6 # So always remember that:
8 # "Heuristics are bug ridden by definition.
9 # If they didn't have bugs, then they'd be algorithms."
11 # In other words, reported bugs are only potential bugs not
12 # real bugs, so they are called issues rather than bugs.
22 if($0 =~ /^((.*?)\/?tools\/winapi_check)\/winapi_check$/)
24 $winapi_check_dir = $1;
32 @INC = ($winapi_check_dir);
35 require "nativeapi.pm";
37 require "preprocessor.pm";
39 require "winapi_function.pm";
40 require "winapi_local.pm";
41 require "winapi_global.pm";
42 require "winapi_options.pm";
43 require "winapi_parser.pm";
50 import winapi_function;
53 import winapi_options;
57 my $current_dir = ".";
58 if(length($wine_dir) != 1) {
59 my $pwd; chomp($pwd = `pwd`);
60 foreach my $n (1..((length($wine_dir) + 1) / 3)) {
61 $pwd =~ s/\/([^\/]*)$//;
62 $current_dir = "$1/$current_dir";
64 $current_dir =~ s/\/.$//;
67 my $output = 'output'->new;
69 my $options = winapi_options->new($output, \@ARGV, $wine_dir);
70 if(!defined($options)) {
71 $output->write("usage: winapi_check [--help] [<files>]\n");
74 } elsif($options->help) {
83 if(!($file_dir =~ s/^(.*?)\/[^\/]*$/$1/)) {
87 $file_dir =~ s/^$wine_dir\///;
89 if($file_dir =~ /^(libtest|program|rc|tests|tools)/ ||
90 $file =~ /dbgmain\.c$/ ||
91 $file =~ /wineclipsrv\.c$/) # FIXME: Kludge
94 } elsif($file_dir =~ /^(debug|miscemu)/) {
101 my $modules = 'modules'->new($options, $output, $wine_dir, $current_dir, \&file_type, "$winapi_check_dir/modules.dat");
103 my $win16api = 'winapi'->new($options, $output, "win16", "$winapi_check_dir/win16");
104 my $win32api = 'winapi'->new($options, $output, "win32", "$winapi_check_dir/win32");
105 my @winapis = ($win16api, $win32api);
107 if($options->global) {
108 'winapi'->read_all_spec_files($modules, $wine_dir, $current_dir, \&file_type, $win16api, $win32api);
110 my @spec_files = $modules->allowed_spec_files($wine_dir, $current_dir);
111 'winapi'->read_spec_files($modules, $wine_dir, $current_dir, \@spec_files, $win16api, $win32api);
114 my $nativeapi = 'nativeapi'->new($options, $output, "$winapi_check_dir/nativeapi.dat", "$wine_dir/configure.in", "$wine_dir/include/config.h.in");
116 for my $name ($win32api->all_functions) {
117 my $module16 = $win16api->function_module($name);
118 my $module32 = $win32api->function_module($name);
120 if(defined($module16)) {
121 $win16api->found_shared_function($name);
122 $win32api->found_shared_function($name);
124 if($options->shared) {
125 $output->write("*.spec: $name: is shared between $module16 (Win16) and $module32 (Win32)\n");
135 } split(/\n/, `find . -name \\*.h`);
137 foreach my $file (@files) {
138 my $file_dir = $file;
139 if(!($file_dir =~ s/(.*?)\/[^\/]*$/$1/)) {
143 $includes{$file} = { name => $file };
147 if(/^\s*\#\s*include\s*\"(.*?)\"/) {
149 if(-e "$file_dir/$header") {
150 $includes{$file}{includes}{"$file_dir/$header"}++;
151 } elsif(-e "$wine_dir/include/$header") {
152 $includes{$file}{includes}{"include/$header"}++;
154 $output->write("$file: #include \"$header\" is not a local include\n");
161 my @files2 = ("acconfig.h", "poppack.h", "pshpack1.h", "pshpack2.h", "pshpack4.h", "pshpack8.h",
162 "storage.h", "ver.h");
163 foreach my $file2 (@files2) {
164 $includes{"include/$file2"}{used}++;
168 my %declared_functions;
171 my $progress_current=0;
172 my $progress_max=scalar($options->c_files);
174 if($options->headers) {
175 $progress_max += scalar($options->h_files);
177 foreach my $file ($options->h_files) {
181 if($options->progress) {
182 $output->progress("$file: file $progress_current of $progress_max");
185 my $found_function = sub {
186 my $documentation = shift;
188 my $return_type = shift;
189 my $calling_convention = shift;
190 my $internal_name = shift;
191 my $refargument_types = shift;
192 my @argument_types = @$refargument_types;
193 my $refargument_names = shift;
194 my @argument_names = @$refargument_names;
195 my $refargument_documentations = shift;
196 my @argument_documentations = @$refargument_documentations;
197 my $statements = shift;
199 foreach my $winapi (@winapis) {
200 my $module = $winapi->function_module($internal_name);
201 if(!defined($module)) { next }
203 my $external_name = $winapi->function_external_name($internal_name);
204 # FIXME: Kludge because of the THUNK variants
205 if(!defined($external_name)) {
209 my $output_function = sub {
212 $output->write("$file: $module: $return_type ");
213 $output->write("$calling_convention ") if $calling_convention;
214 $output->write("$internal_name(" . join(",", @argument_types) . "): $message\n");
217 if(!defined($declared_functions{$winapi->name}{$external_name})) {
218 $declared_functions{$winapi->name}{$external_name} = "$file";
219 } elsif($options->headers_duplicated) {
220 my $message = "declared more than once";
221 if($file ne $declared_functions{$winapi->name}{$external_name}) {
222 $message .= ", first declaration in '" . $declared_functions{$winapi->name}{$external_name} . "'";
224 &$output_function("$message");
227 if($options->headers_misplaced) {
228 if($file =~ /^include\/[^\/]*$/ && $winapi->name eq "win16") {
229 &$output_function("declaration misplaced");
230 } elsif($file =~ /^include\/wine\/[^\/]*$/ && $winapi->name eq "win32") {
231 &$output_function("declaration misplaced");
237 my $found_preprocessor = sub {
238 my $directive = shift;
239 my $argument = shift;
242 winapi_parser::parse_c_file $options, $output, $file, $found_function, $found_preprocessor;
247 my %module_pseudo_stub_count16;
248 my %module_pseudo_stub_count32;
250 foreach my $file ($options->c_files) {
253 my $file_module16 = $modules->allowed_modules_in_file("$current_dir/$file");
254 my $file_module32 = $modules->allowed_modules_in_file("$current_dir/$file");
257 if($options->progress) {
258 $output->progress("$file: file $progress_current of $progress_max");
261 my $file_dir = $file;
262 if(!($file_dir =~ s/(.*?)\/[^\/]*$/$1/)) {
266 my $file_type = file_type($file);
268 my $found_function = sub {
269 my $documentation = shift;
271 my $return_type = shift;
272 my $calling_convention = shift;
273 my $internal_name = shift;
274 my $refargument_types = shift;
275 my @argument_types = @$refargument_types;
276 my $refargument_names = shift;
277 my @argument_names = @$refargument_names;
278 my $refargument_documentations = shift;
279 my @argument_documentations = @$refargument_documentations;
280 my $statements = shift;
282 my $external_name16 = $win16api->function_external_name($internal_name);
283 my $external_name32 = $win32api->function_external_name($internal_name);
285 if($options->global) {
286 $win16api->found_type($return_type) if $options->win16;
287 $win32api->found_type($return_type) if $options->win32;
288 for my $argument (@argument_types) {
289 $win16api->found_type($argument) if $options->win16;
290 $win32api->found_type($argument) if $options->win32;
293 $win16api->found_function($internal_name) if $options->win16;
294 $win32api->found_function($internal_name) if $options->win32;
297 if($file_type ne "application") {
298 my $module16 = $win16api->function_module($internal_name);
299 my $module32 = $win32api->function_module($internal_name);
301 if(defined($module16)) {
302 $modules->found_module_in_dir($module16, $file_dir);
304 if(defined($module32)) {
305 $modules->found_module_in_dir($module32, $file_dir);
308 my $function = 'winapi_function'->new;
309 $functions{$internal_name} = $function;
311 $function->documentation($documentation);
312 $function->linkage($linkage);
313 $function->file($file);
314 $function->return_type($return_type);
315 $function->calling_convention($calling_convention);
316 $function->external_name16($external_name16);
317 $function->external_name32($external_name32);
318 $function->internal_name($internal_name);
319 $function->argument_types([@argument_types]);
320 $function->argument_names([@argument_names]);
321 $function->statements($statements);
322 $function->module16($module16);
323 $function->module32($module32);
326 $prefix .= "$file: ";
327 if(defined($module16) && !defined($module32)) {
328 $prefix .= "$module16: ";
329 } elsif(!defined($module16) && defined($module32)) {
330 $prefix .= "$module32: ";
331 } elsif(defined($module16) && defined($module32)) {
332 $prefix .= "$module16 & $module32: ";
336 $prefix .= "$return_type ";
337 $prefix .= "$calling_convention " if $calling_convention;
338 $prefix .= "$internal_name(" . join(",", @argument_types) . "): ";
339 $output->prefix($prefix);
341 if($options->local && $options->misplaced &&
342 $linkage ne "extern" && $statements)
344 if($options->win16 && $options->report_module($module16)) {
346 foreach my $module (split(/ & /, $module16)) {
347 foreach my $file_module (split(/ & /, $file_module16)) {
348 if($module eq $file_module) {
353 if(!$match && $file ne "library/port.c" && !$nativeapi->is_function($internal_name)) {
354 $output->write("is misplaced\n");
358 if($options->win32 && $options->report_module($module32)) {
360 foreach my $module (split(/ & /, $module32)) {
361 foreach my $file_module (split(/ & /, $file_module32)) {
362 if($module eq $file_module) {
367 if(!$match && $file ne "library/port.c" && !$nativeapi->is_function($internal_name)) {
368 $output->write("is misplaced\n");
373 if($options->local && $options->headers && $options->prototype) {
374 if($options->win16 && $options->report_module($module16)) {
375 if(!defined($external_name16) || (!$nativeapi->is_function($external_name16) &&
376 !defined($declared_functions{$win16api->name}{$external_name16})))
378 if(!defined($external_name16) || ($external_name16 !~ /^DllEntryPoint$/ &&
379 $internal_name !~ /^I(?:Malloc|Storage)16_fn/ &&
380 $internal_name !~ /^(?:\Q$module16\E|THUNK|WIN16)_\Q$external_name16\E(?:16)?$/))
382 $output->write("no prototype\n");
387 if($options->win32 && $options->report_module($module32)) {
388 if(!defined($external_name32) || (!$nativeapi->is_function($external_name32) && !defined($declared_functions{$win32api->name}{$external_name32})))
390 if(!defined($external_name32) || ($external_name32 !~ /^Dll(?:
391 Install|CanUnloadNow|GetClassObject|GetVersion|
392 RegisterServer|RegisterServerEx|UnregisterServer)|DriverProc$/x &&
393 $internal_name !~ /^COMCTL32_Str/ &&
394 $internal_name !~ /^(?:\Q$module32\E|wine)_(?:\Q$external_name32\E|\d+)$/))
396 $output->write("no prototype\n");
402 if($options->local && $options->argument) {
403 if($options->win16 && $options->report_module($module16)) {
404 winapi_local::check_function $options, $output,
405 $return_type, $calling_convention, $external_name16, $internal_name, [@argument_types], $nativeapi, $win16api;
407 if($options->win32 && $options->report_module($module32)) {
408 winapi_local::check_function $options, $output,
409 $return_type, $calling_convention, $external_name32, $internal_name, [@argument_types], $nativeapi, $win32api;
413 if($options->local && $options->statements) {
414 if($options->win16 && $options->report_module($module16)) {
415 winapi_local::check_statements $options, $output, $win16api, \%functions, $function;
418 if($options->win32 && $options->report_module($module32)) {
419 winapi_local::check_statements $options, $output, $win32api, \%functions, $function;
423 if($options->stubs) {
424 if(defined($statements) && $statements =~ /FIXME[^;]*stub/) {
425 if($options->win16 && $options->report_module($module16)) {
426 foreach my $module (split(/ \& /, $module16)) {
427 $module_pseudo_stub_count16{$module}++;
430 if($options->win32 && $options->report_module($module32)) {
431 foreach my $module (split(/ \& /, $module32)) {
432 $module_pseudo_stub_count32{$module}++;
438 if($options->local && $options->documentation &&
439 (defined($module16) || defined($module32)) &&
440 $linkage ne "extern" && $statements)
445 if(defined($module16) && !defined($module32)) {
446 my @uc_modules16 = split(/\s*\&\s*/, uc($module16));
447 push @uc_modules16, "WIN16";
449 $name1 = $internal_name;
450 foreach my $uc_module16 (@uc_modules16) {
451 if($name1 =~ s/^$uc_module16\_//) { last; }
454 # FIXME: This special case is becuase of a very ugly kludge that should be fixed IMHO
456 $name2 = s/^(.*?)16_fn(.*?)$/$116_$2/;
457 } elsif(!defined($module16) && defined($module32)) {
458 my @uc_modules32 = split(/\s*\&\s*/, uc($module32));
459 push @uc_modules32, "wine";
461 foreach my $uc_module32 (@uc_modules32) {
462 if($uc_module32 =~ /^WS2_32$/) {
463 push @uc_modules32, "WSOCK32";
467 $name1 = $internal_name;
468 foreach my $uc_module32 (@uc_modules32) {
469 if($name1 =~ s/^$uc_module32\_//) { last; }
475 my @uc_modules = split(/\s*\&\s*/, uc($module16));
476 push @uc_modules, split(/\s*\&\s*/, uc($module32));
478 $name1 = $internal_name;
479 foreach my $uc_module (@uc_modules) {
480 if($name1 =~ s/^$uc_module\_//) { last; }
486 if($documentation !~ /\b($internal_name|$name1|$name2)\b/) {
487 $output->write("documentation: \\\n$documentation\n");
490 if($options->documentation_width) {
491 if($documentation =~ /(\/\**)/) {
492 my $width = length($1);
494 $comment_width{$width}++;
495 if($width <= 65 || $width >= 81) {
496 $output->write("comment is $width columns wide\n");
501 if($options->documentation_arguments) {
503 for my $argument_documentation (@argument_documentations) {
505 if($argument_documentation ne "") {
506 if($argument_documentation !~ /^\/\*\s+\[(?:in|out|in\/out)\].*?\*\/$/) {
507 $output->write("argument $n documentation: \\\n$argument_documentation\n");
519 my $found_include = sub {
521 if(/^\"config\.h\"/) {
525 my $found_conditional = sub {
528 $nativeapi->found_conditional($_);
530 if($options->config) {
531 if($file_type ne "application") {
532 if(!$nativeapi->is_conditional($_)) {
533 if(/^HAVE_/ && !/^HAVE_(IPX|MESAGL|BUGGY_MESAGL|WINE_CONSTRUCTOR)$/)
535 $output->write("$file: $_ is not declared as a conditional\n");
540 $output->write("$file: conditional $_ used but config.h is not included\n");
546 my $preprocessor = 'preprocessor'->new($found_include, $found_conditional);
547 my $found_preprocessor = sub {
548 my $directive = shift;
549 my $argument = shift;
551 $preprocessor->directive($directive, $argument);
553 if($options->config) {
554 if($directive eq "include") {
556 my $check_protection;
558 if($argument =~ /^<(.*?)>$/) {
560 if($file_type ne "application") {
561 $check_protection = 1;
563 $check_protection = 0;
566 } elsif($argument =~ /^"(.*?)"$/) {
568 $check_protection = 0;
572 if($check_protection) {
573 if((-e "$wine_dir/include/$header" || -e "$file_dir/$header")) {
574 if($header !~ /^ctype.h$/) {
575 $output->write("$file: #include \<$header\> is a local include\n");
579 my $macro = uc($header);
580 $macro =~ y/\.\//__/;
581 $macro = "HAVE_" . $macro;
583 if($nativeapi->is_conditional_header($header)) {
584 if(!$preprocessor->is_def($macro)) {
585 if($macro =~ /^HAVE_X11/) {
586 # Do nothing X Windows is handled differently
587 } elsif($macro =~ /^HAVE_(.*?)_H$/) {
588 if($header ne "alloca.h" && !$preprocessor->is_def("STATFS_DEFINED_BY_$1")) {
589 $output->write("$file: #$directive $argument: is a conditional include, " .
590 "but is not protected\n");
594 } elsif($preprocessor->is_def($macro)) {
595 $output->write("$file: #$directive $argument: is protected, " .
596 "but is not a conditional include\n");
601 if(-e "$file_dir/$header") {
602 $includes{"$file_dir/$header"}{used}++;
603 foreach my $name (keys(%{$includes{"$file_dir/$header"}{includes}})) {
604 $includes{$name}{used}++;
606 } elsif(-e "$file_dir/../$header") { # FIXME: Kludge
607 $includes{"$file_dir/../$header"}{used}++; # FIXME: This is not correct
608 foreach my $name (keys(%{$includes{"$file_dir/../$header"}{includes}})) { # FIXME: This is not correct
609 $includes{$name}{used}++;
611 } elsif(-e "$wine_dir/include/$header") {
612 $includes{"include/$header"}{used}++;
613 foreach my $name (keys(%{$includes{"include/$header"}{includes}})) {
614 $includes{$name}{used}++;
617 $output->write("$file: #include \"$header\" is not a local include\n");
624 winapi_parser::parse_c_file $options, $output, $file, $found_function, $found_preprocessor;
626 if($options->config_unnessary) {
627 if($config && $conditional == 0) {
628 $output->write("$file: includes config.h but do not use any conditionals\n");
632 winapi_local::check_file $options, $output, $file, \%functions;
635 $output->hide_progress;
637 if($options->global) {
638 if($options->documentation_width) {
639 foreach my $width (sort(keys(%comment_width))) {
640 my $count = $comment_width{$width};
641 $output->write("*.c: $count functions have comments of width $width\n");
645 if($options->stubs) {
646 if($options->win16) {
647 my %module_stub_count16;
648 my %module_total_count16;
650 foreach my $name ($win16api->all_functions,$win16api->all_functions_stub) {
651 foreach my $module (split(/ \& /, $win16api->function_module($name))) {
652 if($win16api->function_stub($name)) {
653 $module_stub_count16{$module}++;
655 $module_total_count16{$module}++;
659 foreach my $module ($win16api->all_modules) {
660 if($options->report_module($module)) {
661 my $real_stubs = $module_stub_count16{$module};
662 my $pseudo_stubs = $module_pseudo_stub_count16{$module};
664 if(!defined($real_stubs)) { $real_stubs = 0; }
665 if(!defined($pseudo_stubs)) { $pseudo_stubs = 0; }
667 my $stubs = $real_stubs + $pseudo_stubs;
668 my $total = $module_total_count16{$module};
670 if(!defined($total)) { $total = 0;}
672 $output->write("*.c: $module: ");
673 $output->write("$stubs of $total functions are stubs ($real_stubs real, $pseudo_stubs pseudo)\n");
678 if($options->win32) {
679 my %module_stub_count32;
680 my %module_total_count32;
682 foreach my $name ($win32api->all_functions,$win32api->all_functions_stub) {
683 foreach my $module (split(/ \& /, $win32api->function_module($name))) {
684 if($win32api->function_stub($name)) {
685 $module_stub_count32{$module}++;
687 $module_total_count32{$module}++;
691 foreach my $module ($win32api->all_modules) {
692 if($options->report_module($module)) {
693 my $real_stubs = $module_stub_count32{$module};
694 my $pseudo_stubs = $module_pseudo_stub_count32{$module};
696 if(!defined($real_stubs)) { $real_stubs = 0; }
697 if(!defined($pseudo_stubs)) { $pseudo_stubs = 0; }
699 my $stubs = $real_stubs + $pseudo_stubs;
700 my $total = $module_total_count32{$module};
702 if(!defined($total)) { $total = 0;}
704 $output->write("*.c: $module: ");
705 $output->write("$stubs of $total functions are stubs ($real_stubs real, $pseudo_stubs pseudo)\n");
711 if($options->headers) {
712 foreach my $name (sort(keys(%includes))) {
713 if(!$includes{$name}{used}) {
714 if($options->include) {
715 $output->write("*.c: $name: include file is never used\n");
721 winapi_global::check $options, $output, $win16api, $nativeapi if $options->win16;
722 winapi_global::check $options, $output, $win32api, $nativeapi if $options->win32;
724 $modules->global_report;
725 $nativeapi->global_report;