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 $statements = shift;
197 foreach my $winapi (@winapis) {
198 my $module = $winapi->function_module($internal_name);
199 if(!defined($module)) { next }
201 my $external_name = $winapi->function_external_name($internal_name);
202 # FIXME: Kludge because of the THUNK variants
203 if(!defined($external_name)) {
207 my $output_function = sub {
210 $output->write("$file: $module: $return_type ");
211 $output->write("$calling_convention ") if $calling_convention;
212 $output->write("$internal_name(" . join(",", @argument_types) . "): $message\n");
215 if(!defined($declared_functions{$winapi->name}{$external_name})) {
216 $declared_functions{$winapi->name}{$external_name} = "$file";
217 } elsif($options->headers_duplicated) {
218 my $message = "declared more than once";
219 if($file ne $declared_functions{$winapi->name}{$external_name}) {
220 $message .= ", first declaration in '" . $declared_functions{$winapi->name}{$external_name} . "'";
222 &$output_function("$message");
225 if($options->headers_misplaced) {
226 if($file =~ /^include\/[^\
/]*$/ && $winapi->name eq "win16") {
227 &$output_function("declaration misplaced");
228 } elsif($file =~ /^include\/wine\
/[^\/]*$/ && $winapi->name eq "win32") {
229 &$output_function("declaration misplaced");
235 my $found_preprocessor = sub {
236 my $directive = shift;
237 my $argument = shift;
240 winapi_parser
::parse_c_file
$options, $output, $file, $found_function, $found_preprocessor;
245 my %module_pseudo_stub_count16;
246 my %module_pseudo_stub_count32;
248 foreach my $file ($options->c_files) {
251 my $file_module16 = $modules->allowed_modules_in_file("$current_dir/$file");
252 my $file_module32 = $modules->allowed_modules_in_file("$current_dir/$file");
255 if($options->progress) {
256 $output->progress("$file: file $progress_current of $progress_max");
259 my $file_dir = $file;
260 if(!($file_dir =~ s/(.*?)\/[^\/]*$/$1/)) {
264 my $file_type = file_type
($file);
266 my $found_function = sub {
267 my $documentation = shift;
269 my $return_type = shift;
270 my $calling_convention = shift;
271 my $internal_name = shift;
272 my $refargument_types = shift;
273 my @argument_types = @
$refargument_types;
274 my $refargument_names = shift;
275 my @argument_names = @
$refargument_names;
276 my $statements = shift;
278 my $external_name16 = $win16api->function_external_name($internal_name);
279 my $external_name32 = $win32api->function_external_name($internal_name);
281 if($options->global) {
282 $win16api->found_type($return_type) if $options->win16;
283 $win32api->found_type($return_type) if $options->win32;
284 for my $argument (@argument_types) {
285 $win16api->found_type($argument) if $options->win16;
286 $win32api->found_type($argument) if $options->win32;
289 $win16api->found_function($internal_name) if $options->win16;
290 $win32api->found_function($internal_name) if $options->win32;
293 if($file_type ne "application") {
294 my $module16 = $win16api->function_module($internal_name);
295 my $module32 = $win32api->function_module($internal_name);
297 my $function = 'winapi_function'->new;
298 $functions{$internal_name} = $function;
300 $function->documentation($documentation);
301 $function->linkage($linkage);
302 $function->file($file);
303 $function->return_type($return_type);
304 $function->calling_convention($calling_convention);
305 $function->external_name16($external_name16);
306 $function->external_name32($external_name32);
307 $function->internal_name($internal_name);
308 $function->argument_types([@argument_types]);
309 $function->argument_names([@argument_names]);
310 $function->statements($statements);
311 $function->module16($module16);
312 $function->module32($module32);
314 my $output_module = sub {
318 $output->write("$file: $module: $return_type ");
319 $output->write("$calling_convention ") if $calling_convention;
320 $output->write("$internal_name(" . join(",", @argument_types) . "): $msg\n");
323 my $output16 = &$output_module($module16);
324 my $output32 = &$output_module($module32);
326 if($options->local && $options->misplaced &&
327 $linkage ne "extern" && $statements)
329 if($options->win16 && $options->report_module($module16)) {
331 foreach my $module (split(/ & /, $module16)) {
332 foreach my $file_module (split(/ & /, $file_module16)) {
333 if($module eq $file_module) {
339 &$output16("is misplaced\n");
343 if($options->win32 && $options->report_module($module32)) {
345 foreach my $module (split(/ & /, $module32)) {
346 foreach my $file_module (split(/ & /, $file_module32)) {
347 if($module eq $file_module) {
353 &$output32("is misplaced");
358 if($options->local && $options->headers && $options->prototype) {
359 if($options->win16 && $options->report_module($module16)) {
360 if(!defined($external_name16) || (!$nativeapi->is_function($external_name16) &&
361 !defined($declared_functions{$win16api->name}{$external_name16})))
363 if(!defined($external_name16) || ($external_name16 !~ /^DllEntryPoint$/ &&
364 $internal_name !~ /^I(?:Malloc|Storage)16_fn/ &&
365 $internal_name !~ /^(?:\Q$module16\E|THUNK|WIN16)_\Q$external_name16\E(?:16)?$/))
367 &$output16("no prototype");
372 if($options->win32 && $options->report_module($module32)) {
373 if(!defined($external_name32) || (!$nativeapi->is_function($external_name32) && !defined($declared_functions{$win32api->name}{$external_name32})))
375 if(!defined($external_name32) || ($external_name32 !~ /^Dll
(?
:
376 Install
|CanUnloadNow
|GetClassObject
|GetVersion
|
377 RegisterServer
|RegisterServerEx
|UnregisterServer
)|DriverProc
$/x
&&
378 $internal_name !~ /^COMCTL32_Str/ &&
379 $internal_name !~ /^(?:\Q$module32\E|wine)_(?:\Q$external_name32\E|\d+)$/))
381 &$output32("no prototype");
387 if($options->local && $options->argument) {
388 if($options->win16 && $options->report_module($module16)) {
389 winapi_local
::check_function
$options, $output16,
390 $return_type, $calling_convention, $external_name16, $internal_name, [@argument_types], $nativeapi, $win16api;
392 if($options->win32 && $options->report_module($module32)) {
393 winapi_local
::check_function
$options, $output32,
394 $return_type, $calling_convention, $external_name32, $internal_name, [@argument_types], $nativeapi, $win32api;
398 if($options->local && $options->statements) {
399 if($options->win16 && $options->report_module($module16)) {
400 winapi_local
::check_statements
$options, $output16, $win16api, \
%functions, $function;
403 if($options->win32 && $options->report_module($module32)) {
404 winapi_local
::check_statements
$options, $output32, $win32api, \
%functions, $function;
408 if($options->stubs) {
409 if(defined($statements) && $statements =~ /FIXME[^;]*stub/) {
410 if($options->win16 && $options->report_module($module16)) {
411 foreach my $module (split(/ \& /, $module16)) {
412 $module_pseudo_stub_count16{$module}++;
415 if($options->win32 && $options->report_module($module32)) {
416 foreach my $module (split(/ \& /, $module32)) {
417 $module_pseudo_stub_count32{$module}++;
423 if($options->local && $options->documentation &&
424 (defined($module16) || defined($module32)) &&
425 $linkage ne "extern" && $statements)
430 if(defined($module16) && !defined($module32)) {
431 my @uc_modules16 = split(/\s*\&\s*/, uc($module16));
432 push @uc_modules16, "WIN16";
434 $name1 = $internal_name;
435 foreach my $uc_module16 (@uc_modules16) {
436 if($name1 =~ s/^$uc_module16\_//) { last; }
439 # FIXME: This special case is becuase of a very ugly kludge that should be fixed IMHO
441 $name2 = s/^(.*?)16_fn(.*?)$/$116_$2/;
442 } elsif(!defined($module16) && defined($module32)) {
443 my @uc_modules32 = split(/\s*\&\s*/, uc($module32));
444 push @uc_modules32, "wine";
446 $name1 = $internal_name;
447 foreach my $uc_module32 (@uc_modules32) {
448 if($name1 =~ s/^$uc_module32\_//) { last; }
454 my @uc_modules = split(/\s*\&\s*/, uc($module16));
455 push @uc_modules, split(/\s*\&\s*/, uc($module32));
457 $name1 = $internal_name;
458 foreach my $uc_module (@uc_modules) {
459 if($name1 =~ s/^$uc_module\_//) { last; }
465 if($documentation !~ /\b($internal_name|$name1|$name2)\b/) {
466 $output->write("$file: $internal_name: \\\n");
467 $output->write("$documentation\n");
470 if($options->documentation_width) {
471 if($documentation =~ /(\/\
**)/) {
472 my $width = length($1);
474 $comment_width{$width}++;
475 if($width <= 65 || $width >= 81) {
476 $output->write("$file: $internal_name: comment is $width columns wide\n");
486 my $found_include = sub {
488 if(/^\"config\.h\"/) {
492 my $found_conditional = sub {
495 if($options->config) {
496 if($file_type ne "application") {
497 if(!$nativeapi->is_conditional($_)) {
498 if(/^HAVE_/ && !/^HAVE_(IPX|MESAGL|BUGGY_MESAGL|WINE_CONSTRUCTOR)$/)
500 $output->write("$file: $_ is not declared as a conditional\n");
505 $output->write("$file: conditional $_ used but config.h is not included\n");
511 my $preprocessor = 'preprocessor'->new($found_include, $found_conditional);
512 my $found_preprocessor = sub {
513 my $directive = shift;
514 my $argument = shift;
516 $preprocessor->directive($directive, $argument);
518 if($options->config) {
519 if($directive eq "include") {
521 my $check_protection;
523 if($argument =~ /^<(.*?)>$/) {
525 if($file_type ne "application") {
526 $check_protection = 1;
528 $check_protection = 0;
531 } elsif($argument =~ /^"(.*?)"$/) {
533 $check_protection = 0;
537 if($check_protection) {
538 if((-e
"$wine_dir/include/$header" || -e
"$file_dir/$header")) {
539 if($header !~ /^ctype.h$/) {
540 $output->write("$file: #include \<$header\> is a local include\n");
544 my $macro = uc($header);
545 $macro =~ y/\.\//__
/;
546 $macro = "HAVE_" . $macro;
548 if($nativeapi->is_conditional_header($header)) {
549 if(!$preprocessor->is_def($macro)) {
550 if($macro =~ /^HAVE_X11/) {
551 # Do nothing X Windows is handled differently
552 } elsif($macro =~ /^HAVE_(.*?)_H$/) {
553 if($header ne "alloca.h" && !$preprocessor->is_def("STATFS_DEFINED_BY_$1")) {
554 $output->write("$file: #$directive $argument: is a conditional include, " .
555 "but is not protected\n");
559 } elsif($preprocessor->is_def($macro)) {
560 $output->write("$file: #$directive $argument: is protected, " .
561 "but is not a conditional include\n");
566 if(-e
"$file_dir/$header") {
567 $includes{"$file_dir/$header"}{used
}++;
568 foreach my $name (keys(%{$includes{"$file_dir/$header"}{includes
}})) {
569 $includes{$name}{used
}++;
571 } elsif(-e
"$file_dir/../$header") { # FIXME: Kludge
572 $includes{"$file_dir/../$header"}{used
}++; # FIXME: This is not correct
573 foreach my $name (keys(%{$includes{"$file_dir/../$header"}{includes
}})) { # FIXME: This is not correct
574 $includes{$name}{used
}++;
576 } elsif(-e
"$wine_dir/include/$header") {
577 $includes{"include/$header"}{used
}++;
578 foreach my $name (keys(%{$includes{"include/$header"}{includes
}})) {
579 $includes{$name}{used
}++;
582 $output->write("$file: #include \"$header\" is not a local include\n");
589 winapi_parser
::parse_c_file
$options, $output, $file, $found_function, $found_preprocessor;
591 if($options->config_unnessary) {
592 if($config && $conditional == 0) {
593 $output->write("$file: includes config.h but do not use any conditionals\n");
597 winapi_local
::check_file
$options, $output, $file, \
%functions;
600 $output->hide_progress;
602 if($options->global) {
603 if($options->documentation_width) {
604 foreach my $width (sort(keys(%comment_width))) {
605 my $count = $comment_width{$width};
606 $output->write("*.c: $count functions have comments of width $width\n");
610 if($options->stubs) {
611 if($options->win16) {
612 my %module_stub_count16;
613 my %module_total_count16;
615 foreach my $name ($win16api->all_functions,$win16api->all_functions_stub) {
616 foreach my $module (split(/ \& /, $win16api->function_module($name))) {
617 if($win16api->function_stub($name)) {
618 $module_stub_count16{$module}++;
620 $module_total_count16{$module}++;
624 foreach my $module ($win16api->all_modules) {
625 if($options->report_module($module)) {
626 my $real_stubs = $module_stub_count16{$module};
627 my $pseudo_stubs = $module_pseudo_stub_count16{$module};
629 if(!defined($real_stubs)) { $real_stubs = 0; }
630 if(!defined($pseudo_stubs)) { $pseudo_stubs = 0; }
632 my $stubs = $real_stubs + $pseudo_stubs;
633 my $total = $module_total_count16{$module};
635 if(!defined($total)) { $total = 0;}
637 $output->write("*.c: $module: ");
638 $output->write("$stubs of $total functions are stubs ($real_stubs real, $pseudo_stubs pseudo)\n");
643 if($options->win32) {
644 my %module_stub_count32;
645 my %module_total_count32;
647 foreach my $name ($win32api->all_functions,$win32api->all_functions_stub) {
648 foreach my $module (split(/ \& /, $win32api->function_module($name))) {
649 if($win32api->function_stub($name)) {
650 $module_stub_count32{$module}++;
652 $module_total_count32{$module}++;
656 foreach my $module ($win32api->all_modules) {
657 if($options->report_module($module)) {
658 my $real_stubs = $module_stub_count32{$module};
659 my $pseudo_stubs = $module_pseudo_stub_count32{$module};
661 if(!defined($real_stubs)) { $real_stubs = 0; }
662 if(!defined($pseudo_stubs)) { $pseudo_stubs = 0; }
664 my $stubs = $real_stubs + $pseudo_stubs;
665 my $total = $module_total_count32{$module};
667 if(!defined($total)) { $total = 0;}
669 $output->write("*.c: $module: ");
670 $output->write("$stubs of $total functions are stubs ($real_stubs real, $pseudo_stubs pseudo)\n");
676 foreach my $name (sort(keys(%includes))) {
677 if(!$includes{$name}{used
}) {
678 if($options->include) {
679 $output->write("*.c: $name: include file is never used\n");
684 winapi_global
::check
$options, $output, $win16api, $nativeapi if $options->win16;
685 winapi_global
::check
$options, $output, $win32api, $nativeapi if $options->win32;