4 # Copyright 2006, Jeff Morriss <jeff.morriss.ws[AT]gmail.com>
6 # A simple tool to check source code for function calls that should not
7 # be called by Wireshark code and to perform certain other checks.
10 # checkAPIs.pl [-M] [-g group1] [-g group2] ...
11 # [-s summary-group1] [-s summary-group2] ...
12 # [--nocheck-value-string-array]
13 # [--nocheck-addtext] [--nocheck-hf] [--debug] file1 file2 ...
15 # Wireshark - Network traffic analyzer
16 # By Gerald Combs <gerald@wireshark.org>
17 # Copyright 1998 Gerald Combs
19 # SPDX-License-Identifier: GPL-2.0-or-later
24 use Text::Balanced qw(extract_bracketed);
28 # Group name, e.g. 'prohibited'
30 # 'count_errors' => 1, # 1 if these are errors, 0 if warnings
31 # 'functions' => [ 'f1', 'f2', ...], # Function array
32 # 'function-counts' => {'f1',0, 'f2',0, ...}, # Function Counts hash (initialized in the code)
35 # APIs that MUST NOT be used in Wireshark
36 'prohibited' => { 'count_errors' => 1, 'functions' => [
38 # Use something that won't overwrite the end of your buffer instead
41 # Microsoft provides lists of unsafe functions and their
42 # recommended replacements in "Security Development Lifecycle
43 # (SDL) Banned Function Calls"
44 # https://msdn.microsoft.com/en-us/library/bb288454.aspx
45 # and "Deprecated CRT Functions"
46 # https://msdn.microsoft.com/en-us/library/ms235384.aspx
48 'atoi', # use wsutil/strtoi.h functions
61 # use glib (g_*) versions instead of these:
68 # Windows doesn't have this; use g_ascii_strtoull() instead
70 ### non-portable: fails on Windows Wireshark built with VC newer than VC6
71 # See https://bugs.wireshark.org/bugzilla/show_bug.cgi?id=6695#c2
75 # use memset, memcpy, memcmp instead of these:
79 # The MSDN page for ZeroMemory recommends SecureZeroMemory
82 # use wmem_*, ep_*, or g_* functions instead of these:
83 # (One thing to be aware of is that space allocated with malloc()
84 # may not be freeable--at least on Windows--with g_free() and
93 # These may have unexpected behaviors in some locales (e.g.,
94 # "I" isn't always the upper-case form of "i", and "i" isn't
95 # always the lower-case form of "I"). Use the g_ascii_* version
120 'strerror', # use g_strerror
121 # Use the ws_* version of these:
122 # (Necessary because on Windows we use UTF8 for throughout the code
123 # so we must tweak that to UTF16 before operating on the file. Code
124 # using these functions will work unless the file/path name contains
137 'tmpnam', # use mkstemp
138 '_snwprintf' # use StringCchPrintf
141 ### Soft-Deprecated functions that should not be used in new code but
142 # have not been entirely removed from old code. These will become errors
143 # once they've been removed from all existing code.
144 'soft-deprecated' => { 'count_errors' => 0, 'functions' => [
145 'tvb_length_remaining', # replaced with tvb_captured_length_remaining
148 # These may have unexpected behaviors in some locales (e.g.,
149 # "I" isn't always the upper-case form of "i", and "i" isn't
150 # always the lower-case form of "I"). Use the g_ascii_* version
155 # APIs that SHOULD NOT be used in Wireshark (any more)
156 'deprecated' => { 'count_errors' => 1, 'functions' => [
157 'perror', # Use g_strerror() and report messages in whatever
158 # fashion is appropriate for the code in question.
159 'ctime', # Use abs_time_secs_to_str()
160 'next_tvb_add_port', # Use next_tvb_add_uint() (and a matching change
161 # of NTVB_PORT -> NTVB_UINT)
163 ### Deprecated GLib/GObject functions/macros
164 # (The list is based upon the GLib 2.30.2 & GObject 2.30.2 documentation;
165 # An entry may be commented out if it is currently
166 # being used in Wireshark and if the replacement functionality
167 # is not available in all the GLib versions that Wireshark
168 # currently supports.
169 # Note: Wireshark currently (Jan 2012) requires GLib 2.14 or newer.
170 # The Wireshark build currently (Jan 2012) defines G_DISABLE_DEPRECATED
171 # so use of any of the following should cause the Wireshark build to fail and
172 # therefore the tests for obsolete GLib function usage in checkAPIs should not be needed.
175 'g_allocator_free', # "use slice allocator" (avail since 2.10,2.14)
176 'g_allocator_new', # "use slice allocator" (avail since 2.10,2.14)
177 'g_async_queue_ref_unlocked', # g_async_queue_ref() (OK since 2.8)
178 'g_async_queue_unref_and_unlock', # g_async_queue_unref() (OK since 2.8)
179 'g_atomic_int_exchange_and_add', # since 2.30
181 'g_blow_chunks', # "use slice allocator" (avail since 2.10,2.14)
182 'g_cache_value_foreach', # g_cache_key_foreach()
183 'g_chunk_free', # g_slice_free (avail since 2.10)
184 'g_chunk_new', # g_slice_new (avail since 2.10)
185 'g_chunk_new0', # g_slice_new0 (avail since 2.10)
186 'g_completion_add_items', # since 2.26
187 'g_completion_clear_items', # since 2.26
188 'g_completion_complete', # since 2.26
189 'g_completion_complete_utf8', # since 2.26
190 'g_completion_free', # since 2.26
191 'g_completion_new', # since 2.26
192 'g_completion_remove_items', # since 2.26
193 'g_completion_set_compare', # since 2.26
194 'G_CONST_RETURN', # since 2.26
195 'g_date_set_time', # g_date_set_time_t (avail since 2.10)
197 'g_format_size_for_display', # since 2.30: use g_format_size()
199 'G_GNUC_PRETTY_FUNCTION',
200 'g_hash_table_freeze',
203 'g_io_channel_close',
206 'g_io_channel_write',
207 'g_list_pop_allocator', # "does nothing since 2.10"
208 'g_list_push_allocator', # "does nothing since 2.10"
216 'g_main_set_poll_func',
217 'g_mapped_file_free', # [as of 2.22: use g_map_file_unref]
218 'g_mem_chunk_alloc', # "use slice allocator" (avail since 2.10)
219 'g_mem_chunk_alloc0', # "use slice allocator" (avail since 2.10)
220 'g_mem_chunk_clean', # "use slice allocator" (avail since 2.10)
221 'g_mem_chunk_create', # "use slice allocator" (avail since 2.10)
222 'g_mem_chunk_destroy', # "use slice allocator" (avail since 2.10)
223 'g_mem_chunk_free', # "use slice allocator" (avail since 2.10)
224 'g_mem_chunk_info', # "use slice allocator" (avail since 2.10)
225 'g_mem_chunk_new', # "use slice allocator" (avail since 2.10)
226 'g_mem_chunk_print', # "use slice allocator" (avail since 2.10)
227 'g_mem_chunk_reset', # "use slice allocator" (avail since 2.10)
228 'g_node_pop_allocator', # "does nothing since 2.10"
229 'g_node_push_allocator', # "does nothing since 2.10"
230 'g_relation_count', # since 2.26
231 'g_relation_delete', # since 2.26
232 'g_relation_destroy', # since 2.26
233 'g_relation_exists', # since 2.26
234 'g_relation_index', # since 2.26
235 'g_relation_insert', # since 2.26
236 'g_relation_new', # since 2.26
237 'g_relation_print', # since 2.26
238 'g_relation_select', # since 2.26
239 'g_scanner_add_symbol',
240 'g_scanner_remove_symbol',
241 'g_scanner_foreach_symbol',
242 'g_scanner_freeze_symbol_table',
243 'g_scanner_thaw_symbol_table',
244 'g_slist_pop_allocator', # "does nothing since 2.10"
245 'g_slist_push_allocator', # "does nothing since 2.10"
246 'g_source_get_current_time', # since 2.28: use g_source_get_time()
250 'g_string_sprintf', # use g_string_printf() instead
251 'g_string_sprintfa', # use g_string_append_printf instead
256 'g_tuples_destroy', # since 2.26
257 'g_tuples_index', # since 2.26
258 'g_unicode_canonical_decomposition', # since 2.30: use g_unichar_fully_decompose()
259 'G_UNICODE_COMBINING_MARK', # since 2.30:use G_UNICODE_SPACING_MARK
260 'g_value_set_boxed_take_ownership', # GObject
261 'g_value_set_object_take_ownership', # GObject
262 'g_value_set_param_take_ownership', # GObject
263 'g_value_set_string_take_ownership', # Gobject
264 'G_WIN32_DLLMAIN_FOR_DLL_NAME',
265 'g_win32_get_package_installation_directory',
266 'g_win32_get_package_installation_subdirectory',
270 # APIs that make the program exit. Dissectors shouldn't call these
271 'abort' => { 'count_errors' => 1, 'functions' => [
280 # APIs that print to the terminal. Dissectors shouldn't call these
281 'termoutput' => { 'count_errors' => 0, 'functions' => [
288 my @apiGroups = qw(prohibited deprecated soft-deprecated);
291 # Given a ref to a hash containing "functions" and "functions_count" entries:
292 # Determine if any item of the list of APIs contained in the array referenced by "functions"
293 # exists in the file.
294 # For each API which appears in the file:
295 # Push the API onto the provided list;
296 # Add the number of times the API appears in the file to the total count
297 # for the API (stored as the value of the API key in the hash referenced by "function_counts").
299 sub findAPIinFile($$$)
301 my ($groupHashRef, $fileContentsRef, $foundAPIsRef) = @_;
303 for my $api ( @{$groupHashRef->{functions}} )
306 # Match function calls, but ignore false positives from:
307 # C++ method definition: int MyClass::open(...)
308 # Method invocation: myClass->open(...);
309 # Function declaration: int open(...);
310 # Method invocation: QString().sprintf(...)
311 while (${$fileContentsRef} =~ m/ \W (?<!::|->|\w\ ) (?<!\.) $api \W* \( /gx)
316 push @{$foundAPIsRef}, $api;
317 $groupHashRef->{function_counts}->{$api} += 1;
322 # APIs which (generally) should not be called with an argument of tvb_get_ptr()
324 # Use NULL for the value_ptr instead of tvb_get_ptr() (only if the
325 # given offset and length are equal) with these:
326 'proto_tree_add_bytes_format',
327 'proto_tree_add_bytes_format_value',
328 'proto_tree_add_ether',
329 # Use the tvb_* version of these:
330 # Use tvb_bytes_to_str[_punct] instead of:
332 'bytes_to_str_punct',
337 sub checkAPIsCalledWithTvbGetPtr($$$)
339 my ($APIs, $fileContentsRef, $foundAPIsRef) = @_;
341 for my $api (@{$APIs}) {
345 @items = (${$fileContentsRef} =~ m/ ($api [^;]* ; ) /xsg);
349 if ($item =~ / tvb_get_ptr /xos) {
355 push @{$foundAPIsRef}, $api;
360 # List of possible shadow variable (Majority coming from macOS..)
361 my @ShadowVariable = (
368 sub checkShadowVariable($$$)
370 my ($groupHashRef, $fileContentsRef, $foundAPIsRef) = @_;
372 for my $api ( @{$groupHashRef} )
375 while (${$fileContentsRef} =~ m/ \s $api \s*+ [^\(\w] /gx)
380 push @{$foundAPIsRef}, $api;
385 sub check_snprintf_plus_strlen($$)
387 my ($fileContentsRef, $filename) = @_;
390 # This catches both snprintf() and g_snprint.
391 # If we need to do more APIs, we can make this function look more like
392 # checkAPIsCalledWithTvbGetPtr().
393 @items = (${$fileContentsRef} =~ m/ (snprintf [^;]* ; ) /xsg);
397 if ($item =~ / strlen\s*\( /xos) {
398 print STDERR "Warning: ".$filename." uses snprintf + strlen to assemble strings.\n";
404 #### Regex for use when searching for value-string definitions
405 my $StaticRegex = qr/ static \s+ /xs;
406 my $ConstRegex = qr/ const \s+ /xs;
407 my $Static_andor_ConstRegex = qr/ (?: $StaticRegex $ConstRegex | $StaticRegex | $ConstRegex) /xs;
408 my $ValueStringVarnameRegex = qr/ (?:value|val64|string|range|bytes)_string /xs;
409 my $ValueStringRegex = qr/ ^ \s* $Static_andor_ConstRegex ($ValueStringVarnameRegex) \ + [^;*]+ = [^;]+ [{] .+? [}] \s*? ; /xms;
410 my $EnumValRegex = qr/ $Static_andor_ConstRegex enum_val_t \ + [^;*]+ = [^;]+ [{] .+? [}] \s*? ; /xs;
411 my $NewlineStringRegex = qr/ ["] [^"]* \\n [^"]* ["] /xs;
413 sub check_value_string_arrays($$$)
415 my ($fileContentsRef, $filename, $debug_flag) = @_;
417 # Brute force check for value_string (and string_string or range_string) arrays
418 # which are missing {0, NULL} as the final (terminating) array entry
420 # Assumption: definition is of form (pseudo-Regex):
421 # " (static const|static|const) (value|string|range)_string .+ = { .+ ;"
422 # (possibly over multiple lines)
423 while (${$fileContentsRef} =~ / ( $ValueStringRegex ) /xsog) {
424 # XXX_string array definition found; check if NULL terminated
425 my $vs = my $vsx = $1;
428 $vsx =~ / ( .+ $ValueStringVarnameRegex [^=]+ ) = /xo;
429 printf STDERR "==> %-35.35s: %s\n", $filename, $1;
430 printf STDERR "%s\n", $vs;
434 # Check for expected trailer
437 if ($type eq "string_string") {
438 # XXX shouldn't we reject 0 since it is gchar*?
439 $expectedTrailer = "(NULL|0), NULL";
440 $trailerHint = "NULL, NULL";
441 } elsif ($type eq "range_string") {
442 $expectedTrailer = "0(x0+)?, 0(x0+)?, NULL";
443 $trailerHint = "0, 0, NULL";
444 } elsif ($type eq "bytes_string") {
445 # XXX shouldn't we reject 0 since it is guint8*?
446 $expectedTrailer = "(NULL|0), 0, NULL";
447 $trailerHint = "NULL, NULL";
449 $expectedTrailer = "0(x?0+)?, NULL";
450 $trailerHint = "0, NULL";
452 if ($vs !~ / [{] $expectedTrailer [}] ,? [}] ; $/x) {
453 $vsx =~ /( $ValueStringVarnameRegex [^=]+ ) = /xo;
454 printf STDERR "Error: %-35.35s: {%s} is required as the last %s array entry: %s\n", $filename, $trailerHint, $type, $1;
458 if ($vs !~ / (static)? const $ValueStringVarnameRegex /xo) {
459 $vsx =~ /( $ValueStringVarnameRegex [^=]+ ) = /xo;
460 printf STDERR "Error: %-35.35s: Missing 'const': %s\n", $filename, $1;
463 if ($vs =~ / $NewlineStringRegex /xo && $type ne "bytes_string") {
464 $vsx =~ /( $ValueStringVarnameRegex [^=]+ ) = /xo;
465 printf STDERR "Error: %-35.35s: XXX_string contains a newline: %s\n", $filename, $1;
470 # Brute force check for enum_val_t arrays which are missing {NULL, NULL, ...}
471 # as the final (terminating) array entry
472 # For now use the same option to turn this and value_string checking on and off.
473 # (Is the option even necessary?)
475 # Assumption: definition is of form (pseudo-Regex):
476 # " (static const|static|const) enum_val_t .+ = { .+ ;"
477 # (possibly over multiple lines)
478 while (${$fileContentsRef} =~ / ( $EnumValRegex ) /xsog) {
479 # enum_val_t array definition found; check if NULL terminated
480 my $vs = my $vsx = $1;
482 $vsx =~ / ( .+ enum_val_t [^=]+ ) = /xo;
483 printf STDERR "==> %-35.35s: %s\n", $filename, $1;
484 printf STDERR "%s\n", $vs;
487 # README.developer says
488 # "Don't put a comma after the last tuple of an initializer of an array"
489 # However: since this usage is present in some number of cases, we'll allow for now
490 if ($vs !~ / NULL, NULL, -?[0-9] [}] ,? [}] ; $/xo) {
491 $vsx =~ /( enum_val_t [^=]+ ) = /xo;
492 printf STDERR "Error: %-35.35s: {NULL, NULL, ...} is required as the last enum_val_t array entry: %s\n", $filename, $1;
495 if ($vs !~ / (static)? const enum_val_t /xo) {
496 $vsx =~ /( enum_val_t [^=]+ ) = /xo;
497 printf STDERR "Error: %-35.35s: Missing 'const': %s\n", $filename, $1;
500 if ($vs =~ / $NewlineStringRegex /xo) {
501 $vsx =~ /( (?:value|string|range)_string [^=]+ ) = /xo;
502 printf STDERR "Error: %-35.35s: enum_val_t contains a newline: %s\n", $filename, $1;
511 sub check_included_files($$)
513 my ($fileContentsRef, $filename) = @_;
516 @incFiles = (${$fileContentsRef} =~ m/\#include \s* ([<"].+[>"])/gox);
518 # only our wrapper file wsutils/wsgcrypt.h may include gcrypt.h
519 # all other files should include the wrapper
520 if ($filename !~ /wsgcrypt\.h/) {
521 foreach (@incFiles) {
522 if ( m#([<"]|/+)gcrypt\.h[>"]$# ) {
523 print STDERR "Warning: ".$filename.
524 " includes gcrypt.h directly. ".
525 "Include wsutil/wsgrypt.h instead.\n";
531 # only our wrapper file wsutils/wspcap.h may include pcap.h
532 # all other files should include the wrapper
533 if ($filename !~ /wspcap\.h/) {
534 foreach (@incFiles) {
535 if ( m#([<"]|/+)pcap\.h[>"]$# ) {
536 print STDERR "Warning: ".$filename.
537 " includes pcap.h directly. ".
538 "Include wsutil/wspcap.h instead.\n";
544 # files in the ui/qt directory should include the ui class includes
545 # by using #include <>
546 # this ensures that Visual Studio picks up these files from the
547 # build directory if we're compiling with cmake
548 if ($filename =~ m#ui/qt/# ) {
549 foreach (@incFiles) {
550 if ( m#"ui_.*\.h"$# ) {
551 # strip the quotes to get the base name
552 # for the error message
555 print STDERR "$filename: ".
556 "Please use #include <$_> ".
557 "instead of #include \"$_\".\n";
564 sub check_proto_tree_add_XXX($$)
566 my ($fileContentsRef, $filename) = @_;
570 @items = (${$fileContentsRef} =~ m/ (proto_tree_add_[_a-z0-9]+) \( ([^;]*) \) \s* ; /xsg);
578 #Check to make sure tvb_get* isn't used to pass into a proto_tree_add_<datatype>, when
579 #proto_tree_add_item could just be used instead
580 if ($args =~ /,\s*tvb_get_/xos) {
581 if (($func =~ m/^proto_tree_add_(time|bytes|ipxnet|ipv4|ipv6|ether|guid|oid|string|boolean|float|double|uint|uint64|int|int64|eui64|bitmask_list_value)$/)
583 print STDERR "Error: ".$filename." uses $func with tvb_get_*. Use proto_tree_add_item instead\n";
586 # Print out the function args to make it easier
587 # to find the offending code. But first make
588 # it readable by eliminating extra white space.
590 print STDERR "\tArgs: " . $args . "\n";
594 # Remove anything inside parenthesis in the arguments so we
595 # don't get false positives when someone calls
596 # proto_tree_add_XXX(..., tvb_YYY(..., ENC_ZZZ))
597 # and allow there to be newlines inside
598 $args =~ s/\(.*\)//sg;
600 #Check for accidental usage of ENC_ parameter
601 if ($args =~ /,\s*ENC_/xos) {
602 if (!($func =~ /proto_tree_add_(time|item|bitmask|bits_item|bits_ret_val|item_ret_int|item_ret_uint|bytes_item|checksum)/xos)
604 print STDERR "Error: ".$filename." uses $func with ENC_*.\n";
607 # Print out the function args to make it easier
608 # to find the offending code. But first make
609 # it readable by eliminating extra white space.
611 print STDERR "\tArgs: " . $args . "\n";
620 # Verify that all declared ett_ variables are registered.
621 # Don't bother trying to check usage (for now)...
622 sub check_ett_registration($$)
624 my ($fileContentsRef, $filename) = @_;
625 my @ett_declarations;
626 my %ett_registrations;
627 my @unRegisteredEtts;
630 # A pattern to match ett variable names. Obviously this assumes that
631 # they start with ett_
632 my $EttVarName = qr{ (?: ett_[a-z0-9_]+ (?:\[[0-9]+\])? ) }xi;
635 my $fileContents = ${$fileContentsRef};
636 $fileContents =~ s { ^\s*\#.*$} []xogm;
638 # Find all the ett_ variables declared in the file
639 @ett_declarations = ($fileContents =~ m{
640 ^\s*static # assume declarations are on their own line
642 g?int # could be int or gint
644 ($EttVarName) # variable name
649 if (!@ett_declarations) {
650 print STDERR "Found no etts in ".$filename."\n";
654 #print "Found these etts in ".$filename.": ".join(',', @ett_declarations)."\n\n";
656 # Find the array used for registering the etts
657 # Save off the block of code containing just the variables
659 @reg_blocks = ($fileContents =~ m{
663 \s*\*\s* # it's an array of pointers
664 [a-z0-9_]+ # array name; usually (always?) "ett"
665 \s*\[\s*\]\s* # array brackets
668 ((?:\s*&\s* # address of the following variable
669 $EttVarName # variable name
670 \s*,? # the comma is optional (for the last entry)
671 \s*)+) # match one or more variable names
676 #print "Found this ett registration block in ".$filename.": ".join(',', @reg_blocks)."\n";
678 if (@reg_blocks == 0) {
679 print STDERR "Hmm, found ".@reg_blocks." ett registration blocks in ".$filename."\n";
684 while (@reg_blocks) {
685 my ($block) = @reg_blocks;
688 # Convert the list returned by the match into a hash of the
689 # form ett_variable_name -> 1. Then combine this new hash with
690 # the hash from the last registration block.
691 # (Of course) using hashes makes the lookups much faster.
692 %ett_registrations = map { $_ => 1 } ($block =~ m{
693 \s*&\s* # address of the following variable
694 ($EttVarName) # variable name
695 \s*,? # the comma is optional (for the last entry)
696 }xgios, %ett_registrations);
698 #print "Found these ett registrations in ".$filename.": ";
699 #while( my ($k, $v) = each %ett_registrations ) {
703 # Find which declared etts are not registered.
704 # XXX - using <@ett_declarations> and $_ instead of $ett_var makes this
705 # MUCH slower... Why?
706 while (@ett_declarations) {
707 my ($ett_var) = @ett_declarations;
708 shift @ett_declarations;
710 push(@unRegisteredEtts, $ett_var) if (!$ett_registrations{$ett_var});
713 if (@unRegisteredEtts) {
714 print STDERR "Error: found these unregistered ett variables in ".$filename.": ".join(',', @unRegisteredEtts)."\n";
721 # Given the file contents and a file name, check all of the hf entries for
722 # various problems (such as those checked for in proto.c).
723 sub check_hf_entries($$)
725 my ($fileContentsRef, $filename) = @_;
729 @items = (${$fileContentsRef} =~ m{
732 &\s*([A-Z0-9_\[\]-]+) # &hf
735 ("[A-Z0-9 '\./\(\)_:-]+") # name
737 (NULL|"[A-Z0-9_\.-]*") # abbrev
739 (FT_[A-Z0-9_]+) # field type
741 ([A-Z0-9x\|_]+) # display
745 ([A-Z0-9_]+) # bitmask
747 (NULL|"[A-Z0-9 '\./\(\)\?_:-]+") # blurb (NULL or a string)
752 #print "Found @items items\n";
754 ##my $errorCount_save = $errorCount;
755 my ($hf, $name, $abbrev, $ft, $display, $convert, $bitmask, $blurb) = @items;
756 shift @items; shift @items; shift @items; shift @items; shift @items; shift @items; shift @items; shift @items;
758 #print "name=$name, abbrev=$abbrev, ft=$ft, display=$display, convert=>$convert<, bitmask=$bitmask, blurb=$blurb\n";
760 if ($abbrev eq '""' || $abbrev eq "NULL") {
761 print STDERR "Error: $hf does not have an abbreviation in $filename\n";
764 if ($abbrev =~ m/\.\.+/) {
765 print STDERR "Error: the abbreviation for $hf ($abbrev) contains two or more sequential periods in $filename\n";
768 if ($name eq $abbrev) {
769 print STDERR "Error: the abbreviation for $hf ($abbrev) matches the field name ($name) in $filename\n";
772 if (lc($name) eq lc($blurb)) {
773 print STDERR "Error: the blurb for $hf ($blurb) matches the field name ($name) in $filename\n";
776 if ($name =~ m/"\s+/) {
777 print STDERR "Error: the name for $hf ($name) has leading space in $filename\n";
780 if ($name =~ m/\s+"/) {
781 print STDERR "Error: the name for $hf ($name) has trailing space in $filename\n";
784 if ($blurb =~ m/"\s+/) {
785 print STDERR "Error: the blurb for $hf ($blurb) has leading space in $filename\n";
788 if ($blurb =~ m/\s+"/) {
789 print STDERR "Error: the blurb for $hf ($blurb) has trailing space in $filename\n";
792 if ($abbrev =~ m/\s+/) {
793 print STDERR "Error: the abbreviation for $hf ($abbrev) has white space in $filename\n";
796 if ("\"".$hf ."\"" eq $name) {
797 print STDERR "Error: name is the hf_variable_name in field $name ($abbrev) in $filename\n";
800 if ("\"".$hf ."\"" eq $abbrev) {
801 print STDERR "Error: abbreviation is the hf_variable_name in field $name ($abbrev) in $filename\n";
804 if ($ft ne "FT_BOOLEAN" && $convert =~ m/^TFS\(.*\)/) {
805 print STDERR "Error: $hf uses a true/false string but is an $ft instead of FT_BOOLEAN in $filename\n";
808 if ($ft eq "FT_BOOLEAN" && $convert =~ m/^VALS\(.*\)/) {
809 print STDERR "Error: $hf uses a value_string but is an FT_BOOLEAN in $filename\n";
812 if (($ft eq "FT_BOOLEAN") && ($bitmask !~ /^(0x)?0+$/) && ($display =~ /^BASE_/)) {
813 print STDERR "Error: $hf: FT_BOOLEAN with a bitmask must specify a 'parent field width' for 'display' in $filename\n";
816 if (($ft eq "FT_BOOLEAN") && ($convert !~ m/^((0[xX]0?)?0$|NULL$|TFS)/)) {
817 print STDERR "Error: $hf: FT_BOOLEAN with non-null 'convert' field missing TFS in $filename\n";
820 if ($convert =~ m/RVALS/ && $display !~ m/BASE_RANGE_STRING/) {
821 print STDERR "Error: $hf uses RVALS but 'display' does not include BASE_RANGE_STRING in $filename\n";
824 if ($convert =~ m/^VALS\(&.*\)/) {
825 print STDERR "Error: $hf is passing the address of a pointer to VALS in $filename\n";
828 if ($convert =~ m/^RVALS\(&.*\)/) {
829 print STDERR "Error: $hf is passing the address of a pointer to RVALS in $filename\n";
832 if ($convert !~ m/^((0[xX]0?)?0$|NULL$|VALS|VALS64|VALS_EXT_PTR|RVALS|TFS|CF_FUNC|FRAMENUM_TYPE|&|STRINGS_ENTERPRISES)/ && $display !~ /BASE_CUSTOM/) {
833 print STDERR "Error: non-null $hf 'convert' field missing 'VALS|VALS64|RVALS|TFS|CF_FUNC|FRAMENUM_TYPE|&|STRINGS_ENTERPRISES' in $filename ?\n";
837 ## if (($ft eq "FT_BOOLEAN") && ($bitmask =~ /^(0x)?0+$/) && ($display ne "BASE_NONE")) {
838 ## print STDERR "Error: $abbrev: FT_BOOLEAN with no bitmask must use BASE_NONE for 'display' in $filename\n";
841 ##if ($errorCount != $errorCount_save) {
842 ## print STDERR "name=$name, abbrev=$abbrev, ft=$ft, display=$display, convert=>$convert<, bitmask=$bitmask, blurb=$blurb\n";
850 sub check_pref_var_dupes($$)
852 my ($filecontentsref, $filename) = @_;
855 # Avoid flagging the actual prototypes
856 return 0 if $filename =~ /prefs\.[ch]$/;
859 my $filecontents = ${$filecontentsref};
860 $filecontents =~ s { ^\s*\#.*$} []xogm;
862 # At what position is the variable in the prefs_register_*_preference() call?
863 my %prefs_register_var_pos = (
864 static_text => undef, obsolete => undef, # ignore
865 decode_as_range => -2, range => -2, filename => -2, # second to last
866 enum => -3, # third to last
867 # everything else is the last argument
872 while ($filecontents =~ /prefs_register_(\w+?)_preference/gs) {
873 my ($args) = extract_bracketed(substr($filecontents, $+[0]), '()');
874 $args = substr($args, 1, -1); # strip parens
876 my $pos = $prefs_register_var_pos{$1};
877 next if exists $prefs_register_var_pos{$1} and not defined $pos;
879 my $var = (split /\s*,\s*(?![^(]*\))/, $args)[$pos]; # only commas outside parens
880 push @dupes, $var if $count{$var}++ == 1;
884 print STDERR "$filename: error: found these preference variables used in more than one prefs_register_*_preference:\n\t".join(', ', @dupes)."\n";
893 print "Usage: checkAPIs.pl [-M] [-h] [-g group1[:count]] [-g group2] ... \n";
894 print " [--build] [-summary-group group1] [-summary-group group2] ... \n";
895 print " [--sourcedir=srcdir] \n";
896 print " [--nocheck-value-string-array] \n";
897 print " [--nocheck-addtext] [--nocheck-hf] [--debug]\n";
898 print " [--file=/path/to/file_list]\n";
899 print " file1 file2 ...\n";
901 print " -M: Generate output for -g in 'machine-readable' format\n";
902 print " -p: used by the git pre-commit hook\n";
903 print " -h: help, print usage message\n";
904 print " -g <group>: Check input files for use of APIs in <group>\n";
905 print " (in addition to the default groups)\n";
906 print " Maximum uses can be specified with <group>:<count>\n";
907 print " -summary-group <group>: Output summary (count) for each API in <group>\n";
908 print " (-g <group> also req'd)\n";
909 print " ---nocheck-value-string-array: UNDOCUMENTED\n";
910 print " ---nocheck-addtext: UNDOCUMENTED\n";
911 print " ---nocheck-hf: UNDOCUMENTED\n";
912 print " ---debug: UNDOCUMENTED\n";
913 print " ---build: UNDOCUMENTED\n";
915 print " Default Groups[-g]: ", join (", ", sort @apiGroups), "\n";
916 print " Available Groups: ", join (", ", sort keys %APIs), "\n";
920 # action: remove '#if 0'd code from the input string
921 # args codeRef, fileName
924 # Essentially: Use s//patsub/meg to pass each line to patsub.
925 # patsub monitors #if/#if 0/etc and determines
926 # if a particular code line should be removed.
927 # XXX: This is probably pretty inefficient;
928 # I could imagine using another approach such as converting
929 # the input string to an array of lines and then making
930 # a pass through the array deleting lines as needed.
933 my ($if_lvl, $if0_lvl, $if0); # shared vars
936 sub remove_if0_code {
937 my ($codeRef, $fileName) = @_;
939 my ($preprocRegEx) = qr {
940 ( # $1 [complete line)
944 (if \s 0| if | else | endif) # $2 (only if #...)
951 ($if_lvl, $if0_lvl, $if0) = (0,0,0);
952 $$codeRef =~ s{ $preprocRegEx }{patsub($1,$2,$fileName)}xegm;
954 ($debug == 2) && print "==> After Remove if0: code: [$fileName]\n$$codeRef\n===<\n";
959 my $fileName = @_[2];
963 (defined $_[1]) && print " >$_[1]<\n";
966 # #if/#if 0/#else/#endif processing
971 } elsif ($if eq 'if 0') {
975 $if0 = 1; # inside #if 0
977 } elsif ($if eq 'else') {
978 if ($if0_lvl == $if_lvl) {
981 } elsif ($if eq 'endif') {
982 if ($if0_lvl == $if_lvl) {
988 die "patsub: #if/#endif mismatch in $fileName"
991 return $_[0]; # don't remove preprocessor lines themselves
994 # not preprocessor line: See if under #if 0: If so, remove
1002 # The below Regexp are based on those from:
1003 # http://aspn.activestate.com/ASPN/Cookbook/Rx/Recipe/59811
1004 # They are in the public domain.
1006 # 1. A complicated regex which matches "classic C"-style comments.
1007 my $CComment = qr{ / [*] [^*]* [*]+ (?: [^/*] [^*]* [*]+ )* / }x;
1009 # 1.a A regex that matches C++/C99-and-later-style comments.
1010 # XXX handle comments after a statement and not just at the beginning of a line.
1011 my $CppComment = qr{ ^ \s* // (.*?) \n }xm;
1013 # 2. A regex which matches double-quoted strings.
1014 # ?s added so that strings containing a 'line continuation'
1015 # ( \ followed by a new-line) will match.
1016 my $DoubleQuotedStr = qr{ (?: ["] (?s: \\. | [^\"\\])* ["]) }x;
1018 # 3. A regex which matches single-quoted strings.
1019 my $SingleQuotedStr = qr{ (?: \' (?: \\. | [^\'\\])* [']) }x;
1021 # 4. Now combine 1 through 3 to produce a regex which
1022 # matches _either_ double or single quoted strings
1023 # OR comments. We surround the comment-matching
1024 # regex in capturing parenthesis to store the contents
1025 # of the comment in $1.
1026 # my $commentAndStringRegex = qr{(?:$DoubleQuotedStr|$SingleQuotedStr)|($CComment)|($CppComment)};
1033 # The default list, which can be expanded.
1034 my @apiSummaryGroups = ();
1035 my $check_value_string_array= 1; # default: enabled
1036 my $machine_readable_output = 0; # default: disabled
1037 my $check_hf = 1; # default: enabled
1038 my $check_addtext = 1; # default: enabled
1039 my $debug_flag = 0; # default: disabled
1040 my $buildbot_flag = 0;
1041 my $source_dir = "";
1042 my $filenamelist = "";
1046 my $result = GetOptions(
1047 'group=s' => \@apiGroups,
1048 'summary-group=s' => \@apiSummaryGroups,
1049 'check-value-string-array!' => \$check_value_string_array,
1050 'Machine-readable' => \$machine_readable_output,
1051 'check-hf!' => \$check_hf,
1052 'check-addtext!' => \$check_addtext,
1053 'build' => \$buildbot_flag,
1054 'sourcedir=s' => \$source_dir,
1055 'debug' => \$debug_flag,
1056 'pre-commit' => \$pre_commit,
1057 'file=s' => \$filenamelist,
1058 'help' => \$help_flag
1060 if (!$result || $help_flag) {
1065 # the pre-commit hook only calls checkAPIs one file at a time, so this
1066 # is safe to do globally (and easier)
1068 my $filename = $ARGV[0];
1069 # if the filename is packet-*.c or packet-*.h, then we set the abort and termoutput groups.
1070 if ($filename =~ /\bpacket-[^\/\\]+\.[ch]$/) {
1071 push @apiGroups, "abort";
1072 push @apiGroups, "termoutput";
1076 # Add a 'function_count' anonymous hash to each of the 'apiGroup' entries in the %APIs hash.
1077 for my $apiGroup (keys %APIs) {
1078 my @functions = @{$APIs{$apiGroup}{functions}};
1080 $APIs{$apiGroup}->{function_counts} = {};
1081 @{$APIs{$apiGroup}->{function_counts}}{@functions} = (); # Add fcn names as keys to the anonymous hash
1082 $APIs{$apiGroup}->{max_function_count} = -1;
1083 if ($APIs{$apiGroup}->{count_errors}) {
1084 $APIs{$apiGroup}->{max_function_count} = 0;
1086 $APIs{$apiGroup}->{cur_function_count} = 0;
1090 push @filelist, @ARGV;
1091 if ("$filenamelist" ne "") {
1092 # We have a file containing a list of files to check (possibly in
1093 # addition to those on the command line).
1094 open(FC, $filenamelist) || die("Couldn't open $filenamelist");
1097 # file names can be separated by ;
1098 push @filelist, split(';');
1103 die "no files to process" unless (scalar @filelist);
1105 # Read through the files; do various checks
1106 while ($_ = pop @filelist)
1109 my $fileContents = '';
1113 if ($source_dir and ! -e $filename) {
1114 $filename = $source_dir . '/' . $filename;
1116 if (! -e $filename) {
1117 warn "No such file: \"$filename\"";
1121 # delete leading './'
1122 $filename =~ s{ ^ \. / } {}xo;
1123 unless (-f $filename) {
1124 print STDERR "Warning: $filename is not of type file - skipping.\n";
1128 # Read in the file (ouch, but it's easier that way)
1129 open(FC, $filename) || die("Couldn't open $filename");
1132 $fileContents .= $_;
1133 if ($_ =~ m{ [\x80-\xFF] }xo) {
1134 print STDERR "Error: Found non-ASCII characters on line " .$line. " of " .$filename."\n";
1141 if (($fileContents =~ m{ \$Id .* \$ }xo))
1143 print STDERR "Warning: ".$filename." has an SVN Id tag. Please remove it!\n";
1146 if (($fileContents =~ m{ tab-width:\s*[0-7|9]+ | tabstop=[0-7|9]+ | tabSize=[0-7|9]+ }xo))
1148 # To quote Icf0831717de10fc615971fa1cf75af2f1ea2d03d :
1149 # HT tab stops are set every 8 spaces on UN*X; UN*X tools that treat an HT character
1150 # as tabbing to 4-space tab stops, or that even are configurable but *default* to
1151 # 4-space tab stops (I'm looking at *you*, Xcode!) are broken. tab-width: 4,
1152 # tabstop=4, and tabSize=4 are errors if you ever expect anybody to look at your file
1153 # with a UN*X tool, and every text file will probably be looked at by a UN*X tool at
1154 # some point, so Don't Do That.
1156 # Can I get an "amen!"?
1157 print STDERR "Error: Found modelines with tabstops set to something other than 8 in " .$filename."\n";
1161 # Remove all the C/C++ comments
1162 $fileContents =~ s{ $CComment | $CppComment } []xog;
1164 # optionally check the hf entries (including those under #if 0)
1166 $errorCount += check_hf_entries(\$fileContents, $filename);
1169 if ($fileContents =~ m{ __func__ }xo)
1171 print STDERR "Error: Found __func__ (which is not portable, use G_STRFUNC) in " .$filename."\n";
1174 if ($fileContents =~ m{ %ll }xo)
1176 # use G_GINT64_MODIFIER instead of ll
1177 print STDERR "Error: Found %ll in " .$filename."\n";
1180 if ($fileContents =~ m{ %hh }xo)
1182 # %hh is C99 and Windows doesn't like it:
1183 # http://connect.microsoft.com/VisualStudio/feedback/details/416843/sscanf-cannot-not-handle-hhd-format
1184 # Need to use temporary variables instead.
1185 print STDERR "Error: Found %hh in " .$filename."\n";
1189 # check for files that we should not include directly
1190 # this must be done before quoted strings (#include "file.h") are removed
1191 check_included_files(\$fileContents, $filename);
1193 # Check for value_string and enum_val_t errors: NULL termination,
1194 # const-nes, and newlines within strings
1195 if ($check_value_string_array) {
1196 $errorCount += check_value_string_arrays(\$fileContents, $filename, $debug_flag);
1199 # Remove all the quoted strings
1200 $fileContents =~ s{ $DoubleQuotedStr | $SingleQuotedStr } []xog;
1202 #$errorCount += check_ett_registration(\$fileContents, $filename);
1203 $errorCount += check_pref_var_dupes(\$fileContents, $filename);
1205 # Remove all blank lines
1206 $fileContents =~ s{ ^ \s* $ } []xog;
1208 # Remove all '#if 0'd' code
1209 remove_if0_code(\$fileContents, $filename);
1211 #checkAPIsCalledWithTvbGetPtr(\@TvbPtrAPIs, \$fileContents, \@foundAPIs);
1213 # print STDERR "Found APIs with embedded tvb_get_ptr() calls in ".$filename." : ".join(',', @foundAPIs)."\n"
1216 checkShadowVariable(\@ShadowVariable, \$fileContents, \@foundAPIs);
1218 print STDERR "Warning: Found shadow variable(s) in ".$filename." : ".join(',', @foundAPIs)."\n"
1222 check_snprintf_plus_strlen(\$fileContents, $filename);
1224 $errorCount += check_proto_tree_add_XXX(\$fileContents, $filename);
1227 # Check and count APIs
1228 for my $groupArg (@apiGroups) {
1229 my $pfx = "Warning";
1231 my @groupParts = split(/:/, $groupArg);
1232 my $apiGroup = $groupParts[0];
1233 my $curFuncCount = 0;
1235 if (scalar @groupParts > 1) {
1236 $APIs{$apiGroup}->{max_function_count} = $groupParts[1];
1239 findAPIinFile($APIs{$apiGroup}, \$fileContents, \@foundAPIs);
1241 for my $api (keys %{$APIs{$apiGroup}->{function_counts}} ) {
1242 $curFuncCount += $APIs{$apiGroup}{function_counts}{$api};
1245 # If we have a max function count and we've exceeded it, treat it
1247 if (!$APIs{$apiGroup}->{count_errors} && $APIs{$apiGroup}->{max_function_count} >= 0) {
1248 if ($curFuncCount > $APIs{$apiGroup}->{max_function_count}) {
1249 print STDERR $pfx . ": " . $apiGroup . " exceeds maximum function count: " . $APIs{$apiGroup}->{max_function_count} . "\n";
1250 $APIs{$apiGroup}->{count_errors} = 1;
1254 if ($curFuncCount <= $APIs{$apiGroup}->{max_function_count}) {
1258 if ($APIs{$apiGroup}->{count_errors}) {
1259 # the use of "prohibited" APIs is an error, increment the error count
1260 $errorCount += @foundAPIs;
1264 if (@foundAPIs && ! $machine_readable_output) {
1265 print STDERR $pfx . ": Found " . $apiGroup . " APIs in ".$filename.": ".join(',', @foundAPIs)."\n";
1267 if (@foundAPIs && $machine_readable_output) {
1268 for my $api (@foundAPIs) {
1269 printf STDERR "%-8.8s %-20.20s %-30.30s %-45.45s\n", $pfx, $apiGroup, $filename, $api;
1275 # Summary: Print Use Counts of each API in each requested summary group
1277 if (scalar @apiSummaryGroups > 0) {
1278 my $fileline = join(", ", @ARGV);
1279 printf "\nSummary for " . substr($fileline, 0, 65) . "…\n";
1281 for my $apiGroup (@apiSummaryGroups) {
1282 printf "\nUse counts for %s (maximum allowed total is %d)\n", $apiGroup, $APIs{$apiGroup}->{max_function_count};
1283 for my $api (sort {"\L$a" cmp "\L$b"} (keys %{$APIs{$apiGroup}->{function_counts}} )) {
1284 if ($APIs{$apiGroup}{function_counts}{$api} < 1) { next; }
1285 printf "%5d %-40.40s\n", $APIs{$apiGroup}{function_counts}{$api}, $api;
1293 # Editor modelines - http://www.wireshark.org/tools/modelines.html
1298 # indent-tabs-mode: nil
1301 # vi: set shiftwidth=8 tabstop=8 expandtab:
1302 # :indentSize=8:tabSize=8:noTabs=true: