2 # Wireshark - Network traffic analyzer
3 # By Gerald Combs <gerald@wireshark.org>
4 # Copyright 1998 Gerald Combs
6 # SPDX-License-Identifier: GPL-2.0-or-later
8 # Scan dissectors for calls to val_to_str() and friends,
9 # checking for appropriate format specifier strings in
12 # - more detailed format specifier checking (check letter, that there is only 1)
13 # - scan conformance (.cnf) files for ASN1 dissectors?
22 # Try to exit soon after Ctrl-C is pressed.
25 def signal_handler(sig
, frame
):
28 print('You pressed Ctrl+C - exiting')
30 signal
.signal(signal
.SIGINT
, signal_handler
)
33 # Test for whether the given file was automatically generated.
34 def isGeneratedFile(filename
):
35 # Check file exists - e.g. may have been deleted in a recent commit.
36 if not os
.path
.exists(filename
):
40 f_read
= open(os
.path
.join(filename
), 'r', encoding
="utf8")
43 # The comment to say that its generated is near the top, so give up once
44 # get a few lines down.
48 if (line
.find('Generated automatically') != -1 or
49 line
.find('Generated Automatically') != -1 or
50 line
.find('Autogenerated from') != -1 or
51 line
.find('is autogenerated') != -1 or
52 line
.find('automatically generated by Pidl') != -1 or
53 line
.find('Created by: The Qt Meta Object Compiler') != -1 or
54 line
.find('This file was generated') != -1 or
55 line
.find('This filter was automatically generated') != -1 or
56 line
.find('This file is auto generated, do not edit!') != -1 or
57 line
.find('This file is auto generated') != -1):
61 lines_tested
= lines_tested
+ 1
63 # OK, looks like a hand-written file!
69 def removeComments(code_string
):
70 code_string
= re
.sub(re
.compile(r
"/\*.*?\*/",re
.DOTALL
) ,"" ,code_string
) # C-style comment
71 code_string
= re
.sub(re
.compile(r
"//.*?\n" ) ,"" ,code_string
) # C++-style comment
75 def is_dissector_file(filename
):
76 p
= re
.compile(r
'.*(packet|file)-.*\.c')
77 return p
.match(filename
)
79 def findDissectorFilesInFolder(folder
, recursive
=False):
83 for root
, subfolders
, files
in os
.walk(folder
):
87 f
= os
.path
.join(root
, f
)
88 dissector_files
.append(f
)
90 for f
in sorted(os
.listdir(folder
)):
93 filename
= os
.path
.join(folder
, f
)
94 dissector_files
.append(filename
)
96 return [x
for x
in filter(is_dissector_file
, dissector_files
)]
103 # Check the given dissector file.
104 def checkFile(filename
, generated
):
105 global warnings_found
108 # Check file exists - e.g. may have been deleted in a recent commit.
109 if not os
.path
.exists(filename
):
110 print(filename
, 'does not exist!')
113 with
open(filename
, 'r', encoding
="utf8") as f
:
116 # Remove comments so as not to trip up RE.
117 contents
= removeComments(contents
)
119 matches
= re
.finditer(r
'(?<!try_)(?<!char_)(?<!bytes)(r?val_to_str(?:_ext|)(?:_const|))\(.*?,.*?,\s*(".*?\")\s*\)', contents
)
121 function
= m
.group(1)
122 format_string
= m
.group(2)
124 # Ignore what appears to be a macro.
125 if format_string
.find('#') != -1:
128 if function
.endswith('_const'):
129 # These ones shouldn't have a specifier - its an error if they do.
130 # TODO: I suppose it could be escaped, but haven't seen this...
131 if format_string
.find('%') != -1:
132 # This is an error as format specifier would show in app
133 print('Error:', filename
, " ", m
.group(0),
134 ' - should not have specifiers in unknown string',
135 '(GENERATED)' if generated
else '')
138 # These ones need to have a specifier, and it should be suitable for an int
139 count
= format_string
.count('%')
141 print('Warning:', filename
, " ", m
.group(0),
142 ' - should have suitable format specifier in unknown string (or use _const()?)',
143 '(GENERATED)' if generated
else '')
146 print('Warning:', filename
, " ", m
.group(0),
147 ' - has more than one specifier?',
148 '(GENERATED)' if generated
else '')
149 # TODO: check allowed specifiers (d, u, x, ?) and modifiers (0-9*) in re ?
150 if format_string
.find('%s') != -1:
151 # This is an error as this likely causes a crash
152 print('Error:', filename
, " ", m
.group(0),
153 ' - inappropriate format specifier in unknown string',
154 '(GENERATED)' if generated
else '')
159 #################################################################
162 # command-line args. Controls which dissector files should be checked.
163 # If no args given, will scan all dissectors.
164 parser
= argparse
.ArgumentParser(description
='Check calls in dissectors')
165 parser
.add_argument('--file', action
='append',
166 help='specify individual dissector file to test')
167 parser
.add_argument('--commits', action
='store',
168 help='last N commits to check')
169 parser
.add_argument('--open', action
='store_true',
170 help='check open files')
171 parser
.add_argument('--generated', action
='store_true',
172 help='check generated files')
174 args
= parser
.parse_args()
177 # Get files from wherever command-line args indicate.
180 # Add specified file(s)
182 if not os
.path
.isfile(f
) and not f
.startswith('epan'):
183 f
= os
.path
.join('epan', 'dissectors', f
)
184 if not os
.path
.isfile(f
):
185 print('Chosen file', f
, 'does not exist.')
190 # Get files affected by specified number of commits.
191 command
= ['git', 'diff', '--name-only', 'HEAD~' + args
.commits
]
192 files
= [f
.decode('utf-8')
193 for f
in subprocess
.check_output(command
).splitlines()]
194 # Will examine dissector files only
195 files
= list(filter(lambda f
: is_dissector_file(f
), files
))
198 command
= ['git', 'diff', '--name-only']
199 files
= [f
.decode('utf-8')
200 for f
in subprocess
.check_output(command
).splitlines()]
201 # Only interested in dissector files.
202 files
= list(filter(lambda f
: is_dissector_file(f
), files
))
204 command
= ['git', 'diff', '--staged', '--name-only']
205 files_staged
= [f
.decode('utf-8')
206 for f
in subprocess
.check_output(command
).splitlines()]
207 # Only interested in dissector files.
208 files_staged
= list(filter(lambda f
: is_dissector_file(f
), files_staged
))
209 for f
in files_staged
:
213 # Find all dissector files from folder.
214 files
= findDissectorFilesInFolder(os
.path
.join('epan', 'dissectors'))
215 files
+= findDissectorFilesInFolder(os
.path
.join('plugins', 'epan'), recursive
=True)
216 files
+= findDissectorFilesInFolder(os
.path
.join('epan', 'dissectors', 'asn1'), recursive
=True)
219 # If scanning a subset of files, list them here.
221 if args
.file or args
.commits
or args
.open:
223 print(' '.join(files
), '\n')
225 print('No files to check.\n')
227 print('All dissectors\n')
230 # Now check the chosen files
234 generated
= isGeneratedFile(f
)
235 if args
.generated
or not generated
:
236 checkFile(f
, generated
)
240 print(warnings_found
, 'warnings found')
242 print(errors_found
, 'errors found')