1#!/usr/bin/env perl 2 3# 4# Copyright 2006, Jeff Morriss <jeff.morriss.ws[AT]gmail.com> 5# 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. 8# 9# Usage: 10# checkAPIs.pl [-M] [-g group1] [-g group2] ... 11# [-s summary-group1] [-s summary-group2] ... 12# [--nocheck-hf] 13# [--nocheck-value-string-array] 14# [--nocheck-shadow] 15# [--debug] 16# file1 file2 ... 17# 18# Wireshark - Network traffic analyzer 19# By Gerald Combs <gerald@wireshark.org> 20# Copyright 1998 Gerald Combs 21# 22# SPDX-License-Identifier: GPL-2.0-or-later 23# 24 25use strict; 26use Encode; 27use English; 28use Getopt::Long; 29use Text::Balanced qw(extract_bracketed); 30 31my %APIs = ( 32 # API groups. 33 # Group name, e.g. 'prohibited' 34 # '<name>' => { 35 # 'count_errors' => 1, # 1 if these are errors, 0 if warnings 36 # 'functions' => [ 'f1', 'f2', ...], # Function array 37 # 'function-counts' => {'f1',0, 'f2',0, ...}, # Function Counts hash (initialized in the code) 38 # } 39 # 40 # APIs that MUST NOT be used in Wireshark 41 'prohibited' => { 'count_errors' => 1, 'functions' => [ 42 # Memory-unsafe APIs 43 # Use something that won't overwrite the end of your buffer instead 44 # of these. 45 # 46 # Microsoft provides lists of unsafe functions and their 47 # recommended replacements in "Security Development Lifecycle 48 # (SDL) Banned Function Calls" 49 # https://docs.microsoft.com/en-us/previous-versions/bb288454(v=msdn.10) 50 # and "Deprecated CRT Functions" 51 # https://docs.microsoft.com/en-us/previous-versions/ms235384(v=vs.100) 52 # 53 'atoi', # use wsutil/strtoi.h functions 54 'gets', 55 'sprintf', 56 'g_sprintf', 57 'vsprintf', 58 'g_vsprintf', 59 'strcpy', 60 'strncpy', 61 'strcat', 62 'strncat', 63 'cftime', 64 'ascftime', 65 ### non-portable APIs 66 # use glib (g_*) versions instead of these: 67 'ntohl', 68 'ntohs', 69 'htonl', 70 'htons', 71 'strdup', 72 'strndup', 73 # Windows doesn't have this; use g_ascii_strtoull() instead 74 'strtoull', 75 ### non-portable: fails on Windows Wireshark built with VC newer than VC6 76 # See https://gitlab.com/wireshark/wireshark/-/issues/6695#note_400659130 77 'g_fprintf', 78 'g_vfprintf', 79 ### non-ANSI C 80 # use memset, memcpy, memcmp instead of these: 81 'bzero', 82 'bcopy', 83 'bcmp', 84 # The MSDN page for ZeroMemory recommends SecureZeroMemory 85 # instead. 86 'ZeroMemory', 87 # use wmem_*, ep_*, or g_* functions instead of these: 88 # (One thing to be aware of is that space allocated with malloc() 89 # may not be freeable--at least on Windows--with g_free() and 90 # vice-versa.) 91 'malloc', 92 'calloc', 93 'realloc', 94 'valloc', 95 'free', 96 'cfree', 97 # Locale-unsafe APIs 98 # These may have unexpected behaviors in some locales (e.g., 99 # "I" isn't always the upper-case form of "i", and "i" isn't 100 # always the lower-case form of "I"). Use the g_ascii_* version 101 # instead. 102 'isalnum', 103 'isascii', 104 'isalpha', 105 'iscntrl', 106 'isdigit', 107 'islower', 108 'isgraph', 109 'isprint', 110 'ispunct', 111 'isspace', 112 'isupper', 113 'isxdigit', 114 'tolower', 115 'atof', 116 'strtod', 117 'strcasecmp', 118 'strncasecmp', 119 # Deprecated in glib 2.68 in favor of g_memdup2 120 # We have our local implementation for older versions 121 'g_memdup', 122 'g_strcasecmp', 123 'g_strncasecmp', 124 'g_strup', 125 'g_strdown', 126 'g_string_up', 127 'g_string_down', 128 'strerror', # use g_strerror 129 # Use the ws_* version of these: 130 # (Necessary because on Windows we use UTF8 for throughout the code 131 # so we must tweak that to UTF16 before operating on the file. Code 132 # using these functions will work unless the file/path name contains 133 # non-ASCII chars.) 134 'open', 135 'rename', 136 'mkdir', 137 'stat', 138 'unlink', 139 'remove', 140 'fopen', 141 'freopen', 142 'fstat', 143 'lseek', 144 # Misc 145 'tmpnam', # use mkstemp 146 '_snwprintf' # use StringCchPrintf 147 ] }, 148 149 ### Soft-Deprecated functions that should not be used in new code but 150 # have not been entirely removed from old code. These will become errors 151 # once they've been removed from all existing code. 152 'soft-deprecated' => { 'count_errors' => 0, 'functions' => [ 153 'tvb_length_remaining', # replaced with tvb_captured_length_remaining 154 155 # Locale-unsafe APIs 156 # These may have unexpected behaviors in some locales (e.g., 157 # "I" isn't always the upper-case form of "i", and "i" isn't 158 # always the lower-case form of "I"). Use the g_ascii_* version 159 # instead. 160 'toupper' 161 ] }, 162 163 # APIs that SHOULD NOT be used in Wireshark (any more) 164 'deprecated' => { 'count_errors' => 1, 'functions' => [ 165 'perror', # Use g_strerror() and report messages in whatever 166 # fashion is appropriate for the code in question. 167 'ctime', # Use abs_time_secs_to_str() 168 'next_tvb_add_port', # Use next_tvb_add_uint() (and a matching change 169 # of NTVB_PORT -> NTVB_UINT) 170 171 ### Deprecated GLib/GObject functions/macros 172 # (The list is based upon the GLib 2.30.2 & GObject 2.30.2 documentation; 173 # An entry may be commented out if it is currently 174 # being used in Wireshark and if the replacement functionality 175 # is not available in all the GLib versions that Wireshark 176 # currently supports. 177 # Note: Wireshark currently (Jan 2012) requires GLib 2.14 or newer. 178 # The Wireshark build currently (Jan 2012) defines G_DISABLE_DEPRECATED 179 # so use of any of the following should cause the Wireshark build to fail and 180 # therefore the tests for obsolete GLib function usage in checkAPIs should not be needed. 181 'G_ALLOC_AND_FREE', 182 'G_ALLOC_ONLY', 183 'g_allocator_free', # "use slice allocator" (avail since 2.10,2.14) 184 'g_allocator_new', # "use slice allocator" (avail since 2.10,2.14) 185 'g_async_queue_ref_unlocked', # g_async_queue_ref() (OK since 2.8) 186 'g_async_queue_unref_and_unlock', # g_async_queue_unref() (OK since 2.8) 187 'g_atomic_int_exchange_and_add', # since 2.30 188 'g_basename', 189 'g_blow_chunks', # "use slice allocator" (avail since 2.10,2.14) 190 'g_cache_value_foreach', # g_cache_key_foreach() 191 'g_chunk_free', # g_slice_free (avail since 2.10) 192 'g_chunk_new', # g_slice_new (avail since 2.10) 193 'g_chunk_new0', # g_slice_new0 (avail since 2.10) 194 'g_completion_add_items', # since 2.26 195 'g_completion_clear_items', # since 2.26 196 'g_completion_complete', # since 2.26 197 'g_completion_complete_utf8', # since 2.26 198 'g_completion_free', # since 2.26 199 'g_completion_new', # since 2.26 200 'g_completion_remove_items', # since 2.26 201 'g_completion_set_compare', # since 2.26 202 'G_CONST_RETURN', # since 2.26 203 'g_date_set_time', # g_date_set_time_t (avail since 2.10) 204 'g_dirname', 205 'g_format_size_for_display', # since 2.30: use g_format_size() 206 'G_GNUC_FUNCTION', 207 'G_GNUC_PRETTY_FUNCTION', 208 'g_hash_table_freeze', 209 'g_hash_table_thaw', 210 'G_HAVE_GINT64', 211 'g_io_channel_close', 212 'g_io_channel_read', 213 'g_io_channel_seek', 214 'g_io_channel_write', 215 'g_list_pop_allocator', # "does nothing since 2.10" 216 'g_list_push_allocator', # "does nothing since 2.10" 217 'g_main_destroy', 218 'g_main_is_running', 219 'g_main_iteration', 220 'g_main_new', 221 'g_main_pending', 222 'g_main_quit', 223 'g_main_run', 224 'g_main_set_poll_func', 225 'g_mapped_file_free', # [as of 2.22: use g_map_file_unref] 226 'g_mem_chunk_alloc', # "use slice allocator" (avail since 2.10) 227 'g_mem_chunk_alloc0', # "use slice allocator" (avail since 2.10) 228 'g_mem_chunk_clean', # "use slice allocator" (avail since 2.10) 229 'g_mem_chunk_create', # "use slice allocator" (avail since 2.10) 230 'g_mem_chunk_destroy', # "use slice allocator" (avail since 2.10) 231 'g_mem_chunk_free', # "use slice allocator" (avail since 2.10) 232 'g_mem_chunk_info', # "use slice allocator" (avail since 2.10) 233 'g_mem_chunk_new', # "use slice allocator" (avail since 2.10) 234 'g_mem_chunk_print', # "use slice allocator" (avail since 2.10) 235 'g_mem_chunk_reset', # "use slice allocator" (avail since 2.10) 236 'g_node_pop_allocator', # "does nothing since 2.10" 237 'g_node_push_allocator', # "does nothing since 2.10" 238 'g_relation_count', # since 2.26 239 'g_relation_delete', # since 2.26 240 'g_relation_destroy', # since 2.26 241 'g_relation_exists', # since 2.26 242 'g_relation_index', # since 2.26 243 'g_relation_insert', # since 2.26 244 'g_relation_new', # since 2.26 245 'g_relation_print', # since 2.26 246 'g_relation_select', # since 2.26 247 'g_scanner_add_symbol', 248 'g_scanner_remove_symbol', 249 'g_scanner_foreach_symbol', 250 'g_scanner_freeze_symbol_table', 251 'g_scanner_thaw_symbol_table', 252 'g_slist_pop_allocator', # "does nothing since 2.10" 253 'g_slist_push_allocator', # "does nothing since 2.10" 254 'g_source_get_current_time', # since 2.28: use g_source_get_time() 255 'g_strcasecmp', # 256 'g_strdown', # 257 'g_string_down', # 258 'g_string_sprintf', # use g_string_printf() instead 259 'g_string_sprintfa', # use g_string_append_printf instead 260 'g_string_up', # 261 'g_strncasecmp', # 262 'g_strup', # 263 'g_tree_traverse', 264 'g_tuples_destroy', # since 2.26 265 'g_tuples_index', # since 2.26 266 'g_unicode_canonical_decomposition', # since 2.30: use g_unichar_fully_decompose() 267 'G_UNICODE_COMBINING_MARK', # since 2.30:use G_UNICODE_SPACING_MARK 268 'g_value_set_boxed_take_ownership', # GObject 269 'g_value_set_object_take_ownership', # GObject 270 'g_value_set_param_take_ownership', # GObject 271 'g_value_set_string_take_ownership', # Gobject 272 'G_WIN32_DLLMAIN_FOR_DLL_NAME', 273 'g_win32_get_package_installation_directory', 274 'g_win32_get_package_installation_subdirectory', 275 'qVariantFromValue' 276 ] }, 277 278 'dissectors-prohibited' => { 'count_errors' => 1, 'functions' => [ 279 # APIs that make the program exit. Dissectors shouldn't call these. 280 'abort', 281 'assert', 282 'assert_perror', 283 'exit', 284 'g_assert', 285 'g_error', 286 ] }, 287 288 'dissectors-restricted' => { 'count_errors' => 0, 'functions' => [ 289 # APIs that print to the terminal. Dissectors shouldn't call these. 290 # FIXME: Explain what to use instead. 291 'printf', 292 'g_warning', 293 ] }, 294 295); 296 297my @apiGroups = qw(prohibited deprecated soft-deprecated); 298 299 300# Given a ref to a hash containing "functions" and "functions_count" entries: 301# Determine if any item of the list of APIs contained in the array referenced by "functions" 302# exists in the file. 303# For each API which appears in the file: 304# Push the API onto the provided list; 305# Add the number of times the API appears in the file to the total count 306# for the API (stored as the value of the API key in the hash referenced by "function_counts"). 307 308sub findAPIinFile($$$) 309{ 310 my ($groupHashRef, $fileContentsRef, $foundAPIsRef) = @_; 311 312 for my $api ( @{$groupHashRef->{functions}} ) 313 { 314 my $cnt = 0; 315 # Match function calls, but ignore false positives from: 316 # C++ method definition: int MyClass::open(...) 317 # Method invocation: myClass->open(...); 318 # Function declaration: int open(...); 319 # Method invocation: QString().sprintf(...) 320 while (${$fileContentsRef} =~ m/ \W (?<!::|->|\w\ ) (?<!\.) $api \W* \( /gx) 321 { 322 $cnt += 1; 323 } 324 if ($cnt > 0) { 325 push @{$foundAPIsRef}, $api; 326 $groupHashRef->{function_counts}->{$api} += 1; 327 } 328 } 329} 330 331# APIs which (generally) should not be called with an argument of tvb_get_ptr() 332my @TvbPtrAPIs = ( 333 # Use NULL for the value_ptr instead of tvb_get_ptr() (only if the 334 # given offset and length are equal) with these: 335 'proto_tree_add_bytes_format', 336 'proto_tree_add_bytes_format_value', 337 'proto_tree_add_ether', 338 # Use the tvb_* version of these: 339 # Use tvb_bytes_to_str[_punct] instead of: 340 'bytes_to_str', 341 'bytes_to_str_punct', 342 'SET_ADDRESS', 343 'SET_ADDRESS_HF', 344); 345 346sub checkAPIsCalledWithTvbGetPtr($$$) 347{ 348 my ($APIs, $fileContentsRef, $foundAPIsRef) = @_; 349 350 for my $api (@{$APIs}) { 351 my @items; 352 my $cnt = 0; 353 354 @items = (${$fileContentsRef} =~ m/ ($api [^;]* ; ) /xsg); 355 while (@items) { 356 my ($item) = @items; 357 shift @items; 358 if ($item =~ / tvb_get_ptr /xos) { 359 $cnt += 1; 360 } 361 } 362 363 if ($cnt > 0) { 364 push @{$foundAPIsRef}, $api; 365 } 366 } 367} 368 369# List of possible shadow variable (Majority coming from macOS..) 370my @ShadowVariable = ( 371 'index', 372 'time', 373 'strlen', 374 'system' 375); 376 377sub check_shadow_variable($$$) 378{ 379 my ($groupHashRef, $fileContentsRef, $foundAPIsRef) = @_; 380 381 for my $api ( @{$groupHashRef} ) 382 { 383 my $cnt = 0; 384 while (${$fileContentsRef} =~ m/ \s $api \s*+ [^\(\w] /gx) 385 { 386 $cnt += 1; 387 } 388 if ($cnt > 0) { 389 push @{$foundAPIsRef}, $api; 390 } 391 } 392} 393 394sub check_snprintf_plus_strlen($$) 395{ 396 my ($fileContentsRef, $filename) = @_; 397 my @items; 398 399 # This catches both snprintf() and g_snprint. 400 # If we need to do more APIs, we can make this function look more like 401 # checkAPIsCalledWithTvbGetPtr(). 402 @items = (${$fileContentsRef} =~ m/ (snprintf [^;]* ; ) /xsg); 403 while (@items) { 404 my ($item) = @items; 405 shift @items; 406 if ($item =~ / strlen\s*\( /xos) { 407 print STDERR "Warning: ".$filename." uses snprintf + strlen to assemble strings.\n"; 408 last; 409 } 410 } 411} 412 413#### Regex for use when searching for value-string definitions 414my $StaticRegex = qr/ static \s+ /xs; 415my $ConstRegex = qr/ const \s+ /xs; 416my $Static_andor_ConstRegex = qr/ (?: $StaticRegex $ConstRegex | $StaticRegex | $ConstRegex) /xs; 417my $ValueStringVarnameRegex = qr/ (?:value|val64|string|range|bytes)_string /xs; 418my $ValueStringRegex = qr/ $Static_andor_ConstRegex ($ValueStringVarnameRegex) \ + [^;*#]+ = [^;]+ [{] .+? [}] \s*? ; /xs; 419my $EnumValRegex = qr/ $Static_andor_ConstRegex enum_val_t \ + [^;*]+ = [^;]+ [{] .+? [}] \s*? ; /xs; 420my $NewlineStringRegex = qr/ ["] [^"]* \\n [^"]* ["] /xs; 421 422sub check_value_string_arrays($$$) 423{ 424 my ($fileContentsRef, $filename, $debug_flag) = @_; 425 my $cnt = 0; 426 # Brute force check for value_string (and string_string or range_string) arrays 427 # which are missing {0, NULL} as the final (terminating) array entry 428 429 # Assumption: definition is of form (pseudo-Regex): 430 # " (static const|static|const) (value|string|range)_string .+ = { .+ ;" 431 # (possibly over multiple lines) 432 while (${$fileContentsRef} =~ / ( $ValueStringRegex ) /xsog) { 433 # XXX_string array definition found; check if NULL terminated 434 my $vs = my $vsx = $1; 435 my $type = $2; 436 if ($debug_flag) { 437 $vsx =~ / ( .+ $ValueStringVarnameRegex [^=]+ ) = /xo; 438 printf STDERR "==> %-35.35s: %s\n", $filename, $1; 439 printf STDERR "%s\n", $vs; 440 } 441 $vs =~ s{ \s } {}xg; 442 443 # Check for expected trailer 444 my $expectedTrailer; 445 my $trailerHint; 446 if ($type eq "string_string") { 447 # XXX shouldn't we reject 0 since it is gchar*? 448 $expectedTrailer = "(NULL|0), NULL"; 449 $trailerHint = "NULL, NULL"; 450 } elsif ($type eq "range_string") { 451 $expectedTrailer = "0(x0+)?, 0(x0+)?, NULL"; 452 $trailerHint = "0, 0, NULL"; 453 } elsif ($type eq "bytes_string") { 454 # XXX shouldn't we reject 0 since it is guint8*? 455 $expectedTrailer = "(NULL|0), 0, NULL"; 456 $trailerHint = "NULL, NULL"; 457 } else { 458 $expectedTrailer = "0(x?0+)?, NULL"; 459 $trailerHint = "0, NULL"; 460 } 461 if ($vs !~ / [{] $expectedTrailer [}] ,? [}] ; $/x) { 462 $vsx =~ /( $ValueStringVarnameRegex [^=]+ ) = /xo; 463 printf STDERR "Error: %-35.35s: {%s} is required as the last %s array entry: %s\n", $filename, $trailerHint, $type, $1; 464 $cnt++; 465 } 466 467 if ($vs !~ / (static)? const $ValueStringVarnameRegex /xo) { 468 $vsx =~ /( $ValueStringVarnameRegex [^=]+ ) = /xo; 469 printf STDERR "Error: %-35.35s: Missing 'const': %s\n", $filename, $1; 470 $cnt++; 471 } 472 if ($vs =~ / $NewlineStringRegex /xo && $type ne "bytes_string") { 473 $vsx =~ /( $ValueStringVarnameRegex [^=]+ ) = /xo; 474 printf STDERR "Error: %-35.35s: XXX_string contains a newline: %s\n", $filename, $1; 475 $cnt++; 476 } 477 } 478 479 # Brute force check for enum_val_t arrays which are missing {NULL, NULL, ...} 480 # as the final (terminating) array entry 481 # For now use the same option to turn this and value_string checking on and off. 482 # (Is the option even necessary?) 483 484 # Assumption: definition is of form (pseudo-Regex): 485 # " (static const|static|const) enum_val_t .+ = { .+ ;" 486 # (possibly over multiple lines) 487 while (${$fileContentsRef} =~ / ( $EnumValRegex ) /xsog) { 488 # enum_val_t array definition found; check if NULL terminated 489 my $vs = my $vsx = $1; 490 if ($debug_flag) { 491 $vsx =~ / ( .+ enum_val_t [^=]+ ) = /xo; 492 printf STDERR "==> %-35.35s: %s\n", $filename, $1; 493 printf STDERR "%s\n", $vs; 494 } 495 $vs =~ s{ \s } {}xg; 496 # README.developer says 497 # "Don't put a comma after the last tuple of an initializer of an array" 498 # However: since this usage is present in some number of cases, we'll allow for now 499 if ($vs !~ / NULL, NULL, -?[0-9] [}] ,? [}] ; $/xo) { 500 $vsx =~ /( enum_val_t [^=]+ ) = /xo; 501 printf STDERR "Error: %-35.35s: {NULL, NULL, ...} is required as the last enum_val_t array entry: %s\n", $filename, $1; 502 $cnt++; 503 } 504 if ($vs !~ / (static)? const enum_val_t /xo) { 505 $vsx =~ /( enum_val_t [^=]+ ) = /xo; 506 printf STDERR "Error: %-35.35s: Missing 'const': %s\n", $filename, $1; 507 $cnt++; 508 } 509 if ($vs =~ / $NewlineStringRegex /xo) { 510 $vsx =~ /( (?:value|string|range)_string [^=]+ ) = /xo; 511 printf STDERR "Error: %-35.35s: enum_val_t contains a newline: %s\n", $filename, $1; 512 $cnt++; 513 } 514 } 515 516 return $cnt; 517} 518 519 520sub check_included_files($$) 521{ 522 my ($fileContentsRef, $filename) = @_; 523 my @incFiles; 524 525 @incFiles = (${$fileContentsRef} =~ m/\#include \s* ([<"].+[>"])/gox); 526 527 # only our wrapper file wsutils/wsgcrypt.h may include gcrypt.h 528 # all other files should include the wrapper 529 if ($filename !~ /wsgcrypt\.h/) { 530 foreach (@incFiles) { 531 if ( m#([<"]|/+)gcrypt\.h[>"]$# ) { 532 print STDERR "Warning: ".$filename. 533 " includes gcrypt.h directly. ". 534 "Include wsutil/wsgcrypt.h instead.\n"; 535 last; 536 } 537 } 538 } 539 540 # only our wrapper file wspcap.h may include pcap.h 541 # all other files should include the wrapper 542 if ($filename !~ /wspcap\.h/) { 543 foreach (@incFiles) { 544 if ( m#([<"]|/+)pcap\.h[>"]$# ) { 545 print STDERR "Warning: ".$filename. 546 " includes pcap.h directly. ". 547 "Include wspcap.h instead.\n"; 548 last; 549 } 550 } 551 } 552 553 # files in the ui/qt directory should include the ui class includes 554 # by using #include <> 555 # this ensures that Visual Studio picks up these files from the 556 # build directory if we're compiling with cmake 557 if ($filename =~ m#ui/qt/# ) { 558 foreach (@incFiles) { 559 if ( m#"ui_.*\.h"$# ) { 560 # strip the quotes to get the base name 561 # for the error message 562 s/\"//g; 563 564 print STDERR "$filename: ". 565 "Please use #include <$_> ". 566 "instead of #include \"$_\".\n"; 567 } 568 } 569 } 570} 571 572 573sub check_proto_tree_add_XXX($$) 574{ 575 my ($fileContentsRef, $filename) = @_; 576 my @items; 577 my $errorCount = 0; 578 579 @items = (${$fileContentsRef} =~ m/ (proto_tree_add_[_a-z0-9]+) \( ([^;]*) \) \s* ; /xsg); 580 581 while (@items) { 582 my ($func) = @items; 583 shift @items; 584 my ($args) = @items; 585 shift @items; 586 587 #Check to make sure tvb_get* isn't used to pass into a proto_tree_add_<datatype>, when 588 #proto_tree_add_item could just be used instead 589 if ($args =~ /,\s*tvb_get_/xos) { 590 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)$/) 591 ) { 592 print STDERR "Error: ".$filename." uses $func with tvb_get_*. Use proto_tree_add_item instead\n"; 593 $errorCount++; 594 595 # Print out the function args to make it easier 596 # to find the offending code. But first make 597 # it readable by eliminating extra white space. 598 $args =~ s/\s+/ /g; 599 print STDERR "\tArgs: " . $args . "\n"; 600 } 601 } 602 603 # Remove anything inside parenthesis in the arguments so we 604 # don't get false positives when someone calls 605 # proto_tree_add_XXX(..., tvb_YYY(..., ENC_ZZZ)) 606 # and allow there to be newlines inside 607 $args =~ s/\(.*\)//sg; 608 609 #Check for accidental usage of ENC_ parameter 610 if ($args =~ /,\s*ENC_/xos) { 611 if (!($func =~ /proto_tree_add_(time|item|bitmask|[a-z0-9]+_bits_format_value|bits_item|bits_ret_val|item_ret_int|item_ret_uint|bytes_item|checksum)/xos) 612 ) { 613 print STDERR "Error: ".$filename." uses $func with ENC_*.\n"; 614 $errorCount++; 615 616 # Print out the function args to make it easier 617 # to find the offending code. But first make 618 # it readable by eliminating extra white space. 619 $args =~ s/\s+/ /g; 620 print STDERR "\tArgs: " . $args . "\n"; 621 } 622 } 623 } 624 625 return $errorCount; 626} 627 628 629# Verify that all declared ett_ variables are registered. 630# Don't bother trying to check usage (for now)... 631sub check_ett_registration($$) 632{ 633 my ($fileContentsRef, $filename) = @_; 634 my @ett_declarations; 635 my @ett_address_uses; 636 my %ett_uses; 637 my @unUsedEtts; 638 my $errorCount = 0; 639 640 # A pattern to match ett variable names. Obviously this assumes that 641 # they start with `ett_` 642 my $EttVarName = qr{ (?: ett_[a-z0-9_]+ (?:\[[0-9]+\])? ) }xi; 643 644 # Find all the ett_ variables declared in the file 645 @ett_declarations = (${$fileContentsRef} =~ m{ 646 ^ # assume declarations are on their own line 647 (?:static\s+)? # some declarations aren't static 648 g?int # could be int or gint 649 \s+ 650 ($EttVarName) # variable name 651 \s*=\s* 652 -1\s*; 653 }xgiom); 654 655 if (!@ett_declarations) { 656 # Only complain if the file looks like a dissector 657 #print STDERR "Found no etts in ".$filename."\n" if 658 # (${$fileContentsRef} =~ m{proto_register_field_array}os); 659 return; 660 } 661 #print "Found these etts in ".$filename.": ".join(' ', @ett_declarations)."\n\n"; 662 663 # Find all the uses of the *addresses* of ett variables in the file. 664 # (We assume if someone is using the address they're using it to 665 # register the ett.) 666 @ett_address_uses = (${$fileContentsRef} =~ m{ 667 &\s*($EttVarName) 668 }xgiom); 669 670 if (!@ett_address_uses) { 671 print STDERR "Found no ett address uses in ".$filename."\n"; 672 # Don't treat this as an error. 673 # It's more likely a problem with checkAPIs. 674 return; 675 } 676 #print "Found these etts addresses used in ".$filename.": ".join(' ', @ett_address_uses)."\n\n"; 677 678 # Convert to a hash for fast lookup 679 $ett_uses{$_}++ for (@ett_address_uses); 680 681 # Find which declared etts are not used. 682 while (@ett_declarations) { 683 my ($ett_var) = @ett_declarations; 684 shift @ett_declarations; 685 686 push(@unUsedEtts, $ett_var) if (not exists $ett_uses{$ett_var}); 687 } 688 689 if (@unUsedEtts) { 690 print STDERR "Error: found these unused ett variables in ".$filename.": ".join(' ', @unUsedEtts)."\n"; 691 $errorCount++; 692 } 693 694 return $errorCount; 695} 696 697# Given the file contents and a file name, check all of the hf entries for 698# various problems (such as those checked for in proto.c). 699sub check_hf_entries($$) 700{ 701 my ($fileContentsRef, $filename) = @_; 702 my $errorCount = 0; 703 704 my @items; 705 my $hfRegex = qr{ 706 \{ 707 \s* 708 &\s*([A-Z0-9_\[\]-]+) # &hf 709 \s*,\s* 710 }xis; 711 if (${$fileContentsRef} =~ /^#define\s+NEW_PROTO_TREE_API/m) { 712 $hfRegex = qr{ 713 \sheader_field_info\s+ 714 ([A-Z0-9_]+) 715 \s+ 716 [A-Z0-9_]* 717 \s*=\s* 718 }xis; 719 } 720 @items = (${$fileContentsRef} =~ m{ 721 $hfRegex # &hf or "new" hfi name 722 \{\s* 723 ("[A-Z0-9 '\./\(\)_:-]+") # name 724 \s*,\s* 725 (NULL|"[A-Z0-9_\.-]*") # abbrev 726 \s*,\s* 727 (FT_[A-Z0-9_]+) # field type 728 \s*,\s* 729 ([A-Z0-9x\|_\s]+) # display 730 \s*,\s* 731 ([^,]+?) # convert 732 \s*,\s* 733 ([A-Z0-9_]+) # bitmask 734 \s*,\s* 735 (NULL|"[A-Z0-9 '\./\(\)\?_:-]+") # blurb (NULL or a string) 736 \s*,\s* 737 HFILL # HFILL 738 }xgios); 739 740 #print "Found @items items\n"; 741 while (@items) { 742 ##my $errorCount_save = $errorCount; 743 my ($hf, $name, $abbrev, $ft, $display, $convert, $bitmask, $blurb) = @items; 744 shift @items; shift @items; shift @items; shift @items; shift @items; shift @items; shift @items; shift @items; 745 746 $display =~ s/\s+//g; 747 $convert =~ s/\s+//g; 748 # GET_VALS_EXTP is a macro in packet-mq.h for packet-mq.c and packet-mq-pcf.c 749 $convert =~ s/\bGET_VALS_EXTP\(/VALS_EXT_PTR\(/; 750 751 #print "name=$name, abbrev=$abbrev, ft=$ft, display=$display, convert=>$convert<, bitmask=$bitmask, blurb=$blurb\n"; 752 753 if ($abbrev eq '""' || $abbrev eq "NULL") { 754 print STDERR "Error: $hf does not have an abbreviation in $filename\n"; 755 $errorCount++; 756 } 757 if ($abbrev =~ m/\.\.+/) { 758 print STDERR "Error: the abbreviation for $hf ($abbrev) contains two or more sequential periods in $filename\n"; 759 $errorCount++; 760 } 761 if ($name eq $abbrev) { 762 print STDERR "Error: the abbreviation for $hf ($abbrev) matches the field name ($name) in $filename\n"; 763 $errorCount++; 764 } 765 if (lc($name) eq lc($blurb)) { 766 print STDERR "Error: the blurb for $hf ($blurb) matches the field name ($name) in $filename\n"; 767 $errorCount++; 768 } 769 if ($name =~ m/"\s+/) { 770 print STDERR "Error: the name for $hf ($name) has leading space in $filename\n"; 771 $errorCount++; 772 } 773 if ($name =~ m/\s+"/) { 774 print STDERR "Error: the name for $hf ($name) has trailing space in $filename\n"; 775 $errorCount++; 776 } 777 if ($blurb =~ m/"\s+/) { 778 print STDERR "Error: the blurb for $hf ($blurb) has leading space in $filename\n"; 779 $errorCount++; 780 } 781 if ($blurb =~ m/\s+"/) { 782 print STDERR "Error: the blurb for $hf ($blurb) has trailing space in $filename\n"; 783 $errorCount++; 784 } 785 if ($abbrev =~ m/\s+/) { 786 print STDERR "Error: the abbreviation for $hf ($abbrev) has white space in $filename\n"; 787 $errorCount++; 788 } 789 if ("\"".$hf ."\"" eq $name) { 790 print STDERR "Error: name is the hf_variable_name in field $name ($abbrev) in $filename\n"; 791 $errorCount++; 792 } 793 if ("\"".$hf ."\"" eq $abbrev) { 794 print STDERR "Error: abbreviation is the hf_variable_name in field $name ($abbrev) in $filename\n"; 795 $errorCount++; 796 } 797 if ($ft ne "FT_BOOLEAN" && $convert =~ m/^TFS\(.*\)/) { 798 print STDERR "Error: $hf uses a true/false string but is an $ft instead of FT_BOOLEAN in $filename\n"; 799 $errorCount++; 800 } 801 if ($ft eq "FT_BOOLEAN" && $convert =~ m/^VALS\(.*\)/) { 802 print STDERR "Error: $hf uses a value_string but is an FT_BOOLEAN in $filename\n"; 803 $errorCount++; 804 } 805 if (($ft eq "FT_BOOLEAN") && ($bitmask !~ /^(0x)?0+$/) && ($display =~ /^BASE_/)) { 806 print STDERR "Error: $hf: FT_BOOLEAN with a bitmask must specify a 'parent field width' for 'display' in $filename\n"; 807 $errorCount++; 808 } 809 if (($ft eq "FT_BOOLEAN") && ($convert !~ m/^((0[xX]0?)?0$|NULL$|TFS)/)) { 810 print STDERR "Error: $hf: FT_BOOLEAN with non-null 'convert' field missing TFS in $filename\n"; 811 $errorCount++; 812 } 813 if ($convert =~ m/RVALS/ && $display !~ m/BASE_RANGE_STRING/) { 814 print STDERR "Error: $hf uses RVALS but 'display' does not include BASE_RANGE_STRING in $filename\n"; 815 $errorCount++; 816 } 817 if ($convert =~ m/VALS64/ && $display !~ m/BASE_VAL64_STRING/) { 818 print STDERR "Error: $hf uses VALS64 but 'display' does not include BASE_VAL64_STRING in $filename\n"; 819 $errorCount++; 820 } 821 if ($display =~ /BASE_EXT_STRING/ && $convert !~ /^(VALS_EXT_PTR\(|&)/) { 822 print STDERR "Error: $hf: BASE_EXT_STRING should use VALS_EXT_PTR for 'strings' instead of '$convert' in $filename\n"; 823 $errorCount++; 824 } 825 if ($ft =~ m/^FT_U?INT(8|16|24|32)$/ && $convert =~ m/^VALS64\(/) { 826 print STDERR "Error: $hf: 32-bit field must use VALS instead of VALS64 in $filename\n"; 827 $errorCount++; 828 } 829 if ($ft =~ m/^FT_U?INT(40|48|56|64)$/ && $convert =~ m/^VALS\(/) { 830 print STDERR "Error: $hf: 64-bit field must use VALS64 instead of VALS in $filename\n"; 831 $errorCount++; 832 } 833 if ($convert =~ m/^(VALS|VALS64|RVALS)\(&.*\)/) { 834 print STDERR "Error: $hf is passing the address of a pointer to $1 in $filename\n"; 835 $errorCount++; 836 } 837 if ($convert !~ m/^((0[xX]0?)?0$|NULL$|VALS|VALS64|VALS_EXT_PTR|RVALS|TFS|CF_FUNC|FRAMENUM_TYPE|&|STRINGS_ENTERPRISES)/ && $display !~ /BASE_CUSTOM/) { 838 print STDERR "Error: non-null $hf 'convert' field missing 'VALS|VALS64|RVALS|TFS|CF_FUNC|FRAMENUM_TYPE|&|STRINGS_ENTERPRISES' in $filename ?\n"; 839 $errorCount++; 840 } 841## Benign... 842## if (($ft eq "FT_BOOLEAN") && ($bitmask =~ /^(0x)?0+$/) && ($display ne "BASE_NONE")) { 843## print STDERR "Error: $abbrev: FT_BOOLEAN with no bitmask must use BASE_NONE for 'display' in $filename\n"; 844## $errorCount++; 845## } 846 ##if ($errorCount != $errorCount_save) { 847 ## print STDERR "name=$name, abbrev=$abbrev, ft=$ft, display=$display, convert=>$convert<, bitmask=$bitmask, blurb=$blurb\n"; 848 ##} 849 850 } 851 852 return $errorCount; 853} 854 855sub check_pref_var_dupes($$) 856{ 857 my ($filecontentsref, $filename) = @_; 858 my $errorcount = 0; 859 860 # Avoid flagging the actual prototypes 861 return 0 if $filename =~ /prefs\.[ch]$/; 862 863 # remove macro lines 864 my $filecontents = ${$filecontentsref}; 865 $filecontents =~ s { ^\s*\#.*$} []xogm; 866 867 # At what position is the variable in the prefs_register_*_preference() call? 868 my %prefs_register_var_pos = ( 869 static_text => undef, obsolete => undef, # ignore 870 decode_as_range => -2, range => -2, filename => -2, # second to last 871 enum => -3, # third to last 872 # everything else is the last argument 873 ); 874 875 my @dupes; 876 my %count; 877 while ($filecontents =~ /prefs_register_(\w+?)_preference/gs) { 878 my ($args) = extract_bracketed(substr($filecontents, $+[0]), '()'); 879 $args = substr($args, 1, -1); # strip parens 880 881 my $pos = $prefs_register_var_pos{$1}; 882 next if exists $prefs_register_var_pos{$1} and not defined $pos; 883 $pos //= -1; 884 my $var = (split /\s*,\s*(?![^(]*\))/, $args)[$pos]; # only commas outside parens 885 push @dupes, $var if $count{$var}++ == 1; 886 } 887 888 if (@dupes) { 889 print STDERR "$filename: error: found these preference variables used in more than one prefs_register_*_preference:\n\t".join(', ', @dupes)."\n"; 890 $errorcount++; 891 } 892 893 return $errorcount; 894} 895 896# Check for forbidden control flow changes, see epan/exceptions.h 897sub check_try_catch($$) 898{ 899 my ($fileContentsRef, $filename) = @_; 900 my $errorCount = 0; 901 902 # Match TRY { ... } ENDTRY (with an optional '\' in case of a macro). 903 my @items = (${$fileContentsRef} =~ m/ \bTRY\s*\{ (.+?) \}\s* \\? \s*ENDTRY\b /xsg); 904 for my $block (@items) { 905 if ($block =~ m/ \breturn\b /x) { 906 print STDERR "Error: return is forbidden in TRY/CATCH in $filename\n"; 907 $errorCount++; 908 } 909 910 my @gotoLabels = $block =~ m/ \bgoto\s+ (\w+) /xsg; 911 my %seen = (); 912 for my $gotoLabel (@gotoLabels) { 913 if ($seen{$gotoLabel}) { 914 next; 915 } 916 $seen{$gotoLabel} = 1; 917 918 if ($block !~ /^ \s* $gotoLabel \s* :/xsgm) { 919 print STDERR "Error: goto to label '$gotoLabel' outside TRY/CATCH is forbidden in $filename\n"; 920 $errorCount++; 921 } 922 } 923 } 924 925 return $errorCount; 926} 927 928sub print_usage 929{ 930 print "Usage: checkAPIs.pl [-M] [-h] [-g group1[:count]] [-g group2] ... \n"; 931 print " [-summary-group group1] [-summary-group group2] ... \n"; 932 print " [--sourcedir=srcdir] \n"; 933 print " [--nocheck-hf]\n"; 934 print " [--nocheck-value-string-array] \n"; 935 print " [--nocheck-shadow]\n"; 936 print " [--debug]\n"; 937 print " [--file=/path/to/file_list]\n"; 938 print " file1 file2 ...\n"; 939 print "\n"; 940 print " -M: Generate output for -g in 'machine-readable' format\n"; 941 print " -p: used by the git pre-commit hook\n"; 942 print " -h: help, print usage message\n"; 943 print " -g <group>: Check input files for use of APIs in <group>\n"; 944 print " (in addition to the default groups)\n"; 945 print " Maximum uses can be specified with <group>:<count>\n"; 946 print " -summary-group <group>: Output summary (count) for each API in <group>\n"; 947 print " (-g <group> also req'd)\n"; 948 print " --nocheck-hf: Skip header field definition checks\n"; 949 print " --nocheck-value-string-array: Skip value string array checks\n"; 950 print " --nocheck-shadow: Skip shadow variable checks\n"; 951 print " --debug: UNDOCUMENTED\n"; 952 print "\n"; 953 print " Default Groups[-g]: ", join (", ", sort @apiGroups), "\n"; 954 print " Available Groups: ", join (", ", sort keys %APIs), "\n"; 955} 956 957# ------------- 958# action: remove '#if 0'd code from the input string 959# args codeRef, fileName 960# returns: codeRef 961# 962# Essentially: split the input into blocks of code or lines of #if/#if 0/etc. 963# Remove blocks that follow '#if 0' until '#else/#endif' is found. 964 965{ # block begin 966my $debug = 0; 967 968 sub remove_if0_code { 969 my ($codeRef, $fileName) = @_; 970 971 # Preprocess output (ensure trailing LF and no leading WS before '#') 972 $$codeRef =~ s/^\s*#/#/m; 973 if ($$codeRef !~ /\n$/) { $$codeRef .= "\n"; } 974 975 # Split into blocks of normal code or lines with conditionals. 976 my $ifRegExp = qr/if 0|if|else|endif/; 977 my @blocks = split(/^(#\s*(?:$ifRegExp).*\n)/m, $$codeRef); 978 979 my ($if_lvl, $if0_lvl, $if0) = (0,0,0); 980 my $lines = ''; 981 for my $block (@blocks) { 982 my $if; 983 if ($block =~ /^#\s*($ifRegExp)/) { 984 # #if/#if 0/#else/#endif processing 985 $if = $1; 986 if ($debug == 99) { 987 print(STDERR "if0=$if0 if0_lvl=$if0_lvl lvl=$if_lvl [$if] - $block"); 988 } 989 if ($if eq 'if') { 990 $if_lvl += 1; 991 } elsif ($if eq 'if 0') { 992 $if_lvl += 1; 993 if ($if0_lvl == 0) { 994 $if0_lvl = $if_lvl; 995 $if0 = 1; # inside #if 0 996 } 997 } elsif ($if eq 'else') { 998 if ($if0_lvl == $if_lvl) { 999 $if0 = 0; 1000 } 1001 } elsif ($if eq 'endif') { 1002 if ($if0_lvl == $if_lvl) { 1003 $if0 = 0; 1004 $if0_lvl = 0; 1005 } 1006 $if_lvl -= 1; 1007 if ($if_lvl < 0) { 1008 die "patsub: #if/#endif mismatch in $fileName" 1009 } 1010 } 1011 } 1012 1013 if ($debug == 99) { 1014 print(STDERR "if0=$if0 if0_lvl=$if0_lvl lvl=$if_lvl\n"); 1015 } 1016 # Keep preprocessor lines and blocks that are not enclosed in #if 0 1017 if ($if or $if0 != 1) { 1018 $lines .= $block; 1019 } 1020 } 1021 $$codeRef = $lines; 1022 1023 ($debug == 2) && print "==> After Remove if0: code: [$fileName]\n$$codeRef\n===<\n"; 1024 return $codeRef; 1025 } 1026} # block end 1027 1028# The below Regexp are based on those from: 1029# https://web.archive.org/web/20080614012925/http://aspn.activestate.com/ASPN/Cookbook/Rx/Recipe/59811 1030# They are in the public domain. 1031 1032# 2. A regex which matches double-quoted strings. 1033# ?s added so that strings containing a 'line continuation' 1034# ( \ followed by a new-line) will match. 1035my $DoubleQuotedStr = qr{ (?: ["] (?s: \\. | [^\"\\])* ["]) }x; 1036 1037# 3. A regex which matches single-quoted strings. 1038my $SingleQuotedStr = qr{ (?: \' (?: \\. | [^\'\\])* [']) }x; 1039 1040# 1041# MAIN 1042# 1043my $errorCount = 0; 1044 1045# The default list, which can be expanded. 1046my @apiSummaryGroups = (); 1047my $machine_readable_output = 0; # default: disabled 1048my $check_hf = 1; # default: enabled 1049my $check_value_string_array= 1; # default: enabled 1050my $check_shadow = 1; # default: enabled 1051my $debug_flag = 0; # default: disabled 1052my $source_dir = ""; 1053my $filenamelist = ""; 1054my $help_flag = 0; 1055my $pre_commit = 0; 1056 1057my $result = GetOptions( 1058 'group=s' => \@apiGroups, 1059 'summary-group=s' => \@apiSummaryGroups, 1060 'Machine-readable' => \$machine_readable_output, 1061 'check-hf!' => \$check_hf, 1062 'check-value-string-array!' => \$check_value_string_array, 1063 'check-shadow!' => \$check_shadow, 1064 'sourcedir=s' => \$source_dir, 1065 'debug' => \$debug_flag, 1066 'pre-commit' => \$pre_commit, 1067 'file=s' => \$filenamelist, 1068 'help' => \$help_flag 1069 ); 1070if (!$result || $help_flag) { 1071 print_usage(); 1072 exit(1); 1073} 1074 1075# the pre-commit hook only calls checkAPIs one file at a time, so this 1076# is safe to do globally (and easier) 1077if ($pre_commit) { 1078 my $filename = $ARGV[0]; 1079 # if the filename is packet-*.c or packet-*.h, then we set the abort and termoutput groups. 1080 if ($filename =~ /\bpacket-[^\/\\]+\.[ch]$/) { 1081 push @apiGroups, "abort"; 1082 push @apiGroups, "termoutput"; 1083 } 1084} 1085 1086# Add a 'function_count' anonymous hash to each of the 'apiGroup' entries in the %APIs hash. 1087for my $apiGroup (keys %APIs) { 1088 my @functions = @{$APIs{$apiGroup}{functions}}; 1089 1090 $APIs{$apiGroup}->{function_counts} = {}; 1091 @{$APIs{$apiGroup}->{function_counts}}{@functions} = (); # Add fcn names as keys to the anonymous hash 1092 $APIs{$apiGroup}->{max_function_count} = -1; 1093 if ($APIs{$apiGroup}->{count_errors}) { 1094 $APIs{$apiGroup}->{max_function_count} = 0; 1095 } 1096 $APIs{$apiGroup}->{cur_function_count} = 0; 1097} 1098 1099my @filelist; 1100push @filelist, @ARGV; 1101if ("$filenamelist" ne "") { 1102 # We have a file containing a list of files to check (possibly in 1103 # addition to those on the command line). 1104 open(FC, $filenamelist) || die("Couldn't open $filenamelist"); 1105 1106 while (<FC>) { 1107 # file names can be separated by ; 1108 push @filelist, split(';'); 1109 } 1110 close(FC); 1111} 1112 1113die "no files to process" unless (scalar @filelist); 1114 1115# Read through the files; do various checks 1116while ($_ = pop @filelist) 1117{ 1118 my $filename = $_; 1119 my $fileContents = ''; 1120 my @foundAPIs = (); 1121 my $line; 1122 1123 if ($source_dir and ! -e $filename) { 1124 $filename = $source_dir . '/' . $filename; 1125 } 1126 if (! -e $filename) { 1127 warn "No such file: \"$filename\""; 1128 next; 1129 } 1130 1131 # delete leading './' 1132 $filename =~ s{ ^ \. / } {}xo; 1133 unless (-f $filename) { 1134 print STDERR "Warning: $filename is not of type file - skipping.\n"; 1135 next; 1136 } 1137 1138 # Read in the file (ouch, but it's easier that way) 1139 open(FC, $filename) || die("Couldn't open $filename"); 1140 $line = 1; 1141 while (<FC>) { 1142 $fileContents .= $_; 1143 eval { decode( 'UTF-8', $_, Encode::FB_CROAK ) }; 1144 if ($EVAL_ERROR) { 1145 print STDERR "Error: Found an invalid UTF-8 sequence on line " .$line. " of " .$filename."\n"; 1146 $errorCount++; 1147 } 1148 $line++; 1149 } 1150 close(FC); 1151 1152 if (($fileContents =~ m{ \$Id .* \$ }xo)) 1153 { 1154 print STDERR "Warning: ".$filename." has an SVN Id tag. Please remove it!\n"; 1155 } 1156 1157 if (($fileContents =~ m{ tab-width:\s*[0-7|9]+ | tabstop=[0-7|9]+ | tabSize=[0-7|9]+ }xo)) 1158 { 1159 # To quote Icf0831717de10fc615971fa1cf75af2f1ea2d03d : 1160 # HT tab stops are set every 8 spaces on UN*X; UN*X tools that treat an HT character 1161 # as tabbing to 4-space tab stops, or that even are configurable but *default* to 1162 # 4-space tab stops (I'm looking at *you*, Xcode!) are broken. tab-width: 4, 1163 # tabstop=4, and tabSize=4 are errors if you ever expect anybody to look at your file 1164 # with a UN*X tool, and every text file will probably be looked at by a UN*X tool at 1165 # some point, so Don't Do That. 1166 # 1167 # Can I get an "amen!"? 1168 print STDERR "Error: Found modelines with tabstops set to something other than 8 in " .$filename."\n"; 1169 $errorCount++; 1170 } 1171 1172 # Remove C/C++ comments 1173 # The below pattern is modified (to keep newlines at the end of C++-style comments) from that at: 1174 # https://perldoc.perl.org/perlfaq6.html#How-do-I-use-a-regular-expression-to-strip-C-style-comments-from-a-file? 1175 $fileContents =~ s#/\*[^*]*\*+([^/*][^*]*\*+)*/|//([^\\]|[^\n][\n]?)*?\n|("(\\.|[^"\\])*"|'(\\.|[^'\\])*'|.[^/"'\\]*)#defined $3 ? $3 : "\n"#gse; 1176 1177 # optionally check the hf entries (including those under #if 0) 1178 if ($check_hf) { 1179 $errorCount += check_hf_entries(\$fileContents, $filename); 1180 } 1181 1182 if ($fileContents =~ m{ %ll }xo) 1183 { 1184 # use G_GINT64_MODIFIER instead of ll 1185 print STDERR "Error: Found %ll in " .$filename."\n"; 1186 $errorCount++; 1187 } 1188 if ($fileContents =~ m{ %hh }xo) 1189 { 1190 # %hh is C99 and Windows doesn't like it: 1191 # http://connect.microsoft.com/VisualStudio/feedback/details/416843/sscanf-cannot-not-handle-hhd-format 1192 # Need to use temporary variables instead. 1193 print STDERR "Error: Found %hh in " .$filename."\n"; 1194 $errorCount++; 1195 } 1196 1197 # check for files that we should not include directly 1198 # this must be done before quoted strings (#include "file.h") are removed 1199 check_included_files(\$fileContents, $filename); 1200 1201 # Check for value_string and enum_val_t errors: NULL termination, 1202 # const-nes, and newlines within strings 1203 if ($check_value_string_array) { 1204 $errorCount += check_value_string_arrays(\$fileContents, $filename, $debug_flag); 1205 } 1206 1207 # Remove all the quoted strings 1208 $fileContents =~ s{ $DoubleQuotedStr | $SingleQuotedStr } []xog; 1209 1210 $errorCount += check_pref_var_dupes(\$fileContents, $filename); 1211 1212 # Remove all blank lines 1213 $fileContents =~ s{ ^ \s* $ } []xog; 1214 1215 # Remove all '#if 0'd' code 1216 remove_if0_code(\$fileContents, $filename); 1217 1218 $errorCount += check_ett_registration(\$fileContents, $filename); 1219 1220 #checkAPIsCalledWithTvbGetPtr(\@TvbPtrAPIs, \$fileContents, \@foundAPIs); 1221 #if (@foundAPIs) { 1222 # print STDERR "Found APIs with embedded tvb_get_ptr() calls in ".$filename." : ".join(',', @foundAPIs)."\n" 1223 #} 1224 1225 if ($check_shadow) { 1226 check_shadow_variable(\@ShadowVariable, \$fileContents, \@foundAPIs); 1227 if (@foundAPIs) { 1228 print STDERR "Warning: Found shadow variable(s) in ".$filename." : ".join(',', @foundAPIs)."\n" 1229 } 1230 } 1231 1232 1233 check_snprintf_plus_strlen(\$fileContents, $filename); 1234 1235 $errorCount += check_proto_tree_add_XXX(\$fileContents, $filename); 1236 1237 $errorCount += check_try_catch(\$fileContents, $filename); 1238 1239 1240 # Check and count APIs 1241 for my $groupArg (@apiGroups) { 1242 my $pfx = "Warning"; 1243 @foundAPIs = (); 1244 my @groupParts = split(/:/, $groupArg); 1245 my $apiGroup = $groupParts[0]; 1246 my $curFuncCount = 0; 1247 1248 if (scalar @groupParts > 1) { 1249 $APIs{$apiGroup}->{max_function_count} = $groupParts[1]; 1250 } 1251 1252 findAPIinFile($APIs{$apiGroup}, \$fileContents, \@foundAPIs); 1253 1254 for my $api (keys %{$APIs{$apiGroup}->{function_counts}} ) { 1255 $curFuncCount += $APIs{$apiGroup}{function_counts}{$api}; 1256 } 1257 1258 # If we have a max function count and we've exceeded it, treat it 1259 # as an error. 1260 if (!$APIs{$apiGroup}->{count_errors} && $APIs{$apiGroup}->{max_function_count} >= 0) { 1261 if ($curFuncCount > $APIs{$apiGroup}->{max_function_count}) { 1262 print STDERR $pfx . ": " . $apiGroup . " exceeds maximum function count: " . $APIs{$apiGroup}->{max_function_count} . "\n"; 1263 $APIs{$apiGroup}->{count_errors} = 1; 1264 } 1265 } 1266 1267 if ($curFuncCount <= $APIs{$apiGroup}->{max_function_count}) { 1268 next; 1269 } 1270 1271 if ($APIs{$apiGroup}->{count_errors}) { 1272 # the use of "prohibited" APIs is an error, increment the error count 1273 $errorCount += @foundAPIs; 1274 $pfx = "Error"; 1275 } 1276 1277 if (@foundAPIs && ! $machine_readable_output) { 1278 print STDERR $pfx . ": Found " . $apiGroup . " APIs in ".$filename.": ".join(',', @foundAPIs)."\n"; 1279 } 1280 if (@foundAPIs && $machine_readable_output) { 1281 for my $api (@foundAPIs) { 1282 printf STDERR "%-8.8s %-20.20s %-30.30s %-45.45s\n", $pfx, $apiGroup, $filename, $api; 1283 } 1284 } 1285 } 1286} 1287 1288# Summary: Print Use Counts of each API in each requested summary group 1289 1290if (scalar @apiSummaryGroups > 0) { 1291 my $fileline = join(", ", @ARGV); 1292 printf "\nSummary for " . substr($fileline, 0, 65) . "…\n"; 1293 1294 for my $apiGroup (@apiSummaryGroups) { 1295 printf "\nUse counts for %s (maximum allowed total is %d)\n", $apiGroup, $APIs{$apiGroup}->{max_function_count}; 1296 for my $api (sort {"\L$a" cmp "\L$b"} (keys %{$APIs{$apiGroup}->{function_counts}} )) { 1297 if ($APIs{$apiGroup}{function_counts}{$api} < 1) { next; } 1298 printf "%5d %-40.40s\n", $APIs{$apiGroup}{function_counts}{$api}, $api; 1299 } 1300 } 1301} 1302 1303exit($errorCount > 120 ? 120 : $errorCount); 1304 1305# 1306# Editor modelines - https://www.wireshark.org/tools/modelines.html 1307# 1308# Local variables: 1309# c-basic-offset: 8 1310# tab-width: 8 1311# indent-tabs-mode: nil 1312# End: 1313# 1314# vi: set shiftwidth=8 tabstop=8 expandtab: 1315# :indentSize=8:tabSize=8:noTabs=true: 1316# 1317