| File: | lib/Parrot/Headerizer/Object.pm |
| Coverage: | 96.6% |
| line | stmt | bran | cond | sub | code |
|---|---|---|---|---|---|
| 1 | # Copyright (C) 2004-2010, Parrot Foundation. | ||||
| 2 | # $Id$ | ||||
| 3 | |||||
| 4 | package Parrot::Headerizer::Object; | ||||
| 5 | |||||
| 6 - 23 | =head1 NAME
Parrot::Headerizer::Object - Parrot Header Generation functionality
=head1 SYNOPSIS
use Parrot::Headerizer::Object;
my $headerizer = Parrot::Headerizer::Object->new();
=head1 DESCRIPTION
C<Parrot::Headerizer::Object> knows how to extract all kinds of information out
of C-language files.
=head1 METHODS
=cut | ||||
| 24 | |||||
| 25 | 1 1 1 | use strict; | |||
| 26 | 1 1 1 | use warnings; | |||
| 27 | 1 1 1 | use Data::Dumper;$Data::Dumper::Indent=1; | |||
| 28 | 1 1 1 | use Scalar::Util qw( reftype ); | |||
| 29 | 1 1 1 | use lib qw( lib ); | |||
| 30 | 1 1 1 | use Parrot::Config; | |||
| 31 | 1 | use Parrot::Headerizer::Functions qw( | |||
| 32 | read_file | ||||
| 33 | write_file | ||||
| 34 | qualify_sourcefile | ||||
| 35 | replace_pod_item | ||||
| 36 | no_both_PARROT_EXPORT_and_PARROT_INLINE | ||||
| 37 | validate_prototype_args | ||||
| 38 | no_both_static_and_PARROT_EXPORT | ||||
| 39 | handle_split_declaration | ||||
| 40 | asserts_from_args | ||||
| 41 | shim_test | ||||
| 42 | handle_modified_args | ||||
| 43 | add_newline_if_multiline | ||||
| 44 | add_asserts_to_declarations | ||||
| 45 | func_modifies | ||||
| 46 | add_headerizer_markers | ||||
| 47 | 1 1 | ); | |||
| 48 | |||||
| 49 - 53 | =head2 C<new()> Constructor of headerizer objects. =cut | ||||
| 54 | |||||
| 55 | sub new { | ||||
| 56 | 13 | my ($class, $args) = @_; | |||
| 57 | 13 | if (defined $args) { | |||
| 58 | 3 | die "Argument to Parrot::Headerizer::Object must be hashref" | |||
| 59 | unless reftype($args) eq 'HASH'; | ||||
| 60 | } | ||||
| 61 | else { | ||||
| 62 | 10 | $args = {}; | |||
| 63 | } | ||||
| 64 | 12 | $args->{macro_match} = undef unless defined $args->{macro_match}; | |||
| 65 | 12 | $args->{warnings} = {}; | |||
| 66 | 12 | $args->{message} = ''; | |||
| 67 | 12 180 | $args->{valid_macros} = { map { ( $_, 1 ) } qw( | |||
| 68 | PARROT_EXPORT | ||||
| 69 | PARROT_INLINE | ||||
| 70 | PARROT_NOINLINE | ||||
| 71 | |||||
| 72 | PARROT_CAN_RETURN_NULL | ||||
| 73 | PARROT_CANNOT_RETURN_NULL | ||||
| 74 | |||||
| 75 | PARROT_IGNORABLE_RESULT | ||||
| 76 | PARROT_WARN_UNUSED_RESULT | ||||
| 77 | |||||
| 78 | PARROT_PURE_FUNCTION | ||||
| 79 | PARROT_CONST_FUNCTION | ||||
| 80 | |||||
| 81 | PARROT_DOES_NOT_RETURN | ||||
| 82 | PARROT_DOES_NOT_RETURN_WHEN_FALSE | ||||
| 83 | |||||
| 84 | PARROT_MALLOC | ||||
| 85 | PARROT_OBSERVER | ||||
| 86 | |||||
| 87 | PARROT_HOT | ||||
| 88 | PARROT_COLD | ||||
| 89 | ) | ||||
| 90 | }; | ||||
| 91 | 12 | return bless $args, $class; | |||
| 92 | } | ||||
| 93 | |||||
| 94 | sub get_sources { | ||||
| 95 | 9 | my $self = shift; | |||
| 96 | 9 | my @ofiles = @_; | |||
| 97 | 9 | my %sourcefiles; | |||
| 98 | 9 | my %sourcefiles_with_statics; | |||
| 99 | 9 | my %api; | |||
| 100 | # Walk the object files and find corresponding source (either .c or .pmc) | ||||
| 101 | 9 | for my $ofile (@ofiles) { | |||
| 102 | |||||
| 103 | # Skip files in the src/ops/ subdirectory. | ||||
| 104 | 9 | next if $ofile =~ m/^\Qsrc$PConfig{slash}ops\E/ || # if run by hand... | |||
| 105 | $ofile =~ m{^src/ops}; # ... or by makefile | ||||
| 106 | |||||
| 107 | 8 | $ofile =~ s/\\/\//g; | |||
| 108 | |||||
| 109 | 8 | my $is_yacc = ($ofile =~ /\.y$/); | |||
| 110 | 8 | if ( !$is_yacc ) { | |||
| 111 | 8 | my $sfile = $ofile; | |||
| 112 | 8 | $sfile =~ s/\Q$PConfig{o}\E$/.s/; | |||
| 113 | 8 | next if -f $sfile; | |||
| 114 | } | ||||
| 115 | |||||
| 116 | 7 | my ($sourcefile, $source_code, $hfile) = | |||
| 117 | qualify_sourcefile( { | ||||
| 118 | ofile => $ofile, | ||||
| 119 | PConfig => \%PConfig, | ||||
| 120 | is_yacc => $is_yacc, | ||||
| 121 | } ); | ||||
| 122 | |||||
| 123 | 7 | my @decls; | |||
| 124 | 7 | if ( $self->{macro_match} ) { | |||
| 125 | 1 | @decls = $self->extract_function_declarations( $source_code ); | |||
| 126 | } | ||||
| 127 | else { | ||||
| 128 | 6 | @decls = | |||
| 129 | $self->extract_function_declarations_and_update_source( $sourcefile ); | ||||
| 130 | } | ||||
| 131 | |||||
| 132 | 7 | for my $decl (@decls) { | |||
| 133 | 38 | my $components = | |||
| 134 | $self->function_components_from_declaration( $sourcefile, $decl ); | ||||
| 135 | 38 35 | push( @{ $sourcefiles{$hfile}->{$sourcefile} }, $components ) | |||
| 136 | unless $hfile eq 'none'; | ||||
| 137 | 38 3 | push( @{ $sourcefiles_with_statics{$sourcefile} }, $components ) | |||
| 138 | if $components->{is_static}; | ||||
| 139 | 38 | if ( $self->{macro_match} ) { | |||
| 140 | 7 10 7 | if ( grep { $_ eq $self->{macro_match} } @{$components->{macros}} ) { | |||
| 141 | 2 2 | push( @{ $api{$sourcefile} }, $components ); | |||
| 142 | } | ||||
| 143 | } | ||||
| 144 | } | ||||
| 145 | } # for @cfiles | ||||
| 146 | 9 | $self->{sourcefiles} = \%sourcefiles; | |||
| 147 | 9 | $self->{sourcefiles_with_statics} = \%sourcefiles_with_statics; | |||
| 148 | 9 | $self->{api} = \%api; | |||
| 149 | } | ||||
| 150 | |||||
| 151 - 158 | =head2 C<extract_function_declarations()>
$headerizer->extract_function_declarations($text)
Extracts the function declarations from the text argument, and returns an
array of strings containing the function declarations.
=cut | ||||
| 159 | |||||
| 160 | sub extract_function_declarations { | ||||
| 161 | 7 | my $self = shift; | |||
| 162 | 7 | my $text = shift; | |||
| 163 | |||||
| 164 | # Only check the YACC C code if we find what looks like YACC file | ||||
| 165 | 7 | $text =~ s/%\{(.*)%\}.*/$1/sm; | |||
| 166 | |||||
| 167 | # Drop all text after HEADERIZER STOP | ||||
| 168 | 7 | $text =~ s{/\*\s*HEADERIZER STOP.+}{}s; | |||
| 169 | |||||
| 170 | # Strip blocks of comments | ||||
| 171 | 7 | $text =~ s{^/\*.*?\*/}{}mxsg; | |||
| 172 | |||||
| 173 | # Strip # compiler directives | ||||
| 174 | 7 | $text =~ s{^#(\\\n|.)*}{}mg; | |||
| 175 | |||||
| 176 | # Strip code blocks | ||||
| 177 | 7 | $text =~ s/^{.+?^}//msg; | |||
| 178 | |||||
| 179 | # Split on paragraphs | ||||
| 180 | 7 | my @funcs = split /\n{2,}/, $text; | |||
| 181 | |||||
| 182 | # If it doesn't start in the left column, it's not a func | ||||
| 183 | 7 136 | @funcs = grep { /^\S/ } @funcs; | |||
| 184 | |||||
| 185 | # Typedefs, enums and externs are no good | ||||
| 186 | 7 43 | @funcs = grep { !/^(?:typedef|enum|extern)\b/ } @funcs; | |||
| 187 | |||||
| 188 | # Structs are OK if they're not alone on the line | ||||
| 189 | 7 43 | @funcs = grep { !/^struct.+;\n/ } @funcs; | |||
| 190 | |||||
| 191 | # Structs are OK if they're not being defined | ||||
| 192 | 7 43 | @funcs = grep { !/^(?:static\s+)?struct.+{\n/ } @funcs; | |||
| 193 | |||||
| 194 | # Ignore magic function name YY_DECL | ||||
| 195 | 7 43 | @funcs = grep { !/YY_DECL/ } @funcs; | |||
| 196 | |||||
| 197 | # Ignore anything with magic words HEADERIZER SKIP | ||||
| 198 | 7 43 | @funcs = grep { !m{/\*\s*HEADERIZER SKIP\s*\*/} } @funcs; | |||
| 199 | |||||
| 200 | # pmclass declarations in PMC files are no good | ||||
| 201 | 7 43 | @funcs = grep { !m{^pmclass } } @funcs; | |||
| 202 | |||||
| 203 | # Variables are of no use to us | ||||
| 204 | 7 41 | @funcs = grep { !/=/ } @funcs; | |||
| 205 | |||||
| 206 | # Get rid of any blocks at the end | ||||
| 207 | 7 7 | s/\s*{.*//s for @funcs; | |||
| 208 | |||||
| 209 | # Toast anything non-whitespace | ||||
| 210 | 7 41 | @funcs = grep { /\S/ } @funcs; | |||
| 211 | |||||
| 212 | # If it's got a semicolon, it's not a function header | ||||
| 213 | 7 41 | @funcs = grep { !/;/ } @funcs; | |||
| 214 | |||||
| 215 | # remove any remaining }'s | ||||
| 216 | 7 38 | @funcs = grep {! /^}/} @funcs; | |||
| 217 | |||||
| 218 | 7 | chomp @funcs; | |||
| 219 | |||||
| 220 | 7 | return @funcs; | |||
| 221 | } | ||||
| 222 | |||||
| 223 - 228 | =head2 extract_function_declaration_and_update_source( $cfile_name ) Extract all the function declarations from the C file specified by I<$cfile_name>, and update the comment blocks within. =cut | ||||
| 229 | |||||
| 230 | sub extract_function_declarations_and_update_source { | ||||
| 231 | 6 | my $self = shift; | |||
| 232 | 6 | my $cfile_name = shift; | |||
| 233 | |||||
| 234 | 6 | open( my $fhin, '<', $cfile_name ) or die "Can't open $cfile_name: $!"; | |||
| 235 | 6 | my $text = join( '', <$fhin> ); | |||
| 236 | 6 | close $fhin; | |||
| 237 | |||||
| 238 | 6 | my @func_declarations = $self->extract_function_declarations( $text ); | |||
| 239 | 6 | for my $decl ( @func_declarations ) { | |||
| 240 | 31 | my $specs = $self->function_components_from_declaration( $cfile_name, $decl ); | |||
| 241 | 31 | my $name = $specs->{name}; | |||
| 242 | |||||
| 243 | 31 | my $heading = $self->generate_documentation_signature($decl); | |||
| 244 | 31 | $text = replace_pod_item( { | |||
| 245 | text => $text, | ||||
| 246 | name => $name, | ||||
| 247 | heading => $heading, | ||||
| 248 | cfile_name => $cfile_name, | ||||
| 249 | } ); | ||||
| 250 | } | ||||
| 251 | 6 | open( my $fhout, '>', $cfile_name ) or die "Can't create $cfile_name: $!"; | |||
| 252 | 6 6 | print {$fhout} $text; | |||
| 253 | 6 | close $fhout; | |||
| 254 | |||||
| 255 | 6 | return @func_declarations; | |||
| 256 | } | ||||
| 257 | |||||
| 258 - 275 | =head2 C<function_components_from_declaration($file, $proto)>
$file => the filename
$proto => the function declaration
Returns an anonymous hash of function components:
file => $file,
name => $name,
args => \@args,
macros => \@macros,
is_static => $is_static,
is_inline => $parrot_inline,
is_api => $parrot_api,
is_ignorable => $is_ignorable,
return_type => $return_type,
=cut | ||||
| 276 | |||||
| 277 | sub function_components_from_declaration { | ||||
| 278 | 69 | my $self = shift; | |||
| 279 | 69 | my $file = shift; | |||
| 280 | 69 | my $proto = shift; | |||
| 281 | |||||
| 282 | 69 | my @lines = split( /\n/, $proto ); | |||
| 283 | 69 | chomp @lines; | |||
| 284 | |||||
| 285 | 69 | my @macros; | |||
| 286 | 69 | my $parrot_api; | |||
| 287 | 69 | my $parrot_inline; | |||
| 288 | |||||
| 289 | 69 | while ( @lines && ( $lines[0] =~ /^PARROT_/ ) ) { | |||
| 290 | 94 | my $macro = shift @lines; | |||
| 291 | 94 | if ( $macro eq 'PARROT_EXPORT' ) { | |||
| 292 | 63 | $parrot_api = 1; | |||
| 293 | } | ||||
| 294 | elsif ( $macro eq 'PARROT_INLINE' ) { | ||||
| 295 | 2 | $parrot_inline = 1; | |||
| 296 | } | ||||
| 297 | 94 | push( @macros, $macro ); | |||
| 298 | } | ||||
| 299 | |||||
| 300 | 69 | my $return_type = shift @lines; | |||
| 301 | 69 | my $args = join( ' ', @lines ); | |||
| 302 | |||||
| 303 | 69 | $args =~ s/\s+/ /g; | |||
| 304 | 69 | $args =~ s{([^(]+)\s*\((.+)\);?}{$2} | |||
| 305 | or die qq{Couldn't handle "$proto" in $file\n}; | ||||
| 306 | |||||
| 307 | 69 | my $name = $1; | |||
| 308 | 69 | $args = $2; | |||
| 309 | |||||
| 310 | 69 | no_both_PARROT_EXPORT_and_PARROT_INLINE( { | |||
| 311 | file => $file, | ||||
| 312 | name => $name, | ||||
| 313 | parrot_inline => $parrot_inline, | ||||
| 314 | parrot_api => $parrot_api, | ||||
| 315 | } ); | ||||
| 316 | |||||
| 317 | 69 | my @args = validate_prototype_args( $args, $proto ); | |||
| 318 | |||||
| 319 | 69 | my $is_static; | |||
| 320 | 69 | ($return_type, $is_static) = no_both_static_and_PARROT_EXPORT( { | |||
| 321 | file => $file, | ||||
| 322 | name => $name, | ||||
| 323 | return_type => $return_type, | ||||
| 324 | parrot_api => $parrot_api, | ||||
| 325 | } ); | ||||
| 326 | |||||
| 327 | 69 | my $is_ignorable = 0; | |||
| 328 | 69 | my %macros; | |||
| 329 | 69 | for my $macro (@macros) { | |||
| 330 | 94 | $macros{$macro} = 1; | |||
| 331 | 94 | if (not $self->valid_macro($macro)) { | |||
| 332 | 0 | $self->squawk( $file, $name, "Invalid macro $macro" ); | |||
| 333 | } | ||||
| 334 | 94 | if ( $macro eq 'PARROT_IGNORABLE_RESULT' ) { | |||
| 335 | 2 | $is_ignorable = 1; | |||
| 336 | } | ||||
| 337 | } | ||||
| 338 | $self->check_pointer_return_type( { | ||||
| 339 | 69 | return_type => $return_type, | |||
| 340 | macros => \%macros, | ||||
| 341 | name => $name, | ||||
| 342 | file => $file, | ||||
| 343 | } ); | ||||
| 344 | |||||
| 345 | return { | ||||
| 346 | 69 | file => $file, | |||
| 347 | name => $name, | ||||
| 348 | args => \@args, | ||||
| 349 | macros => \@macros, | ||||
| 350 | is_static => $is_static, | ||||
| 351 | is_inline => $parrot_inline, | ||||
| 352 | is_api => $parrot_api, | ||||
| 353 | is_ignorable => $is_ignorable, | ||||
| 354 | return_type => $return_type, | ||||
| 355 | }; | ||||
| 356 | } | ||||
| 357 | |||||
| 358 - 367 | =head2 C<check_pointer_return_type()>
$self->check_pointer_return_type( {
return_type => $return_type,
macros => \%macros,
name => $name,
file => $file,
} );
=cut | ||||
| 368 | |||||
| 369 | sub check_pointer_return_type { | ||||
| 370 | 76 | my ($self, $args) = @_; | |||
| 371 | 76 | if ( $args->{return_type} =~ /\*/ ) { | |||
| 372 | 33 | if ( !$args->{macros}->{PARROT_CAN_RETURN_NULL} && !$args->{macros}->{PARROT_CANNOT_RETURN_NULL} ) { | |||
| 373 | 2 | if ( $args->{name} !~ /^yy/ ) { # Don't complain about lexer-created functions | |||
| 374 | 1 | $self->squawk( $args->{file}, $args->{name}, | |||
| 375 | 'Returns a pointer, but no PARROT_CAN(NOT)_RETURN_NULL macro found.' ); | ||||
| 376 | } | ||||
| 377 | } | ||||
| 378 | elsif ( $args->{macros}->{PARROT_CAN_RETURN_NULL} && $args->{macros}->{PARROT_CANNOT_RETURN_NULL} ) { | ||||
| 379 | 1 | $self->squawk( $args->{file}, $args->{name}, | |||
| 380 | q{Can't have both PARROT_CAN_RETURN_NULL and PARROT_CANNOT_RETURN_NULL together.} ); | ||||
| 381 | } | ||||
| 382 | } | ||||
| 383 | } | ||||
| 384 | |||||
| 385 - 390 | =head2 C<generate_documentation_signature> Given an extracted function signature, return a modified version suitable for inclusion in POD documentation. =cut | ||||
| 391 | |||||
| 392 | sub generate_documentation_signature { | ||||
| 393 | 31 | my $self = shift; | |||
| 394 | 31 | my $function_decl = shift; | |||
| 395 | |||||
| 396 | # strip out any PARROT_* function modifiers | ||||
| 397 | 31 | foreach my $key ($self->valid_macros) { | |||
| 398 | 465 | $function_decl =~ s/^$key$//m; | |||
| 399 | } | ||||
| 400 | |||||
| 401 | 31 | $function_decl =~ s/^\s+//g; | |||
| 402 | 31 | $function_decl =~ s/\s+/ /g; | |||
| 403 | |||||
| 404 | # strip out any ARG* modifiers | ||||
| 405 | 31 | $function_decl =~ s/ARG(?:IN|IN_NULLOK|OUT|OUT_NULLOK|MOD|MOD_NULLOK|FREE|FREE_NOTNULL)\((.*?)\)/$1/g; | |||
| 406 | |||||
| 407 | # strip out the SHIM modifier | ||||
| 408 | 31 | $function_decl =~ s/SHIM\((.*?)\)/$1/g; | |||
| 409 | |||||
| 410 | # strip out the NULL modifiers | ||||
| 411 | 31 | $function_decl =~ s/(?:NULLOK|NOTNULL)\((.*?)\)/$1/g; | |||
| 412 | |||||
| 413 | # SHIM_INTERP is still a PARROT_INTERP | ||||
| 414 | 31 | $function_decl =~ s/SHIM_INTERP/PARROT_INTERP/g; | |||
| 415 | |||||
| 416 | # wrap with POD | ||||
| 417 | 31 | $function_decl = "=item C<$function_decl>"; | |||
| 418 | |||||
| 419 | # Wrap long lines. | ||||
| 420 | 31 | my $line_len = 80; | |||
| 421 | 31 | if (length($function_decl)<= $line_len) { | |||
| 422 | 17 | return $function_decl; | |||
| 423 | } | ||||
| 424 | else { | ||||
| 425 | 14 | return handle_split_declaration( | |||
| 426 | $function_decl, | ||||
| 427 | $line_len, | ||||
| 428 | ); | ||||
| 429 | } | ||||
| 430 | } | ||||
| 431 | |||||
| 432 - 438 | =head2 C<valid_macro()>
$headerizer->valid_macro( $macro )
Returns a boolean saying whether I<$macro> is a valid C<PARROT_XXX> macro.
=cut | ||||
| 439 | |||||
| 440 | sub valid_macro { | ||||
| 441 | 96 | my $self = shift; | |||
| 442 | 96 | my $macro = shift; | |||
| 443 | |||||
| 444 | 96 | return exists $self->{valid_macros}{$macro}; | |||
| 445 | } | ||||
| 446 | |||||
| 447 - 453 | =head2 C<valid_macros()>
$headerizer->valid_macros()
Returns a list of all the valid C<PARROT_XXX> macros.
=cut | ||||
| 454 | |||||
| 455 | sub valid_macros { | ||||
| 456 | 32 | my $self = shift; | |||
| 457 | |||||
| 458 | 32 32 | my @macros = sort keys %{$self->{valid_macros}}; | |||
| 459 | |||||
| 460 | 32 | return @macros; | |||
| 461 | } | ||||
| 462 | |||||
| 463 - 471 | =head2 C<squawk($file, $func, $error)> Headerizer-specific ways of complaining if something went wrong. $file => filename $func => function name $error => error message text =cut | ||||
| 472 | |||||
| 473 | sub squawk { | ||||
| 474 | 6 | my $self = shift; | |||
| 475 | 6 | my $file = shift; | |||
| 476 | 6 | my $func = shift; | |||
| 477 | 6 | my $error = shift; | |||
| 478 | |||||
| 479 | 6 6 | push( @{ $self->{warnings}{$file}{$func} }, $error ); | |||
| 480 | |||||
| 481 | 6 | return; | |||
| 482 | } | ||||
| 483 | |||||
| 484 | sub process_sources { | ||||
| 485 | 4 | my ($self) = @_; | |||
| 486 | 4 4 | my %sourcefiles = %{$self->{sourcefiles}}; | |||
| 487 | 4 4 | my %sourcefiles_with_statics = %{$self->{sourcefiles_with_statics}}; | |||
| 488 | 4 4 | my %api = %{$self->{api}}; | |||
| 489 | 4 | if ( $self->{macro_match} ) { | |||
| 490 | 1 | my $nfuncs = 0; | |||
| 491 | 1 | for my $cfile ( sort keys %api ) { | |||
| 492 | 1 1 1 | my @funcs = sort { $a->{name} cmp $b->{name} } @{$api{$cfile}}; | |||
| 493 | 1 | print "$cfile\n"; | |||
| 494 | 1 | for my $func ( @funcs ) { | |||
| 495 | 2 | print " $func->{name}\n"; | |||
| 496 | 2 | ++$nfuncs; | |||
| 497 | } | ||||
| 498 | } | ||||
| 499 | 1 | my $s = $nfuncs == 1 ? '' : 's'; | |||
| 500 | 1 | $self->{message} = "$nfuncs $self->{macro_match} function$s"; | |||
| 501 | } | ||||
| 502 | else { # Normal headerization and updating | ||||
| 503 | # Update all the .h files | ||||
| 504 | 3 | for my $hfile ( sort keys %sourcefiles ) { | |||
| 505 | 1 | my $sourcefiles = $sourcefiles{$hfile}; | |||
| 506 | |||||
| 507 | 1 | my $header = read_file($hfile); | |||
| 508 | |||||
| 509 | 1 1 | for my $cfile ( sort keys %{$sourcefiles} ) { | |||
| 510 | 1 1 | my @funcs = @{ $sourcefiles->{$cfile} }; | |||
| 511 | 1 7 | @funcs = grep { not $_->{is_static} } @funcs; # skip statics | |||
| 512 | 1 | $header = $self->replace_headerized_declarations( | |||
| 513 | $header, $cfile, $hfile, @funcs ); | ||||
| 514 | } | ||||
| 515 | |||||
| 516 | 1 | write_file( $hfile, $header ); | |||
| 517 | } | ||||
| 518 | |||||
| 519 | # Update all the .c files in place | ||||
| 520 | 3 | for my $cfile ( sort keys %sourcefiles_with_statics ) { | |||
| 521 | 2 2 | my @funcs = @{ $sourcefiles_with_statics{$cfile} }; | |||
| 522 | 2 3 | @funcs = grep { $_->{is_static} } @funcs; | |||
| 523 | |||||
| 524 | 2 | my $source = read_file($cfile); | |||
| 525 | 2 | $source = $self->replace_headerized_declarations( $source, 'static', $cfile, @funcs ); | |||
| 526 | |||||
| 527 | 2 | write_file( $cfile, $source ); | |||
| 528 | } | ||||
| 529 | 3 | $self->{message} = "Headerization complete."; | |||
| 530 | } | ||||
| 531 | } | ||||
| 532 | |||||
| 533 | sub replace_headerized_declarations { | ||||
| 534 | 3 | my $self = shift; | |||
| 535 | 3 | my $source_code = shift; | |||
| 536 | 3 | my $sourcefile = shift; | |||
| 537 | 3 | my $hfile = shift; | |||
| 538 | 3 | my @funcs = @_; | |||
| 539 | |||||
| 540 | # Allow a way to not headerize statics | ||||
| 541 | 3 | if ( $source_code =~ m{/\*\s*HEADERIZER NONE:\s*$sourcefile\s*\*/} ) { | |||
| 542 | 0 | return $source_code; | |||
| 543 | } | ||||
| 544 | |||||
| 545 | 15 | @funcs = sort { | |||
| 546 | 3 | ( ( $b->{is_api} || 0 ) <=> ( $a->{is_api} || 0 ) ) | |||
| 547 | || ( ( lc($a->{name}) || '') cmp ( lc($b->{name}) || '') ) | ||||
| 548 | } @funcs; | ||||
| 549 | 3 | my @function_decls = $self->make_function_decls(@funcs); | |||
| 550 | |||||
| 551 | 3 | my $markers_args = { | |||
| 552 | function_decls => \@function_decls, | ||||
| 553 | sourcefile => $sourcefile, | ||||
| 554 | hfile => $hfile, | ||||
| 555 | code => $source_code, | ||||
| 556 | }; | ||||
| 557 | |||||
| 558 | 3 | return add_headerizer_markers( $markers_args ); | |||
| 559 | } | ||||
| 560 | |||||
| 561 | |||||
| 562 | sub make_function_decls { | ||||
| 563 | 3 | my $self = shift; | |||
| 564 | 3 | my @funcs = @_; | |||
| 565 | |||||
| 566 | 3 | my @decls; | |||
| 567 | 3 | foreach my $func (@funcs) { | |||
| 568 | 10 | my $alt_void = ' '; | |||
| 569 | |||||
| 570 | # Splint can't handle /*@alt void@*/ on pointers, although this page | ||||
| 571 | # http://www.mail-archive.com/lclint-interest@virginia.edu/msg00139.html | ||||
| 572 | # seems to say that we can. | ||||
| 573 | 10 | if ( $func->{is_ignorable} && ($func->{return_type} !~ /\*/) ) { | |||
| 574 | 1 | $alt_void = " /*\@alt void@*/\n"; | |||
| 575 | } | ||||
| 576 | |||||
| 577 | 10 | my $decl = sprintf( "%s%s%s(" => ( | |||
| 578 | $func->{return_type}, | ||||
| 579 | $alt_void, | ||||
| 580 | $func->{name} | ||||
| 581 | ) ); | ||||
| 582 | 10 | $decl = "static $decl" if $func->{is_static}; | |||
| 583 | |||||
| 584 | 10 10 | my @args = @{ $func->{args} }; | |||
| 585 | 10 | my @attrs = $self->attrs_from_args( $func, @args ); | |||
| 586 | |||||
| 587 | 10 | my @modified_args = shim_test($func, \@args); | |||
| 588 | |||||
| 589 | 10 | my $multiline; | |||
| 590 | 10 | ($decl, $multiline) = handle_modified_args( | |||
| 591 | $decl, \@modified_args); | ||||
| 592 | |||||
| 593 | 10 22 | my $attrs = join( "", map { "\n\t\t$_" } @attrs ); | |||
| 594 | 10 | if ($attrs) { | |||
| 595 | 8 | $decl .= $attrs; | |||
| 596 | 8 | $multiline = 1; | |||
| 597 | } | ||||
| 598 | 10 10 | my @macros = @{ $func->{macros} }; | |||
| 599 | 10 | $multiline = 1 if @macros; | |||
| 600 | |||||
| 601 | 10 | $decl = add_newline_if_multiline($decl, $multiline); | |||
| 602 | 10 | $decl = join( "\n", @macros, $decl ); | |||
| 603 | 10 | $decl =~ s/\t/ /g; | |||
| 604 | 10 | push( @decls, $decl ); | |||
| 605 | } | ||||
| 606 | |||||
| 607 | 3 | @decls = add_asserts_to_declarations( \@funcs, \@decls ); | |||
| 608 | |||||
| 609 | 3 | return @decls; | |||
| 610 | } | ||||
| 611 | |||||
| 612 | sub attrs_from_args { | ||||
| 613 | 17 | my $self = shift; | |||
| 614 | 17 | my $func = shift; | |||
| 615 | 17 | my @args = @_; | |||
| 616 | |||||
| 617 | 17 | my @attrs = (); | |||
| 618 | 17 | my @mods = (); | |||
| 619 | |||||
| 620 | 17 | my $name = $func->{name}; | |||
| 621 | 17 | my $file = $func->{file}; | |||
| 622 | 17 | my $n = 0; | |||
| 623 | 17 | for my $arg (@args) { | |||
| 624 | 30 | ++$n; | |||
| 625 | 30 | @mods = func_modifies($arg, \@mods); | |||
| 626 | 30 | if ( $arg =~ m{(ARGIN|ARGOUT|ARGMOD|ARGFREE_NOTNULL|NOTNULL)\(} || $arg eq 'PARROT_INTERP' ) { | |||
| 627 | 15 | push( @attrs, "__attribute__nonnull__($n)" ); | |||
| 628 | } | ||||
| 629 | 30 | if ( ( $arg =~ m{\*} ) && ( $arg !~ /\b(SHIM|((ARGIN|ARGOUT|ARGMOD)(_NULLOK)?)|ARGFREE(_NOTNULL)?)\b/ ) ) { | |||
| 630 | 2 | if ( $name !~ /^yy/ ) { # Don't complain about the lexer auto-generated funcs | |||
| 631 | 1 | $self->squawk( $file, $name, qq{"$arg" isn't protected with an ARGIN, ARGOUT or ARGMOD (or a _NULLOK variant), or ARGFREE} ); | |||
| 632 | } | ||||
| 633 | } | ||||
| 634 | 30 | if ( ($arg =~ /\bconst\b/) && ($arg =~ /\*/) && ($arg !~ /\*\*/) && ($arg =~ /\b(ARG(MOD|OUT))\b/) ) { | |||
| 635 | 1 | $self->squawk( $file, $name, qq{"$arg" is const, but that $1 conflicts with const} ); | |||
| 636 | } | ||||
| 637 | } | ||||
| 638 | |||||
| 639 | 17 | return (@attrs,@mods); | |||
| 640 | } | ||||
| 641 | |||||
| 642 | sub print_final_message { | ||||
| 643 | 6 | my $self = shift; | |||
| 644 | 6 | if ($self->{message} ne '') { | |||
| 645 | 5 | print "$self->{message}\n"; | |||
| 646 | } | ||||
| 647 | } | ||||
| 648 | |||||
| 649 - 663 | =head2 C<print_headerizer_warnings()> =over 4 =item * Purpose =item * Arguments =item * Return Value =item * Comment =back =cut | ||||
| 664 | |||||
| 665 | sub print_warnings { | ||||
| 666 | 17 | my $self = shift; | |||
| 667 | 17 17 | my %warnings = %{$self->{warnings}}; | |||
| 668 | 17 | if ( keys %warnings ) { | |||
| 669 | 6 | my $nwarnings = 0; | |||
| 670 | 6 | my $nwarningfuncs = 0; | |||
| 671 | 6 | my $nwarningfiles = 0; | |||
| 672 | 6 | for my $file ( sort keys %warnings ) { | |||
| 673 | 7 | ++$nwarningfiles; | |||
| 674 | 7 | print "$file\n"; | |||
| 675 | 7 | my $funcs = $warnings{$file}; | |||
| 676 | 7 7 | for my $func ( sort keys %{$funcs} ) { | |||
| 677 | 8 | ++$nwarningfuncs; | |||
| 678 | 8 8 | for my $error ( @{ $funcs->{$func} } ) { | |||
| 679 | 14 | print " $func: $error\n"; | |||
| 680 | 14 | ++$nwarnings; | |||
| 681 | } | ||||
| 682 | } | ||||
| 683 | } | ||||
| 684 | |||||
| 685 | 6 | print "$nwarnings warnings in $nwarningfuncs funcs in $nwarningfiles C files\n"; | |||
| 686 | } | ||||
| 687 | } | ||||
| 688 | |||||
| 689 | 1; | ||||
| 690 | |||||
| 691 | # Local Variables: | ||||
| 692 | # mode: cperl | ||||
| 693 | # cperl-indent-level: 4 | ||||
| 694 | # fill-column: 100 | ||||
| 695 | # End: | ||||
| 696 | # vim: expandtab shiftwidth=4: | ||||