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