skinny: remove comment that matches field label (callState)
[metze/wireshark/wip.git] / tools / checkAPIs.pl
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-value-string-array]
13 #              [--nocheck-addtext] [--nocheck-hf] [--debug] file1 file2 ...
14 #
15 # Wireshark - Network traffic analyzer
16 # By Gerald Combs <gerald@wireshark.org>
17 # Copyright 1998 Gerald Combs
18 #
19 # SPDX-License-Identifier: GPL-2.0-or-later
20 #
21
22 use strict;
23 use Getopt::Long;
24 use Text::Balanced qw(extract_bracketed);
25
26 my %APIs = (
27         # API groups.
28         # Group name, e.g. 'prohibited'
29         # '<name>' => {
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)
33         # }
34         #
35         # APIs that MUST NOT be used in Wireshark
36         'prohibited' => { 'count_errors' => 1, 'functions' => [
37                 # Memory-unsafe APIs
38                 # Use something that won't overwrite the end of your buffer instead
39                 # of these.
40                 #
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
47                 #
48                 'atoi', # use wsutil/strtoi.h functions
49                 'gets',
50                 'sprintf',
51                 'g_sprintf',
52                 'vsprintf',
53                 'g_vsprintf',
54                 'strcpy',
55                 'strncpy',
56                 'strcat',
57                 'strncat',
58                 'cftime',
59                 'ascftime',
60                 ### non-portable APIs
61                 # use glib (g_*) versions instead of these:
62                 'ntohl',
63                 'ntohs',
64                 'htonl',
65                 'htons',
66                 'strdup',
67                 'strndup',
68                 # Windows doesn't have this; use g_ascii_strtoull() instead
69                 'strtoull',
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
72                 'g_fprintf',
73                 'g_vfprintf',
74                 ### non-ANSI C
75                 # use memset, memcpy, memcmp instead of these:
76                 'bzero',
77                 'bcopy',
78                 'bcmp',
79                 # The MSDN page for ZeroMemory recommends SecureZeroMemory
80                 # instead.
81                 'ZeroMemory',
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
85                 # vice-versa.)
86                 'malloc',
87                 'calloc',
88                 'realloc',
89                 'valloc',
90                 'free',
91                 'cfree',
92                 # Locale-unsafe APIs
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
96                 # instead.
97                 'isalnum',
98                 'isascii',
99                 'isalpha',
100                 'iscntrl',
101                 'isdigit',
102                 'islower',
103                 'isgraph',
104                 'isprint',
105                 'ispunct',
106                 'isspace',
107                 'isupper',
108                 'isxdigit',
109                 'tolower',
110                 'atof',
111                 'strtod',
112                 'strcasecmp',
113                 'strncasecmp',
114                 'g_strcasecmp',
115                 'g_strncasecmp',
116                 'g_strup',
117                 'g_strdown',
118                 'g_string_up',
119                 'g_string_down',
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
125                 # non-ASCII chars.)
126                 'open',
127                 'rename',
128                 'mkdir',
129                 'stat',
130                 'unlink',
131                 'remove',
132                 'fopen',
133                 'freopen',
134                 'fstat',
135                 'lseek',
136                 # Misc
137                 'tmpnam',       # use mkstemp
138                 '_snwprintf'    # use StringCchPrintf
139                 ] },
140
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
146
147                 # Locale-unsafe APIs
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
151                 # instead.
152                 'toupper'
153             ] },
154
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)
162
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.
173                 'G_ALLOC_AND_FREE',
174                 'G_ALLOC_ONLY',
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
180                 'g_basename',
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)
196                 'g_dirname',
197                 'g_format_size_for_display',                    # since 2.30: use g_format_size()
198                 'G_GNUC_FUNCTION',
199                 'G_GNUC_PRETTY_FUNCTION',
200                 'g_hash_table_freeze',
201                 'g_hash_table_thaw',
202                 'G_HAVE_GINT64',
203                 'g_io_channel_close',
204                 'g_io_channel_read',
205                 'g_io_channel_seek',
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"
209                 'g_main_destroy',
210                 'g_main_is_running',
211                 'g_main_iteration',
212                 'g_main_new',
213                 'g_main_pending',
214                 'g_main_quit',
215                 'g_main_run',
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()
247                 'g_strcasecmp',                                 #
248                 'g_strdown',                                    #
249                 'g_string_down',                                #
250                 'g_string_sprintf',                             # use g_string_printf() instead
251                 'g_string_sprintfa',                            # use g_string_append_printf instead
252                 'g_string_up',                                  #
253                 'g_strncasecmp',                                #
254                 'g_strup',                                      #
255                 'g_tree_traverse',
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',
267                 'qVariantFromValue'
268                 ] },
269
270         # APIs that make the program exit. Dissectors shouldn't call these
271         'abort' => { 'count_errors' => 1, 'functions' => [
272                 'abort',
273                 'assert',
274                 'assert_perror',
275                 'exit',
276                 'g_assert',
277                 'g_error',
278                 ] },
279
280         # APIs that print to the terminal. Dissectors shouldn't call these
281         'termoutput' => { 'count_errors' => 0, 'functions' => [
282                 'printf',
283                 'g_warning',
284                 ] },
285
286 );
287
288 my @apiGroups = qw(prohibited deprecated soft-deprecated);
289
290
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").
298
299 sub findAPIinFile($$$)
300 {
301         my ($groupHashRef, $fileContentsRef, $foundAPIsRef) = @_;
302
303         for my $api ( @{$groupHashRef->{functions}} )
304         {
305                 my $cnt = 0;
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)
312                 {
313                         $cnt += 1;
314                 }
315                 if ($cnt > 0) {
316                         push @{$foundAPIsRef}, $api;
317                         $groupHashRef->{function_counts}->{$api} += 1;
318                 }
319         }
320 }
321
322 # APIs which (generally) should not be called with an argument of tvb_get_ptr()
323 my @TvbPtrAPIs = (
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:
331         'bytes_to_str',
332         'bytes_to_str_punct',
333         'SET_ADDRESS',
334         'SET_ADDRESS_HF',
335 );
336
337 sub checkAPIsCalledWithTvbGetPtr($$$)
338 {
339         my ($APIs, $fileContentsRef, $foundAPIsRef) = @_;
340
341         for my $api (@{$APIs}) {
342                 my @items;
343                 my $cnt = 0;
344
345                 @items = (${$fileContentsRef} =~ m/ ($api [^;]* ; ) /xsg);
346                 while (@items) {
347                         my ($item) = @items;
348                         shift @items;
349                         if ($item =~ / tvb_get_ptr /xos) {
350                                 $cnt += 1;
351                         }
352                 }
353
354                 if ($cnt > 0) {
355                         push @{$foundAPIsRef}, $api;
356                 }
357         }
358 }
359
360 # List of possible shadow variable (Majority coming from macOS..)
361 my @ShadowVariable = (
362         'index',
363         'time',
364         'strlen',
365         'system'
366 );
367
368 sub checkShadowVariable($$$)
369 {
370         my ($groupHashRef, $fileContentsRef, $foundAPIsRef) = @_;
371
372         for my $api ( @{$groupHashRef} )
373         {
374                 my $cnt = 0;
375                 while (${$fileContentsRef} =~ m/ \s $api \s*+ [^\(\w] /gx)
376                 {
377                         $cnt += 1;
378                 }
379                 if ($cnt > 0) {
380                         push @{$foundAPIsRef}, $api;
381                 }
382         }
383 }
384
385 sub check_snprintf_plus_strlen($$)
386 {
387         my ($fileContentsRef, $filename) = @_;
388         my @items;
389
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);
394         while (@items) {
395                 my ($item) = @items;
396                 shift @items;
397                 if ($item =~ / strlen\s*\( /xos) {
398                         print STDERR "Warning: ".$filename." uses snprintf + strlen to assemble strings.\n";
399                         last;
400                 }
401         }
402 }
403
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;
412
413 sub check_value_string_arrays($$$)
414 {
415         my ($fileContentsRef, $filename, $debug_flag) = @_;
416         my $cnt = 0;
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
419
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;
426                 my $type = $2;
427                 if ($debug_flag) {
428                         $vsx =~ / ( .+ $ValueStringVarnameRegex [^=]+ ) = /xo;
429                         printf STDERR "==> %-35.35s: %s\n", $filename, $1;
430                         printf STDERR "%s\n", $vs;
431                 }
432                 $vs =~ s{ \s } {}xg;
433
434                 # Check for expected trailer
435                 my $expectedTrailer;
436                 my $trailerHint;
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";
448                 } else {
449                         $expectedTrailer = "0(x?0+)?, NULL";
450                         $trailerHint = "0, NULL";
451                 }
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;
455                         $cnt++;
456                 }
457
458                 if ($vs !~ / (static)? const $ValueStringVarnameRegex /xo)  {
459                         $vsx =~ /( $ValueStringVarnameRegex [^=]+ ) = /xo;
460                         printf STDERR "Error: %-35.35s: Missing 'const': %s\n", $filename, $1;
461                         $cnt++;
462                 }
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;
466                         $cnt++;
467                 }
468         }
469
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?)
474
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;
481                 if ($debug_flag) {
482                         $vsx =~ / ( .+ enum_val_t [^=]+ ) = /xo;
483                         printf STDERR "==> %-35.35s: %s\n", $filename, $1;
484                         printf STDERR "%s\n", $vs;
485                 }
486                 $vs =~ s{ \s } {}xg;
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;
493                         $cnt++;
494                 }
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;
498                         $cnt++;
499                 }
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;
503                         $cnt++;
504                 }
505         }
506
507         return $cnt;
508 }
509
510
511 sub check_included_files($$)
512 {
513         my ($fileContentsRef, $filename) = @_;
514         my @incFiles;
515
516         @incFiles = (${$fileContentsRef} =~ m/\#include \s* ([<"].+[>"])/gox);
517
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";
526                                 last;
527                         }
528                 }
529         }
530
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";
539                                 last;
540                         }
541                 }
542         }
543
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
553                                 s/\"//g;
554
555                                 print STDERR "$filename: ".
556                                         "Please use #include <$_> ".
557                                         "instead of #include \"$_\".\n";
558                         }
559                 }
560         }
561 }
562
563
564 sub check_proto_tree_add_XXX($$)
565 {
566         my ($fileContentsRef, $filename) = @_;
567         my @items;
568         my $errorCount = 0;
569
570         @items = (${$fileContentsRef} =~ m/ (proto_tree_add_[_a-z0-9]+) \( ([^;]*) \) \s* ; /xsg);
571
572         while (@items) {
573                 my ($func) = @items;
574                 shift @items;
575                 my ($args) = @items;
576                 shift @items;
577
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)$/)
582                            ) {
583                                 print STDERR "Error: ".$filename." uses $func with tvb_get_*. Use proto_tree_add_item instead\n";
584                                 $errorCount++;
585
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.
589                                 $args =~ s/\s+/ /g;
590                                 print STDERR "\tArgs: " . $args . "\n";
591                         }
592                 }
593
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;
599
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)
603                            ) {
604                                 print STDERR "Error: ".$filename." uses $func with ENC_*.\n";
605                                 $errorCount++;
606
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.
610                                 $args =~ s/\s+/ /g;
611                                 print STDERR "\tArgs: " . $args . "\n";
612                         }
613                 }
614         }
615
616         return $errorCount;
617 }
618
619
620 # Verify that all declared ett_ variables are registered.
621 # Don't bother trying to check usage (for now)...
622 sub check_ett_registration($$)
623 {
624         my ($fileContentsRef, $filename) = @_;
625         my @ett_declarations;
626         my %ett_registrations;
627         my @unRegisteredEtts;
628         my $errorCount = 0;
629
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;
633
634         # Remove macro lines
635         my $fileContents = ${$fileContentsRef};
636         $fileContents =~ s { ^\s*\#.*$} []xogm;
637
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
641                 \s+
642                 g?int                   # could be int or gint
643                 \s+
644                 ($EttVarName)           # variable name
645                 \s*=\s*
646                 -1\s*;
647         }xgiom);
648
649         if (!@ett_declarations) {
650                 print STDERR "Found no etts in ".$filename."\n";
651                 return;
652         }
653
654         #print "Found these etts in ".$filename.": ".join(',', @ett_declarations)."\n\n";
655
656         # Find the array used for registering the etts
657         # Save off the block of code containing just the variables
658         my @reg_blocks;
659         @reg_blocks = ($fileContents =~ m{
660                 static
661                 \s+
662                 g?int
663                 \s*\*\s*                # it's an array of pointers
664                 [a-z0-9_]+              # array name; usually (always?) "ett"
665                 \s*\[\s*\]\s*           # array brackets
666                 =
667                 \s*\{
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
672                 \}
673                 \s*
674                 ;
675         }xgios);
676         #print "Found this ett registration block in ".$filename.": ".join(',', @reg_blocks)."\n";
677
678         if (@reg_blocks == 0) {
679                 print STDERR "Hmm, found ".@reg_blocks." ett registration blocks in ".$filename."\n";
680                 # For now...
681                 return;
682         }
683
684         while (@reg_blocks) {
685                 my ($block) = @reg_blocks;
686                 shift @reg_blocks;
687
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);
697         }
698         #print "Found these ett registrations in ".$filename.": ";
699         #while( my ($k, $v) = each %ett_registrations ) {
700         #          print "$k\n";
701         #}
702
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;
709
710                 push(@unRegisteredEtts, $ett_var) if (!$ett_registrations{$ett_var});
711         }
712
713         if (@unRegisteredEtts) {
714                 print STDERR "Error: found these unregistered ett variables in ".$filename.": ".join(',', @unRegisteredEtts)."\n";
715                 $errorCount++;
716         }
717
718         return $errorCount;
719 }
720
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($$)
724 {
725         my ($fileContentsRef, $filename) = @_;
726         my $errorCount = 0;
727
728         my @items;
729         @items = (${$fileContentsRef} =~ m{
730                                   \{
731                                   \s*
732                                   &\s*([A-Z0-9_\[\]-]+)         # &hf
733                                   \s*,\s*
734                                   \{\s*
735                                   ("[A-Z0-9 '\./\(\)_:-]+")     # name
736                                   \s*,\s*
737                                   (NULL|"[A-Z0-9_\.-]*")        # abbrev
738                                   \s*,\s*
739                                   (FT_[A-Z0-9_]+)               # field type
740                                   \s*,\s*
741                                   ([A-Z0-9x\|_]+)               # display
742                                   \s*,\s*
743                                   ([^,]+?)                      # convert
744                                   \s*,\s*
745                                   ([A-Z0-9_]+)                  # bitmask
746                                   \s*,\s*
747                                   (NULL|"[A-Z0-9 '\./\(\)\?_:-]+")      # blurb (NULL or a string)
748                                   \s*,\s*
749                                   HFILL                         # HFILL
750         }xgios);
751
752         #print "Found @items items\n";
753         while (@items) {
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;
757
758                 #print "name=$name, abbrev=$abbrev, ft=$ft, display=$display, convert=>$convert<, bitmask=$bitmask, blurb=$blurb\n";
759
760                 if ($abbrev eq '""' || $abbrev eq "NULL") {
761                         print STDERR "Error: $hf does not have an abbreviation in $filename\n";
762                         $errorCount++;
763                 }
764                 if ($abbrev =~ m/\.\.+/) {
765                         print STDERR "Error: the abbreviation for $hf ($abbrev) contains two or more sequential periods in $filename\n";
766                         $errorCount++;
767                 }
768                 if ($name eq $abbrev) {
769                         print STDERR "Error: the abbreviation for $hf ($abbrev) matches the field name ($name) in $filename\n";
770                         $errorCount++;
771                 }
772                 if (lc($name) eq lc($blurb)) {
773                         print STDERR "Error: the blurb for $hf ($blurb) matches the field name ($name) in $filename\n";
774                         $errorCount++;
775                 }
776                 if ($name =~ m/"\s+/) {
777                         print STDERR "Error: the name for $hf ($name) has leading space in $filename\n";
778                         $errorCount++;
779                 }
780                 if ($name =~ m/\s+"/) {
781                         print STDERR "Error: the name for $hf ($name) has trailing space in $filename\n";
782                         $errorCount++;
783                 }
784                 if ($blurb =~ m/"\s+/) {
785                         print STDERR "Error: the blurb for $hf ($blurb) has leading space in $filename\n";
786                         $errorCount++;
787                 }
788                 if ($blurb =~ m/\s+"/) {
789                         print STDERR "Error: the blurb for $hf ($blurb) has trailing space in $filename\n";
790                         $errorCount++;
791                 }
792                 if ($abbrev =~ m/\s+/) {
793                         print STDERR "Error: the abbreviation for $hf ($abbrev) has white space in $filename\n";
794                         $errorCount++;
795                 }
796                 if ("\"".$hf ."\"" eq $name) {
797                         print STDERR "Error: name is the hf_variable_name in field $name ($abbrev) in $filename\n";
798                         $errorCount++;
799                 }
800                 if ("\"".$hf ."\"" eq $abbrev) {
801                         print STDERR "Error: abbreviation is the hf_variable_name in field $name ($abbrev) in $filename\n";
802                         $errorCount++;
803                 }
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";
806                         $errorCount++;
807                 }
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";
810                         $errorCount++;
811                 }
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";
814                         $errorCount++;
815                 }
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";
818                         $errorCount++;
819                 }
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";
822                         $errorCount++;
823                 }
824                 if ($convert =~ m/^VALS\(&.*\)/) {
825                         print STDERR "Error: $hf is passing the address of a pointer to VALS in $filename\n";
826                         $errorCount++;
827                 }
828                 if ($convert =~ m/^RVALS\(&.*\)/) {
829                         print STDERR "Error: $hf is passing the address of a pointer to RVALS in $filename\n";
830                         $errorCount++;
831                 }
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";
834                         $errorCount++;
835                 }
836 ## Benign...
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";
839 ##                      $errorCount++;
840 ##              }
841                 ##if ($errorCount != $errorCount_save) {
842                 ##        print STDERR "name=$name, abbrev=$abbrev, ft=$ft, display=$display, convert=>$convert<, bitmask=$bitmask, blurb=$blurb\n";
843                 ##}
844
845         }
846
847         return $errorCount;
848 }
849
850 sub check_pref_var_dupes($$)
851 {
852         my ($filecontentsref, $filename) = @_;
853         my $errorcount = 0;
854
855         # Avoid flagging the actual prototypes
856         return 0 if $filename =~ /prefs\.[ch]$/;
857
858         # remove macro lines
859         my $filecontents = ${$filecontentsref};
860         $filecontents =~ s { ^\s*\#.*$} []xogm;
861
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
868         );
869
870         my @dupes;
871         my %count;
872         while ($filecontents =~ /prefs_register_(\w+?)_preference/gs) {
873                 my ($args) = extract_bracketed(substr($filecontents, $+[0]), '()');
874                 $args = substr($args, 1, -1); # strip parens
875
876                 my $pos = $prefs_register_var_pos{$1};
877                 next if exists $prefs_register_var_pos{$1} and not defined $pos;
878                 $pos //= -1;
879                 my $var = (split /\s*,\s*(?![^(]*\))/, $args)[$pos]; # only commas outside parens
880                 push @dupes, $var if $count{$var}++ == 1;
881         }
882
883         if (@dupes) {
884                 print STDERR "$filename: error: found these preference variables used in more than one prefs_register_*_preference:\n\t".join(', ', @dupes)."\n";
885                 $errorcount++;
886         }
887
888         return $errorcount;
889 }
890
891 sub print_usage
892 {
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";
900         print "\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";
914         print "\n";
915         print "   Default Groups[-g]: ", join (", ", sort @apiGroups), "\n";
916         print "   Available Groups:   ", join (", ", sort keys %APIs), "\n";
917 }
918
919 # -------------
920 # action:  remove '#if 0'd code from the input string
921 # args     codeRef, fileName
922 # returns: codeRef
923 #
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.
931
932 {  # block begin
933 my ($if_lvl, $if0_lvl, $if0); # shared vars
934 my $debug = 0;
935
936     sub remove_if0_code {
937         my ($codeRef, $fileName)  = @_;
938
939         my ($preprocRegEx) = qr {
940                                     (                                    # $1 [complete line)
941                                         ^
942                                         (?:                              # non-capturing
943                                             \s* \# \s*
944                                             (if \s 0| if | else | endif) # $2 (only if #...)
945                                         ) ?
946                                         .*
947                                         $
948                                     )
949                             }xom;
950
951         ($if_lvl, $if0_lvl, $if0) = (0,0,0);
952         $$codeRef =~ s{ $preprocRegEx }{patsub($1,$2,$fileName)}xegm;
953
954         ($debug == 2) && print "==> After Remove if0: code: [$fileName]\n$$codeRef\n===<\n";
955         return $codeRef;
956     }
957
958     sub patsub {
959         my $fileName = @_[2];
960
961         if ($debug == 99) {
962             print "-->$_[0]\n";
963             (defined $_[1]) && print "  >$_[1]<\n";
964         }
965
966         # #if/#if 0/#else/#endif processing
967         if (defined $_[1]) {
968             my ($if) = $_[1];
969             if ($if eq 'if') {
970                 $if_lvl += 1;
971             } elsif ($if eq 'if 0') {
972                 $if_lvl += 1;
973                 if ($if0_lvl == 0) {
974                     $if0_lvl = $if_lvl;
975                     $if0     = 1;  # inside #if 0
976                 }
977             } elsif ($if eq 'else') {
978                 if ($if0_lvl == $if_lvl) {
979                     $if0 = 0;
980                 }
981             } elsif ($if eq 'endif') {
982                 if ($if0_lvl == $if_lvl) {
983                     $if0     = 0;
984                     $if0_lvl = 0;
985                 }
986                 $if_lvl -= 1;
987                 if ($if_lvl < 0) {
988                     die "patsub: #if/#endif mismatch in $fileName"
989                 }
990             }
991             return $_[0];  # don't remove preprocessor lines themselves
992         }
993
994         # not preprocessor line: See if under #if 0: If so, remove
995         if ($if0 == 1) {
996             return '';  # remove
997         }
998         return $_[0];
999     }
1000 }  # block end
1001
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.
1005
1006 # 1. A complicated regex which matches "classic C"-style comments.
1007 my $CComment = qr{ / [*] [^*]* [*]+ (?: [^/*] [^*]* [*]+ )* / }x;
1008
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;
1012
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;
1017
1018 # 3. A regex which matches single-quoted strings.
1019 my $SingleQuotedStr = qr{ (?: \' (?: \\. | [^\'\\])* [']) }x;
1020
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)};
1027
1028 #
1029 # MAIN
1030 #
1031 my $errorCount = 0;
1032
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 = "";
1043 my $help_flag = 0;
1044 my $pre_commit = 0;
1045
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
1059                         );
1060 if (!$result || $help_flag) {
1061         print_usage();
1062         exit(1);
1063 }
1064
1065 # the pre-commit hook only calls checkAPIs one file at a time, so this
1066 # is safe to do globally (and easier)
1067 if ($pre_commit) {
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";
1073     }
1074 }
1075
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}};
1079
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;
1085         }
1086         $APIs{$apiGroup}->{cur_function_count}   = 0;
1087 }
1088
1089 my @filelist;
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");
1095
1096         while (<FC>) {
1097                 # file names can be separated by ;
1098                 push @filelist, split(';');
1099         }
1100         close(FC);
1101 }
1102
1103 die "no files to process" unless (scalar @filelist);
1104
1105 # Read through the files; do various checks
1106 while ($_ = pop @filelist)
1107 {
1108         my $filename = $_;
1109         my $fileContents = '';
1110         my @foundAPIs = ();
1111         my $line;
1112
1113         if ($source_dir and ! -e $filename) {
1114                 $filename = $source_dir . '/' . $filename;
1115         }
1116         if (! -e $filename) {
1117                 warn "No such file: \"$filename\"";
1118                 next;
1119         }
1120
1121         # delete leading './'
1122         $filename =~ s{ ^ \. / } {}xo;
1123         unless (-f $filename) {
1124                 print STDERR "Warning: $filename is not of type file - skipping.\n";
1125                 next;
1126         }
1127
1128         # Read in the file (ouch, but it's easier that way)
1129         open(FC, $filename) || die("Couldn't open $filename");
1130         $line = 1;
1131         while (<FC>) {
1132                 $fileContents .= $_;
1133                 if ($_ =~ m{ [\x80-\xFF] }xo) {
1134                         print STDERR "Error: Found non-ASCII characters on line " .$line. " of " .$filename."\n";
1135                         $errorCount++;
1136                 }
1137                 $line++;
1138         }
1139         close(FC);
1140
1141         if (($fileContents =~ m{ \$Id .* \$ }xo))
1142         {
1143                 print STDERR "Warning: ".$filename." has an SVN Id tag. Please remove it!\n";
1144         }
1145
1146         if (($fileContents =~ m{ tab-width:\s*[0-7|9]+ | tabstop=[0-7|9]+ | tabSize=[0-7|9]+ }xo))
1147         {
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.
1155                 #
1156                 # Can I get an "amen!"?
1157                 print STDERR "Error: Found modelines with tabstops set to something other than 8 in " .$filename."\n";
1158                 $errorCount++;
1159         }
1160
1161         # Remove all the C/C++ comments
1162         $fileContents =~ s{ $CComment | $CppComment } []xog;
1163
1164         # optionally check the hf entries (including those under #if 0)
1165         if ($check_hf) {
1166             $errorCount += check_hf_entries(\$fileContents, $filename);
1167         }
1168
1169         if ($fileContents =~ m{ __func__ }xo)
1170         {
1171                 print STDERR "Error: Found __func__ (which is not portable, use G_STRFUNC) in " .$filename."\n";
1172                 $errorCount++;
1173         }
1174         if ($fileContents =~ m{ %ll }xo)
1175         {
1176                 # use G_GINT64_MODIFIER instead of ll
1177                 print STDERR "Error: Found %ll in " .$filename."\n";
1178                 $errorCount++;
1179         }
1180         if ($fileContents =~ m{ %hh }xo)
1181         {
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";
1186                 $errorCount++;
1187         }
1188
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);
1192
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);
1197         }
1198
1199         # Remove all the quoted strings
1200         $fileContents =~ s{ $DoubleQuotedStr | $SingleQuotedStr } []xog;
1201
1202         #$errorCount += check_ett_registration(\$fileContents, $filename);
1203         $errorCount += check_pref_var_dupes(\$fileContents, $filename);
1204
1205         # Remove all blank lines
1206         $fileContents =~ s{ ^ \s* $ } []xog;
1207
1208         # Remove all '#if 0'd' code
1209         remove_if0_code(\$fileContents, $filename);
1210
1211         #checkAPIsCalledWithTvbGetPtr(\@TvbPtrAPIs, \$fileContents, \@foundAPIs);
1212         #if (@foundAPIs) {
1213         #       print STDERR "Found APIs with embedded tvb_get_ptr() calls in ".$filename." : ".join(',', @foundAPIs)."\n"
1214         #}
1215
1216         checkShadowVariable(\@ShadowVariable, \$fileContents, \@foundAPIs);
1217         if (@foundAPIs) {
1218                print STDERR "Warning: Found shadow variable(s) in ".$filename." : ".join(',', @foundAPIs)."\n"
1219         }
1220
1221
1222         check_snprintf_plus_strlen(\$fileContents, $filename);
1223
1224         $errorCount += check_proto_tree_add_XXX(\$fileContents, $filename);
1225
1226
1227         # Check and count APIs
1228         for my $groupArg (@apiGroups) {
1229                 my $pfx = "Warning";
1230                 @foundAPIs = ();
1231                 my @groupParts = split(/:/, $groupArg);
1232                 my $apiGroup = $groupParts[0];
1233                 my $curFuncCount = 0;
1234
1235                 if (scalar @groupParts > 1) {
1236                         $APIs{$apiGroup}->{max_function_count} = $groupParts[1];
1237                 }
1238
1239                 findAPIinFile($APIs{$apiGroup}, \$fileContents, \@foundAPIs);
1240
1241                 for my $api (keys %{$APIs{$apiGroup}->{function_counts}}   ) {
1242                         $curFuncCount += $APIs{$apiGroup}{function_counts}{$api};
1243                 }
1244
1245                 # If we have a max function count and we've exceeded it, treat it
1246                 # as an error.
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;
1251                         }
1252                 }
1253
1254                 if ($curFuncCount <= $APIs{$apiGroup}->{max_function_count}) {
1255                         next;
1256                 }
1257
1258                 if ($APIs{$apiGroup}->{count_errors}) {
1259                         # the use of "prohibited" APIs is an error, increment the error count
1260                         $errorCount += @foundAPIs;
1261                         $pfx = "Error";
1262                 }
1263
1264                 if (@foundAPIs && ! $machine_readable_output) {
1265                         print STDERR $pfx . ": Found " . $apiGroup . " APIs in ".$filename.": ".join(',', @foundAPIs)."\n";
1266                 }
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;
1270                         }
1271                 }
1272         }
1273 }
1274
1275 # Summary: Print Use Counts of each API in each requested summary group
1276
1277 if (scalar @apiSummaryGroups > 0) {
1278         my $fileline = join(", ", @ARGV);
1279         printf "\nSummary for " . substr($fileline, 0, 65) . "…\n";
1280
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;
1286                 }
1287         }
1288 }
1289
1290 exit($errorCount);
1291
1292 #
1293 # Editor modelines  -  http://www.wireshark.org/tools/modelines.html
1294 #
1295 # Local variables:
1296 # c-basic-offset: 8
1297 # tab-width: 8
1298 # indent-tabs-mode: nil
1299 # End:
1300 #
1301 # vi: set shiftwidth=8 tabstop=8 expandtab:
1302 # :indentSize=8:tabSize=8:noTabs=true:
1303 #