586 lines
17 KiB
Perl
Executable File
586 lines
17 KiB
Perl
Executable File
#!/usr/bin/perl -w
|
|
|
|
# Copyright 1999-2001 Patrik Stridvall
|
|
|
|
# Note that winapi_check are using heuristics quite heavily.
|
|
# So always remember that:
|
|
#
|
|
# "Heuristics are bug ridden by definition.
|
|
# If they didn't have bugs, then they'd be algorithms."
|
|
#
|
|
# In other words, reported bugs are only potential bugs not
|
|
# real bugs, so they are called issues rather than bugs.
|
|
#
|
|
|
|
use strict;
|
|
|
|
BEGIN {
|
|
$0 =~ m%^(.*?/?tools)/winapi_check/winapi_check$%;
|
|
require "$1/winapi/setup.pm";
|
|
}
|
|
|
|
use config qw(
|
|
&file_type &files_filter &files_skip
|
|
&get_h_files
|
|
$current_dir $wine_dir $winapi_dir $winapi_check_dir
|
|
);
|
|
use modules;
|
|
use nativeapi;
|
|
use output;
|
|
use preprocessor;
|
|
use util qw(&is_subset);
|
|
use winapi;
|
|
use winapi_documentation;
|
|
use winapi_function;
|
|
use winapi_local;
|
|
use winapi_global;
|
|
use winapi_options;
|
|
use winapi_parser;
|
|
|
|
my $output = 'output'->new;
|
|
|
|
my $options = winapi_options->new($output, \@ARGV, $wine_dir);
|
|
if(!defined($options)) {
|
|
$output->write("usage: winapi_check [--help] [<files>]\n");
|
|
|
|
exit 1;
|
|
} elsif($options->help) {
|
|
$options->show_help;
|
|
exit;
|
|
}
|
|
|
|
my $modules = 'modules'->new($options, $output, $wine_dir, $current_dir, \&file_type, "$winapi_check_dir/modules.dat");
|
|
|
|
my $win16api = 'winapi'->new($options, $output, "win16", "$winapi_check_dir/win16");
|
|
my $win32api = 'winapi'->new($options, $output, "win32", "$winapi_check_dir/win32");
|
|
my @winapis = ($win16api, $win32api);
|
|
|
|
if($options->global) {
|
|
'winapi'->read_all_spec_files($modules, $wine_dir, $current_dir, \&file_type, $win16api, $win32api);
|
|
} else {
|
|
my @spec_files = $modules->allowed_spec_files($wine_dir, $current_dir);
|
|
'winapi'->read_spec_files($modules, $wine_dir, $current_dir, \@spec_files, $win16api, $win32api);
|
|
}
|
|
|
|
my $nativeapi = 'nativeapi'->new($options, $output, "$winapi_check_dir/nativeapi.dat", "$wine_dir/configure.in", "$wine_dir/include/config.h.in");
|
|
|
|
my %declared_functions;
|
|
|
|
my %include2info;
|
|
{
|
|
my @files = get_h_files("winelib");
|
|
|
|
my $progress_current = 0;
|
|
my $progress_max = scalar(@files);
|
|
|
|
foreach my $file (@files) {
|
|
$progress_current++;
|
|
if($options->progress) {
|
|
$output->lazy_progress("$file: file $progress_current of $progress_max");
|
|
}
|
|
|
|
my $file_dir = $file;
|
|
if(!($file_dir =~ s%(.*?)/[^/]+$%$1%)) {
|
|
$file_dir = ".";
|
|
}
|
|
|
|
$include2info{$file} = { name => $file };
|
|
|
|
open(IN, "< $wine_dir/$file");
|
|
while(<IN>) {
|
|
if(/^\s*\#\s*include\s*\"(.*?)\"/) {
|
|
my $header = $1;
|
|
if(-e "$wine_dir/$file_dir/$header") {
|
|
$include2info{$file}{includes}{"$file_dir/$header"}++;
|
|
} elsif(-e "$wine_dir/$file_dir/../$header") {
|
|
if($file_dir =~ m%^(.*?)/[^/]+$%) {
|
|
$include2info{$file}{includes}{"$1/$header"}++;
|
|
} else {
|
|
$include2info{$file}{includes}{"$header"}++;
|
|
}
|
|
} elsif(-e "$wine_dir/include/$header") {
|
|
$include2info{$file}{includes}{"include/$header"}++;
|
|
} else {
|
|
$output->write("$file: #include \"$header\" is not a local include\n");
|
|
}
|
|
}
|
|
}
|
|
close(IN);
|
|
}
|
|
|
|
my @files2 = ("acconfig.h", "poppack.h", "pshpack1.h", "pshpack2.h", "pshpack4.h", "pshpack8.h",
|
|
"storage.h", "ver.h");
|
|
foreach my $file2 (@files2) {
|
|
$include2info{"include/$file2"}{used}++;
|
|
}
|
|
}
|
|
|
|
my @c_files = $options->c_files;
|
|
@c_files = files_skip(@c_files);
|
|
@c_files = files_filter("winelib", @c_files);
|
|
|
|
my @h_files = $options->h_files;
|
|
@h_files = files_skip(@h_files);
|
|
@h_files = files_filter("winelib", @h_files);
|
|
|
|
my $progress_current = 0;
|
|
my $progress_max = scalar(@c_files);
|
|
|
|
if($options->headers) {
|
|
$progress_max += scalar(@h_files);
|
|
|
|
foreach my $file (@h_files) {
|
|
my %functions;
|
|
|
|
$progress_current++;
|
|
if($options->progress) {
|
|
$output->progress("$file: file $progress_current of $progress_max");
|
|
}
|
|
|
|
my $found_function = sub {
|
|
my $function = shift;
|
|
|
|
$output->prefix($function->prefix);
|
|
|
|
my $function_line = $function->function_line;
|
|
my $internal_name = $function->internal_name;
|
|
my $statements = $function->statements;
|
|
|
|
if($options->headers_misplaced &&
|
|
!($function->is_win16 && $function->is_win32) &&
|
|
(($function->is_win16 && $file =~ /^include\/[^\/]*$/) ||
|
|
($function->is_win32 && $file =~ /^include\/wine\/[^\/]*$/)))
|
|
{
|
|
$output->write("declaration misplaced\n");
|
|
}
|
|
|
|
if(!defined($statements)) {
|
|
my $previous_function = $declared_functions{$internal_name};
|
|
if(!defined($previous_function)) {
|
|
$declared_functions{$internal_name} = $function;
|
|
} elsif($options->headers_duplicated) {
|
|
my $file = $previous_function->file;
|
|
my $function_line = $previous_function->function_line;
|
|
$output->write("duplicate declaration (first declaration at $file:$function_line)\n");
|
|
}
|
|
}
|
|
};
|
|
|
|
my $found_preprocessor = sub {
|
|
my $directive = shift;
|
|
my $argument = shift;
|
|
};
|
|
|
|
winapi_parser::parse_c_file $options, $output, $file, $found_function, $found_preprocessor;
|
|
}
|
|
}
|
|
|
|
my %module2functions = ();
|
|
my %type_found = ();
|
|
|
|
foreach my $file (@c_files) {
|
|
my %functions = ();
|
|
my %includes = ();
|
|
|
|
my $file_module16 = $modules->allowed_modules_in_file("$current_dir/$file");
|
|
my $file_module32 = $modules->allowed_modules_in_file("$current_dir/$file");
|
|
|
|
$progress_current++;
|
|
if($options->progress) {
|
|
$output->progress("$file: file $progress_current of $progress_max");
|
|
}
|
|
|
|
my $file_dir = $file;
|
|
if(!($file_dir =~ s/(.*?)\/[^\/]*$/$1/)) {
|
|
$file_dir = ".";
|
|
}
|
|
|
|
my $found_function = sub {
|
|
my $function = shift;
|
|
|
|
$output->prefix($function->prefix);
|
|
|
|
my $internal_name = $function->internal_name;
|
|
$functions{$internal_name} = $function;
|
|
|
|
my $declared_function = $declared_functions{$internal_name};
|
|
|
|
my $documentation_line = $function->documentation_line;
|
|
my $documentation = $function->documentation;
|
|
my $linkage = $function->linkage;
|
|
my $return_type = $function->return_type;
|
|
my $calling_convention = $function->calling_convention;
|
|
my @argument_types = @{$function->argument_types};
|
|
my @argument_names = @{$function->argument_names};
|
|
my @argument_documentations = @{$function->argument_documentations};
|
|
my $statements = $function->statements;
|
|
|
|
my $module16 = $function->module16;
|
|
my $module32 = $function->module32;
|
|
|
|
my $external_name16 = $function->external_name16;
|
|
my $external_name32 = $function->external_name32;
|
|
|
|
foreach my $module ($function->modules) {
|
|
$module2functions{$module}{$internal_name} = $function;
|
|
for my $type ($return_type, @argument_types) {
|
|
$type_found{$module}{$type}++;
|
|
}
|
|
}
|
|
|
|
foreach my $module ($function->modules) {
|
|
$modules->found_module_in_dir($module, $file_dir);
|
|
}
|
|
|
|
if($options->shared) {
|
|
if($win16api->is_shared_internal_function($internal_name) ||
|
|
$win32api->is_shared_internal_function($internal_name))
|
|
{
|
|
$output->write("is shared between Win16 and Win32\n");
|
|
}
|
|
}
|
|
|
|
if($options->headers_needed && defined($declared_function)) {
|
|
my $needed_include = $declared_function->file;
|
|
|
|
if(!defined($includes{$needed_include})) {
|
|
my $header = $needed_include;
|
|
$header =~ s%^(include|$file_dir)/%%;
|
|
$output->write("prototype not included: #include \"$header\" is needed\n");
|
|
}
|
|
}
|
|
|
|
if(1) {
|
|
# FIXME: Not correct
|
|
if(defined($external_name16)) {
|
|
$external_name16 = (split(/\s*&\s*/, $external_name16))[0];
|
|
}
|
|
|
|
# FIXME: Not correct
|
|
if(defined($external_name32)) {
|
|
$external_name32 = (split(/\s*&\s*/, $external_name32))[0];
|
|
}
|
|
|
|
if($options->local && $options->misplaced &&
|
|
$linkage ne "extern" && defined($statements))
|
|
{
|
|
if($options->win16 && $options->report_module($module16))
|
|
{
|
|
if($file ne "library/port.c" &&
|
|
!$nativeapi->is_function($internal_name) &&
|
|
!is_subset($module16, $file_module16))
|
|
{
|
|
foreach my $module16 (split(/\s*&\s*/, $module16)) {
|
|
if(!$win16api->is_function_stub($module16, $internal_name)) {
|
|
$output->write("is misplaced ($module16)\n");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if($options->win32 && $options->report_module($module32))
|
|
{
|
|
if($file ne "library/port.c" &&
|
|
!$nativeapi->is_function($internal_name) &&
|
|
!is_subset($module32, $file_module32))
|
|
{
|
|
foreach my $module32 (split(/\s*&\s*/, $module32)) {
|
|
if(!$win32api->is_function_stub($module32, $internal_name)) {
|
|
$output->write("is misplaced ($module32)\n");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if($options->local && $options->headers && $options->prototype) {
|
|
if($options->win16 && $options->report_module($module16)) {
|
|
if(!$nativeapi->is_function($internal_name) &&
|
|
!defined($declared_functions{$internal_name}))
|
|
{
|
|
$output->write("no prototype\n");
|
|
}
|
|
}
|
|
|
|
if($options->win32 && $options->report_module($module32)) {
|
|
if(!defined($external_name32) || (!$nativeapi->is_function($external_name32) && !defined($declared_functions{$external_name32})))
|
|
{
|
|
if(!defined($external_name32) || ($external_name32 !~ /^Dll(?:
|
|
Install|CanUnloadNow|GetClassObject|GetVersion|
|
|
RegisterServer|RegisterServerEx|UnregisterServer)|DriverProc$/x &&
|
|
$internal_name !~ /^COMCTL32_Str/ &&
|
|
$internal_name !~ /^(?:\Q$module32\E|wine)_(?:\Q$external_name32\E|\d+)$/))
|
|
{
|
|
$output->write("no prototype\n");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if($options->local && $options->argument) {
|
|
if($options->win16 && $options->report_module($module16)) {
|
|
winapi_local::check_function $options, $output,
|
|
$return_type, $calling_convention, $external_name16, $internal_name, [@argument_types], $nativeapi, $win16api;
|
|
}
|
|
if($options->win32 && $options->report_module($module32)) {
|
|
winapi_local::check_function $options, $output,
|
|
$return_type, $calling_convention, $external_name32, $internal_name, [@argument_types], $nativeapi, $win32api;
|
|
}
|
|
}
|
|
|
|
if($options->local && $options->statements) {
|
|
if($options->win16 && $options->report_module($module16)) {
|
|
winapi_local::check_statements $options, $output, $win16api, \%functions, $function;
|
|
}
|
|
|
|
if($options->win32 && $options->report_module($module32)) {
|
|
winapi_local::check_statements $options, $output, $win32api, \%functions, $function;
|
|
}
|
|
}
|
|
|
|
if($options->local && $options->documentation &&
|
|
(defined($module16) || defined($module32)) &&
|
|
$linkage ne "static" && ($linkage ne "" || defined($statements)))
|
|
{
|
|
winapi_documentation::check_documentation $options, $output, $win16api, $win32api, $modules, $function;
|
|
}
|
|
$output->prefix("");
|
|
}
|
|
};
|
|
|
|
my $config = 0;
|
|
my $conditional = 0;
|
|
my $found_include = sub {
|
|
local $_ = shift;
|
|
if(/^\"config\.h\"/) {
|
|
$config++;
|
|
}
|
|
};
|
|
my $found_conditional = sub {
|
|
local $_ = shift;
|
|
|
|
$nativeapi->found_conditional($_);
|
|
|
|
if($options->config) {
|
|
if(!$nativeapi->is_conditional($_)) {
|
|
if(/^HAVE_/ && !/^HAVE_(IPX|MESAGL|BUGGY_MESAGL|WINE_CONSTRUCTOR)$/)
|
|
{
|
|
$output->write("$file: $_ is not declared as a conditional\n");
|
|
}
|
|
} else {
|
|
$conditional++;
|
|
if(!$config) {
|
|
$output->write("$file: conditional $_ used but config.h is not included\n");
|
|
}
|
|
}
|
|
}
|
|
};
|
|
my $preprocessor = 'preprocessor'->new($found_include, $found_conditional);
|
|
my $found_preprocessor = sub {
|
|
my $directive = shift;
|
|
my $argument = shift;
|
|
|
|
$preprocessor->directive($directive, $argument);
|
|
|
|
if($options->config) {
|
|
if($directive eq "include") {
|
|
my $header;
|
|
my $check_protection;
|
|
my $check_local;
|
|
if($argument =~ /^<(.*?)>$/) {
|
|
$header = $1;
|
|
$check_protection = 1;
|
|
$check_local = 0;
|
|
} elsif($argument =~ /^\"(.*?)\"$/) {
|
|
$header = $1;
|
|
$check_protection = 0;
|
|
$check_local = 1;
|
|
} else {
|
|
$output->write("$file: #$directive $argument: is unparsable\n");
|
|
|
|
$header = undef;
|
|
$check_protection = 0;
|
|
$check_local = 0;
|
|
}
|
|
|
|
if(defined($header)) {
|
|
my $include;
|
|
if(-e "$wine_dir/include/$header") {
|
|
$include = "include/$header";
|
|
} elsif(-e "$file_dir/$header") {
|
|
$include = "$file_dir/$header";
|
|
} elsif(-e "$file_dir/../$header") {
|
|
if($file_dir =~ m%^(.*?)/[^/]+$%) {
|
|
$include = "$1/$header";
|
|
} else {
|
|
$include = "$header";
|
|
}
|
|
} elsif($header eq "controls.h") { # FIXME: Kludge
|
|
$include = "dlls/user/controls.h";
|
|
} elsif($check_local) {
|
|
$output->write("$file: #include \"$header\": file not found\n");
|
|
}
|
|
|
|
if(defined($include)) {
|
|
$includes{$include}++;
|
|
foreach my $include (keys(%{$include2info{$include}{includes}})) {
|
|
$includes{$include}++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if($check_protection && $header) {
|
|
if((-e "$wine_dir/include/$header" || -e "$wine_dir/$file_dir/$header")) {
|
|
if($header !~ /^ctype.h$/) {
|
|
$output->write("$file: #include \<$header\> is a local include\n");
|
|
}
|
|
}
|
|
|
|
my $macro = uc($header);
|
|
$macro =~ y/\.\//__/;
|
|
$macro = "HAVE_" . $macro;
|
|
|
|
if($nativeapi->is_conditional_header($header)) {
|
|
if(!$preprocessor->is_def($macro)) {
|
|
if($macro =~ /^HAVE_X11/) {
|
|
# Do nothing X Windows is handled differently
|
|
} elsif($macro =~ /^HAVE_(.*?)_H$/) {
|
|
if($header ne "alloca.h" && !$preprocessor->is_def("STATFS_DEFINED_BY_$1")) {
|
|
$output->write("$file: #$directive $argument: is a conditional include, " .
|
|
"but is not protected\n");
|
|
}
|
|
}
|
|
}
|
|
} elsif($preprocessor->is_def($macro)) {
|
|
$output->write("$file: #$directive $argument: is protected, " .
|
|
"but is not a conditional include\n");
|
|
}
|
|
}
|
|
|
|
if($check_local && $header) {
|
|
if(-e "$file_dir/$header") {
|
|
if($file_dir ne ".") {
|
|
$include2info{"$file_dir/$header"}{used}++;
|
|
foreach my $name (keys(%{$include2info{"$file_dir/$header"}{includes}})) {
|
|
$include2info{$name}{used}++;
|
|
}
|
|
} else {
|
|
$include2info{"$header"}{used}++;
|
|
foreach my $name (keys(%{$include2info{"$header"}{includes}})) {
|
|
$include2info{$name}{used}++;
|
|
}
|
|
}
|
|
} elsif(-e "$wine_dir/$file_dir/../$header") {
|
|
if($file_dir =~ m%^(.*?)/[^/]+$%) {
|
|
$include2info{"$1/$header"}{used}++;
|
|
foreach my $name (keys(%{$include2info{"$1/$header"}{includes}})) {
|
|
$include2info{$name}{used}++;
|
|
}
|
|
} else {
|
|
$include2info{"$header"}{used}++;
|
|
foreach my $name (keys(%{$include2info{"$header"}{includes}})) {
|
|
$include2info{$name}{used}++;
|
|
}
|
|
}
|
|
} elsif($header eq "controls.h") { # FIXME: Kludge
|
|
$include2info{"dlls/user/$header"}{used}++;
|
|
foreach my $name (keys(%{$include2info{"dlls/user/$header"}{includes}})) {
|
|
$include2info{$name}{used}++;
|
|
}
|
|
} elsif(-e "$wine_dir/include/$header") {
|
|
$include2info{"include/$header"}{used}++;
|
|
foreach my $name (keys(%{$include2info{"include/$header"}{includes}})) {
|
|
$include2info{$name}{used}++;
|
|
}
|
|
} else {
|
|
$output->write("$file: #include \"$header\" is not a local include\n");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
winapi_parser::parse_c_file $options, $output, $file, $found_function, $found_preprocessor;
|
|
|
|
if($options->config_unnessary) {
|
|
if($config && $conditional == 0) {
|
|
$output->write("$file: include2info config.h but do not use any conditionals\n");
|
|
}
|
|
}
|
|
|
|
winapi_local::check_file $options, $output, $file, \%functions;
|
|
}
|
|
|
|
$output->hide_progress;
|
|
|
|
if($options->declared) {
|
|
my %dirs;
|
|
foreach my $file (@c_files) {
|
|
my $dir = $file;
|
|
$dir =~ s%/?[^/]*$%%;
|
|
if($dir) {
|
|
if($current_dir ne ".") {
|
|
$dir = "$current_dir/$dir";
|
|
}
|
|
} else {
|
|
$dir = "$current_dir";
|
|
}
|
|
$dirs{$dir}++;
|
|
}
|
|
|
|
foreach my $module ($modules->all_modules) {
|
|
my $incomplete = 0;
|
|
foreach my $module_dir ($modules->allowed_dirs_for_module($module)) {
|
|
my $found = 0;
|
|
foreach my $dir (sort(keys(%dirs))) {
|
|
if($module_dir eq $dir) {
|
|
$found = 1;
|
|
last;
|
|
}
|
|
}
|
|
if(!$found) {
|
|
$incomplete = 1;
|
|
}
|
|
}
|
|
if(!$incomplete) {
|
|
if($options->declared) {
|
|
foreach my $winapi (@winapis) {
|
|
if(!$winapi->is_module($module)) { next; }
|
|
my $functions = $module2functions{$module};
|
|
foreach my $internal_name ($winapi->all_internal_functions_in_module($module)) {
|
|
my $function = $functions->{$internal_name};
|
|
if(!defined($function) && !$nativeapi->is_function($internal_name) &&
|
|
!($module eq "user" && $internal_name =~
|
|
/^(?:GlobalAddAtomA|GlobalDeleteAtom|GlobalFindAtomA|
|
|
GlobalGetAtomNameA|lstrcmpiA)$/x))
|
|
{
|
|
$output->write("*.c: $module: $internal_name: " .
|
|
"function declared but not implemented or declared external\n");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if($options->global) {
|
|
winapi_documentation::report_documentation $options, $output;
|
|
|
|
if($options->headers_unused) {
|
|
foreach my $name (sort(keys(%include2info))) {
|
|
if(!$include2info{$name}{used}) {
|
|
if($options->include) {
|
|
$output->write("*.c: $name: include file is never used\n");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
winapi_global::check $options, $output, $win16api, $nativeapi, \%type_found if $options->win16;
|
|
winapi_global::check $options, $output, $win32api, $nativeapi, \%type_found if $options->win32;
|
|
|
|
$modules->global_report;
|
|
$nativeapi->global_report;
|
|
}
|