- Minor API files update.
[wine.git] / tools / winapi_check / winapi_check
blob1f518579c659ede042e459e38f029069ce7a26b0
1 #!/usr/bin/perl -w
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.
15 use strict;
17 my $wine_dir;
18 my $winapi_check_dir;
20 BEGIN {
22 if($0 =~ /^((.*?)\/?tools\/winapi_check)\/winapi_check$/)
24 $winapi_check_dir = $1;
25 if($2 ne "")
27 $wine_dir = $2;
28 } else {
29 $wine_dir = ".";
32 @INC = ($winapi_check_dir);
34 require "modules.pm";
35 require "nativeapi.pm";
36 require "output.pm";
37 require "preprocessor.pm";
38 require "winapi.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";
45 import modules;
46 import nativeapi;
47 import output;
48 import preprocessor;
49 import winapi;
50 import winapi_function;
51 import winapi_local;
52 import winapi_global;
53 import winapi_options;
54 import winapi_parser;
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");
73 exit 1;
74 } elsif($options->help) {
75 $options->show_help;
76 exit;
79 sub file_type {
80 my $file = shift;
82 my $file_dir = $file;
83 if(!($file_dir =~ s/^(.*?)\/[^\/]*$/$1/)) {
84 $file_dir = ".";
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
93 return "application";
94 } elsif($file_dir =~ /^(debug|miscemu)/) {
95 return "emulator";
96 } else {
97 return "library";
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);
109 } else {
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");
130 my %includes;
132 my @files = map {
133 s/^.\/(.*)$/$1/;
134 $_;
135 } split(/\n/, `find . -name \\*.h`);
137 foreach my $file (@files) {
138 my $file_dir = $file;
139 if(!($file_dir =~ s/(.*?)\/[^\/]*$/$1/)) {
140 $file_dir = ".";
143 $includes{$file} = { name => $file };
145 open(IN, "< $file");
146 while(<IN>) {
147 if(/^\s*\#\s*include\s*\"(.*?)\"/) {
148 my $header = $1;
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"}++;
153 } else {
154 $output->write("$file: #include \"$header\" is not a local include\n");
158 close(IN);
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;
170 my $progress_output;
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) {
178 my %functions;
180 $progress_current++;
181 if($options->progress) {
182 $output->progress("$file: file $progress_current of $progress_max");
185 my $found_function = sub {
186 my $documentation = shift;
187 my $linkage = 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)) {
204 next;
207 my $output_function = sub {
208 my $message = shift;
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;
244 my %comment_width;
245 my %module_pseudo_stub_count16;
246 my %module_pseudo_stub_count32;
248 foreach my $file ($options->c_files) {
249 my %functions = ();
251 my $file_module16 = $modules->allowed_modules_in_file("$current_dir/$file");
252 my $file_module32 = $modules->allowed_modules_in_file("$current_dir/$file");
254 $progress_current++;
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/)) {
261 $file_dir = ".";
264 my $file_type = file_type($file);
266 my $found_function = sub {
267 my $documentation = shift;
268 my $linkage = 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 {
315 my $module = shift;
316 return sub {
317 my $msg = shift;
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)) {
330 my $match = 0;
331 foreach my $module (split(/ & /, $module16)) {
332 foreach my $file_module (split(/ & /, $file_module16)) {
333 if($module eq $file_module) {
334 $match++;
338 if(!$match) {
339 &$output16("is misplaced\n");
343 if($options->win32 && $options->report_module($module32)) {
344 my $match = 0;
345 foreach my $module (split(/ & /, $module32)) {
346 foreach my $file_module (split(/ & /, $file_module32)) {
347 if($module eq $file_module) {
348 $match++;
352 if(!$match) {
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)
427 my $name1;
428 my $name2;
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
440 $name2 = $name1;
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; }
451 $name2 = $name1;
452 $name2 =~ s/AW$//;
453 } else {
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; }
462 $name2 = $name1;
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");
484 my $config = 0;
485 my $conditional = 0;
486 my $found_include = sub {
487 local $_ = shift;
488 if(/^\"config\.h\"/) {
489 $config++;
492 my $found_conditional = sub {
493 local $_ = shift;
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");
502 } else {
503 $conditional++;
504 if(!$config) {
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") {
520 my $header;
521 my $check_protection;
522 my $check_local;
523 if($argument =~ /^<(.*?)>$/) {
524 $header = $1;
525 if($file_type ne "application") {
526 $check_protection = 1;
527 } else {
528 $check_protection = 0;
530 $check_local = 0;
531 } elsif($argument =~ /^"(.*?)"$/) {
532 $header = $1;
533 $check_protection = 0;
534 $check_local = 1;
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");
565 if($check_local) {
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}++;
581 } else {
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;