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