Add function prototypes.
[wine] / tools / winapi_check / winapi_check
1 #!/usr/bin/perl -w
2
3 # Copyright 1999-2002 Patrik Stridvall
4 #
5 # This library is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU Lesser General Public
7 # License as published by the Free Software Foundation; either
8 # version 2.1 of the License, or (at your option) any later version.
9 #
10 # This library is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 # Lesser General Public License for more details.
14 #
15 # You should have received a copy of the GNU Lesser General Public
16 # License along with this library; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18 #
19
20 # Note that winapi_check are using heuristics quite heavily.
21 # So always remember that:
22 #
23 # "Heuristics are bug ridden by definition.
24 #  If they didn't have bugs, then they'd be algorithms."
25 #
26 # In other words, reported bugs are only potential bugs not
27 # real bugs, so they are called issues rather than bugs.
28 #
29
30 use strict;
31
32 BEGIN {
33     $0 =~ m%^(.*?/?tools)/winapi_check/winapi_check$%;
34     require "$1/winapi/setup.pm";
35 }
36
37 use config qw(
38     files_filter files_skip
39     get_h_files
40     $current_dir $wine_dir
41 );
42 use output qw($output);
43 use winapi_check_options qw($options);
44
45 BEGIN {
46     if($options->progress) {
47         $output->enable_progress;
48     } else {
49         $output->disable_progress;
50     }
51 }
52
53 use modules qw($modules);
54 use nativeapi qw($nativeapi);
55 use winapi qw($win16api $win32api @winapis);
56
57 use preprocessor;
58 use type;
59 use util qw(is_subset);
60 use winapi_documentation;
61 use winapi_function;
62 use winapi_local;
63 use winapi_global;
64 use winapi_parser;
65
66 my %include2info;
67
68 if ($options->global) {
69     my @files = get_h_files("winelib");
70
71     my $progress_current = 0;
72     my $progress_max = scalar(@files);
73
74     foreach my $file (@files) {
75         $progress_current++;
76         $output->lazy_progress("$file: file $progress_current of $progress_max");
77
78         my $file_dir = $file;
79         if(!($file_dir =~ s%(.*?)/[^/]+$%$1%)) {
80             $file_dir = ".";
81         }
82
83         $include2info{$file} = { name => $file };
84
85         open(IN, "< $wine_dir/$file");
86         while(<IN>) {
87             if(/^\s*\#\s*include\s*\"(.*?)\"/) {
88                 my $header = $1;
89                 if(-e "$wine_dir/$file_dir/$header") {
90                     $include2info{$file}{includes}{"$file_dir/$header"}++;
91                 } elsif(-e "$wine_dir/$file_dir/../$header") {
92                     if($file_dir =~ m%^(.*?)/[^/]+$%) {
93                         $include2info{$file}{includes}{"$1/$header"}++;
94                     } else {
95                         $include2info{$file}{includes}{"$header"}++;
96                     }
97                 } elsif(-e "$wine_dir/include/$header") {
98                     $include2info{$file}{includes}{"include/$header"}++;
99                 } elsif ($header ne "config.h") {
100                     $output->write("$file: #include \"$header\" is not a local include\n");
101                 }
102             }
103         }
104         close(IN);
105     }
106
107     my @files2 = ("acconfig.h", "poppack.h", "pshpack1.h", "pshpack2.h", "pshpack4.h", "pshpack8.h",
108                   "storage.h", "ver.h");
109     foreach my $file2 (@files2) {
110         $include2info{"include/$file2"}{used}++;
111     }
112 }
113
114 my @c_files = $options->c_files;
115 @c_files = files_skip(@c_files);
116 @c_files = files_filter("winelib", @c_files);
117
118 my @h_files = $options->h_files;
119 @h_files = files_skip(@h_files);
120 @h_files = files_filter("winelib", @h_files);
121
122 my $all_modules = 0;
123 my %complete_module;
124 if($options->global) {
125     my @complete_modules = $modules->complete_modules(\@c_files);
126
127     foreach my $module (@complete_modules) {
128         $complete_module{$module}++;
129     }
130
131     my $all_modules = 1;
132     foreach my $module ($modules->all_modules) {
133         if(!$complete_module{$module}) {
134             $all_modules = 0;
135             if($wine_dir eq ".") {
136                 $output->write("*.c: module $module is not complete\n");
137             }
138         }
139     }
140 }
141
142 if(1) {
143     foreach my $winapi (@winapis) {
144         foreach my $broken_forward ($winapi->all_broken_forwards) {
145             (my $module, my $external_name, my $forward_module, my $forward_external_name) = @$broken_forward;
146             if($complete_module{$forward_module}) {
147                 $output->write("$module.spec: forward is broken: $external_name => $forward_module.$forward_external_name\n");
148             }
149         }
150     }
151 }
152
153 my $progress_current = 0;
154 my $progress_max = scalar(@c_files);
155
156 my %declared_functions;
157
158 if($options->headers) {
159     $progress_max += scalar(@h_files);
160
161     foreach my $file (@h_files) {
162         my %functions;
163
164         $progress_current++;
165         $output->progress("$file: file $progress_current of $progress_max");
166
167         my $found_c_comment = sub {
168             my $begin_line = shift;
169             my $end_line = shift;
170             my $comment = shift;
171
172             if(0) {
173                 if($begin_line == $end_line) {
174                     $output->write("$file:$begin_line: $comment\n");
175                 } else {
176                     $output->write("$file:$begin_line-$end_line: \\\n$comment\n");
177                 }
178             }
179         };
180
181         my $found_cplusplus_comment = sub {
182             my $line = shift;
183             my $comment = shift;
184
185             if($options->comments_cplusplus) {
186                 $output->write("$file:$line: C++ comments not allowed: $comment\n");
187             }
188         };
189
190         my $create_function = sub {
191             return 'winapi_function'->new;
192         };
193
194         my $found_function = sub {
195             my $function = shift;
196
197             my $internal_name = $function->internal_name;
198
199             $output->progress("$file (file $progress_current of $progress_max): $internal_name");
200             $output->prefix_callback(sub { return $function->prefix; });
201
202             my $function_line = $function->function_line;
203             my $linkage = $function->linkage;
204             my $external_name = $function->external_name;
205             my $statements = $function->statements;
206
207             if($options->headers_misplaced &&
208                !($function->is_win16 && $function->is_win32) &&
209                (($function->is_win16 && $file =~ /^include\/[^\/]*$/) ||
210                 ($function->is_win32 && $file =~ /^include\/wine\/[^\/]*$/)))
211             {
212                 $output->write("declaration misplaced\n");
213             }
214
215             if(defined($external_name) && !defined($statements) &&
216                ($linkage eq "" || $linkage eq "extern"))
217             {
218                 my $previous_function = $declared_functions{$internal_name};
219                 if(!defined($previous_function)) {
220                     $declared_functions{$internal_name} = $function;
221                 } elsif($options->headers_duplicated) {
222                     my $file = $previous_function->file;
223                     my $function_line = $previous_function->function_line;
224                     if($file =~ /\.h$/) {
225                         $output->write("duplicate declaration (first declaration at $file:$function_line)\n");
226                     }
227                 }
228             }
229             $output->prefix("");
230         };
231
232         my $create_type = sub {
233             return 'type'->new;
234         };
235
236         my $found_type = sub {
237             my $type = shift;
238         };
239
240         my $found_preprocessor = sub {
241             my $directive = shift;
242             my $argument = shift;
243         };
244
245         winapi_parser::parse_c_file($file, {
246             c_comment_found => $found_c_comment,
247             cplusplus_comment_found => $found_cplusplus_comment,
248             function_create => $create_function,
249             function_found => $found_function,
250             type_create => $create_type,
251             type_found => $found_type,
252             preprocessor_found => $found_preprocessor
253         });
254     }
255 }
256
257 my %module2functions = ();
258
259 foreach my $file (@c_files) {
260     my %functions = ();
261     my %includes = ();
262
263     $includes{$file}++;
264
265     my $file_module16 = $modules->allowed_modules_in_file("$current_dir/$file");
266     my $file_module32 = $modules->allowed_modules_in_file("$current_dir/$file");
267
268     $progress_current++;
269     $output->progress("$file (file $progress_current of $progress_max)");
270
271     my $file_dir = $file;
272     if(!($file_dir =~ s/(.*?)\/[^\/]*$/$1/)) {
273         $file_dir = ".";
274     }
275
276     my $found_c_comment = sub {
277         my $begin_line = shift;
278         my $end_line = shift;
279         my $comment = shift;
280
281         if(0) {
282             if($begin_line == $end_line) {
283                 $output->write("$file:$begin_line: $comment\n");
284             } else {
285                 $output->write("$file:$begin_line-$end_line: \\\n$comment\n");
286             }
287         }
288     };
289
290     my $found_cplusplus_comment = sub {
291         my $line = shift;
292         my $comment = shift;
293
294         if($options->comments_cplusplus) {
295             $output->write("$file:$line: C++ comments not allowed: $comment\n");
296         }
297     };
298
299     my $create_function = sub {
300         return 'winapi_function'->new;
301     };
302
303     my $found_function = sub {
304         my $function = shift;
305
306         my $internal_name = $function->internal_name;
307         $functions{$internal_name} = $function;
308
309         $output->progress("$file (file $progress_current of $progress_max): $internal_name");
310         $output->prefix_callback(sub { return $function->prefix; });
311
312         my $declared_function = $declared_functions{$internal_name};
313
314         my $documentation_line = $function->documentation_line;
315         my $documentation = $function->documentation;
316         my $linkage = $function->linkage;
317         my $return_type = $function->return_type;
318         my $calling_convention = $function->calling_convention;
319         my $statements = $function->statements;
320
321         my $module16 = $function->module16;
322         my $module32 = $function->module32;
323
324         my $external_name = $function->external_name;
325         my $external_name16 = $function->external_name16;
326         my $external_name32 = $function->external_name32;
327
328         if(defined($external_name) && !defined($statements) &&
329            ($linkage eq "" || $linkage eq "extern"))
330         {
331             my $previous_function = $declared_functions{$internal_name};
332             if(!defined($previous_function)) {
333                 $declared_functions{$internal_name} = $function;
334             } else {
335                 my $file = $previous_function->file;
336                 my $function_line = $previous_function->function_line;
337
338                 my $header = $file;
339                 $header =~ s%^(include|$file_dir)/%%;
340                 if($header !~ m%^msvcrt/% || $file_dir =~ m%^dlls/msvcrt%) {
341                     $output->write("duplicate declaration (first declaration at $file:$function_line)\n");
342                 }
343             }
344         }
345
346         if ($options->global) {
347             foreach my $module ($function->modules) {
348                 $module2functions{$module}{$internal_name} = $function;
349             }
350         }
351
352         foreach my $module ($function->modules) {
353             $modules->found_module_in_dir($module, $file_dir);
354         }
355
356         if($options->shared) {
357             if($win16api->is_shared_internal_function($internal_name) ||
358                $win32api->is_shared_internal_function($internal_name))
359             {
360                 $output->write("is shared between Win16 and Win32\n");
361             }
362         }
363
364         if($options->headers && $options->headers_needed &&
365            defined($declared_function) && defined($external_name) &&
366            defined($statements))
367         {
368             my $needed_include = $declared_function->file;
369
370             if(!defined($includes{$needed_include})) {
371                 my $header = $needed_include;
372                 $header =~ s%^(include|$file_dir)/%%;
373                 if($header !~ m%^msvcrt/% || $file_dir =~ m%^dlls/msvcrt%) {
374                     $output->write("prototype not included: #include \"$header\" is needed\n");
375                 }
376             }
377         }
378
379         if($options->local && $options->argument && defined($statements)) {
380             winapi_local::check_function($function);
381         }
382
383         if($options->local && $options->statements && defined($statements)) {
384             winapi_local::check_statements(\%functions, $function);
385         }
386
387         if($options->local && $options->documentation &&
388            (defined($module16) || defined($module32)) &&
389            $linkage eq "" && defined($statements))
390         {
391             winapi_documentation::check_documentation($function);
392         }
393
394         if(1) {
395             # FIXME: Not correct
396             if(defined($external_name16)) {
397                 $external_name16 = (split(/\s*&\s*/, $external_name16))[0];
398             }
399
400             # FIXME: Not correct
401             if(defined($external_name32)) {
402                 $external_name32 = (split(/\s*&\s*/, $external_name32))[0];
403             }
404
405             if($options->local && $options->misplaced &&
406                $linkage ne "extern" && defined($statements))
407             {
408                 if($options->win16 && $options->report_module($module16))
409                 {
410                     if($file ne "library/port.c" &&
411                        !$nativeapi->is_function($internal_name) &&
412                        !is_subset($module16, $file_module16))
413                     {
414                         foreach my $module16 (split(/\s*&\s*/, $module16)) {
415                             if(!$win16api->is_function_stub($module16, $internal_name)) {
416                                 $output->write("is misplaced ($module16)\n");
417                             }
418                         }
419                     }
420                 }
421
422                 if($options->win32 && $options->report_module($module32))
423                 {
424                     if($file ne "library/port.c" &&
425                        !$nativeapi->is_function($internal_name) &&
426                        !is_subset($module32, $file_module32))
427                     {
428                         foreach my $module32 (split(/\s*&\s*/, $module32)) {
429                             if(!$win32api->is_function_stub($module32, $internal_name)) {
430                                 $output->write("is misplaced ($module32)\n");
431                             }
432                         }
433                     }
434                 }
435             }
436
437             if($options->local && $options->headers && $options->prototype) {
438                 if($options->win16 && $options->report_module($module16)) {
439                     if(!$nativeapi->is_function($internal_name) &&
440                        !defined($declared_functions{$internal_name}))
441                     {
442                         $output->write("no prototype\n");
443                     }
444                 }
445
446                 if($options->win32 && $options->report_module($module32)) {
447                     if(!defined($external_name32) || (!$nativeapi->is_function($external_name32) &&                                                       !defined($declared_functions{$external_name32})))
448                     {
449                         if(!defined($external_name32) || ($external_name32 !~ /^Dll(?:
450                            Install|CanUnloadNow|GetClassObject|GetVersion|
451                            RegisterServer|RegisterServerEx|UnregisterServer)|DriverProc$/x &&
452                            $internal_name !~ /^COMCTL32_Str/ &&
453                            $internal_name !~ /^(?:\Q$module32\E|wine)_(?:\Q$external_name32\E|\d+)$/))
454                         {
455                             $output->write("no prototype\n");
456                         }
457                     }
458                 }
459             }
460         }
461
462         $output->prefix("");
463     };
464
465     my $config = 0;
466     my $conditional = 0;
467     my $found_include = sub {
468         local $_ = shift;
469         if(/^\"(?:config\.h|wine\/port\.h)\"/) {
470             $config++;
471         }
472     };
473     my $found_conditional = sub {
474         local $_ = shift;
475
476         $nativeapi->found_conditional($_);
477
478         if($options->config) {
479             if(!$nativeapi->is_conditional($_)) {
480                 if(/^HAVE_/ && !/^HAVE_(IPX|MESAGL|BUGGY_MESAGL|WINE_CONSTRUCTOR)$/)
481                 {
482                     $output->write("$file: $_ is not declared as a conditional\n");
483                 }
484             } else {
485                 $conditional++;
486                 if(!$config) {
487                     $output->write("$file: conditional $_ used but config.h is not included\n");
488                 }
489             }
490         }
491     };
492
493     my $create_type = sub {
494         return 'type'->new;
495     };
496
497     my $found_type = sub {
498         my $type = shift;
499     };
500
501     my $preprocessor = 'preprocessor'->new($found_include, $found_conditional);
502     my $found_preprocessor = sub {
503         my $directive = shift;
504         my $argument = shift;
505
506         $preprocessor->directive($directive, $argument);
507
508         if($options->config) {
509             if($directive eq "include") {
510                 my $header;
511                 my $check_protection;
512                 my $check_local;
513                 if($argument =~ /^<(.*?)>$/) {
514                     $header = $1;
515                     $check_protection = 1;
516                     $check_local = 0;
517                 } elsif($argument =~ /^\"(.*?)\"$/) {
518                     $header = $1;
519                     $check_protection = 0;
520                     $check_local = 1;
521                 } else {
522                     $output->write("$file: #$directive $argument: is unparsable\n");
523
524                     $header = undef;
525                     $check_protection = 0;
526                     $check_local = 0;
527                 }
528
529                 if(defined($header)) {
530                     my $include;
531                     if(-e "$wine_dir/include/$header") {
532                         $include = "include/$header";
533                     } elsif(-e "$file_dir/$header") {
534                         $include = "$file_dir/$header";
535                     } elsif(-e "$file_dir/../$header") {
536                         if($file_dir =~ m%^(.*?)/[^/]+$%) {
537                             $include = "$1/$header";
538                         } else {
539                             $include = "$header";
540                         }
541                     } elsif($header =~ /^(?:kernel_private\.h)$/) { # FIXME: Kludge
542                         $include = "dlls/kernel/$header";
543                     } elsif($header =~ /^(?:gdi_private\.h)$/) { # FIXME: Kludge
544                         $include = "dlls/gdi/$header";
545                     } elsif($header =~ /^(?:ntdll_misc\.h)$/) { # FIXME: Kludge
546                         $include = "dlls/ntdll/$header";
547                     } elsif($header =~ /^(?:controls\.h|message\.h)$/) { # FIXME: Kludge
548                         $include = "dlls/user/$header";
549                     } elsif($header =~ /^(?:ts_xlib\.h|winproc\.h|x11drv\.h|x11font\.h)$/) { # FIXME: Kludge
550                         $include = "dlls/x11drv/$header";
551                     } elsif($check_local && $header ne "config.h") {
552                         $output->write("$file: #include \"$header\": file not found\n");
553                     }
554
555                     if(defined($include)) {
556                         $includes{$include}++;
557                         foreach my $include (keys(%{$include2info{$include}{includes}})) {
558                             $includes{$include}++;
559                         }
560                     }
561                 }
562
563                 if($check_protection && $header) {
564                     if((-e "$wine_dir/include/$header" || -e "$wine_dir/$file_dir/$header")) {
565                         if($header !~ /^(oleauto\.h|win(?:base|def|error|gdi|nls|nt|user)\.h)$/ &&
566                            $file_dir !~ /tests$/)
567                         {
568                             $output->write("$file: #include \<$header\> is a local include\n");
569                         }
570                     }
571
572                     my $macro = uc($header);
573                     $macro =~ y/\.\//__/;
574                     $macro = "HAVE_" . $macro;
575
576                     if($nativeapi->is_conditional_header($header)) {
577                         if(!$preprocessor->is_def($macro)) {
578                             if($macro =~ /^HAVE_X11/) {
579                                 # Do nothing X Windows is handled differently
580                             } elsif($macro =~ /^HAVE_(.*?)_H$/) {
581                                 my $name = $1;
582                                 if($header !~ /^alloca\.h$/ &&
583                                    $file_dir !~ /tests$/ &&
584                                    !$preprocessor->is_def("STATFS_DEFINED_BY_$name"))
585                                 {
586                                     $output->write("$file: #$directive $argument: is a conditional include, " .
587                                                    "but is not protected\n");
588                                 }
589                             }
590                         }
591                     } elsif($preprocessor->is_def($macro)) {
592                         $output->write("$file: #$directive $argument: is protected, " .
593                                        "but is not a conditional include\n");
594                     }
595                 }
596
597                 if($check_local && $header) {
598                     if(-e "$file_dir/$header") {
599                         if($file_dir ne ".") {
600                             $include2info{"$file_dir/$header"}{used}++;
601                             foreach my $name (keys(%{$include2info{"$file_dir/$header"}{includes}})) {
602                                 $include2info{$name}{used}++;
603                             }
604                         } else {
605                             $include2info{"$header"}{used}++;
606                             foreach my $name (keys(%{$include2info{"$header"}{includes}})) {
607                                 $include2info{$name}{used}++;
608                             }
609                         }
610                     } elsif(-e "$file_dir/../$header") {
611                         if($file_dir =~ m%^(.*?)/[^/]+$%) {
612                             $include2info{"$1/$header"}{used}++;
613                             foreach my $name (keys(%{$include2info{"$1/$header"}{includes}})) {
614                                 $include2info{$name}{used}++;
615                             }
616                         } else {
617                             $include2info{"$header"}{used}++;
618                             foreach my $name (keys(%{$include2info{"$header"}{includes}})) {
619                                 $include2info{$name}{used}++;
620                             }
621                         }
622                     } elsif($header =~ /^(?:kernel_private\.h)$/) { # FIXME: Kludge
623                         $include2info{"dlls/kernel/$header"}{used}++;
624                         foreach my $name (keys(%{$include2info{"dlls/kernel/$header"}{includes}})) {
625                             $include2info{$name}{used}++;
626                         }
627                     } elsif($header =~ /^(?:gdi_private\.h)$/) { # FIXME: Kludge
628                         $include2info{"dlls/gdi/$header"}{used}++;
629                         foreach my $name (keys(%{$include2info{"dlls/gdi/$header"}{includes}})) {
630                             $include2info{$name}{used}++;
631                         }
632                     } elsif($header =~ /^(?:ntdll_misc\.h)$/) { # FIXME: Kludge
633                         $include2info{"dlls/ntdll/$header"}{used}++;
634                         foreach my $name (keys(%{$include2info{"dlls/ntdll/$header"}{includes}})) {
635                             $include2info{$name}{used}++;
636                         }
637                     } elsif($header =~ /^(?:controls\.h|message\.h)$/) { # FIXME: Kludge
638                         $include2info{"dlls/user/$header"}{used}++;
639                         foreach my $name (keys(%{$include2info{"dlls/user/$header"}{includes}})) {
640                             $include2info{$name}{used}++;
641                         }
642                     } elsif($header =~ /^(?:ts_xlib\.h|winproc\.h|x11drv\.h|x11font\.h)$/) { # FIXME: Kludge
643                         $include2info{"dlls/x11drv/$header"}{used}++;
644                         foreach my $name (keys(%{$include2info{"dlls/x11drv/$header"}{includes}})) {
645                             $include2info{$name}{used}++;
646                         }
647                     } elsif(-e "$wine_dir/include/$header") {
648                         $include2info{"include/$header"}{used}++;
649                         foreach my $name (keys(%{$include2info{"include/$header"}{includes}})) {
650                             $include2info{$name}{used}++;
651                         }
652                     } elsif ($header ne "config.h") {
653                         $output->write("$file: #include \"$header\" is not a local include\n");
654                     }
655                 }
656             }
657         }
658     };
659
660     winapi_parser::parse_c_file($file, {
661         c_comment_found => $found_c_comment,
662         cplusplus_comment_found => $found_cplusplus_comment,
663         function_create => $create_function,
664         function_found => $found_function,
665         type_create => $create_type,
666         type_found => $found_type,
667         preprocessor_found => $found_preprocessor
668     });
669
670     if($options->config_unnecessary) {
671         if($config && $conditional == 0) {
672             $output->write("$file: include2info config.h but do not use any conditionals\n");
673         }
674     }
675
676     winapi_local::check_file($file, \%functions);
677 }
678
679 if($options->global) {
680     winapi_global::check_modules(\%complete_module, \%module2functions);
681
682     if($all_modules) {
683         winapi_global::check_all_modules(\%include2info);
684     }
685 }