third_party:waf: update to upstream 2.0.4 release
[bbaumbach/samba.git] / third_party / waf / waflib / extras / cppcheck.py
1 #! /usr/bin/env python
2 # encoding: utf-8
3 # WARNING! Do not edit! https://waf.io/book/index.html#_obtaining_the_waf_file
4
5 #! /usr/bin/env python
6 # -*- encoding: utf-8 -*-
7 # Michel Mooij, michel.mooij7@gmail.com
8
9 """
10 Tool Description
11 ================
12 This module provides a waf wrapper (i.e. waftool) around the C/C++ source code
13 checking tool 'cppcheck'.
14
15 See http://cppcheck.sourceforge.net/ for more information on the cppcheck tool
16 itself.
17 Note that many linux distributions already provide a ready to install version
18 of cppcheck. On fedora, for instance, it can be installed using yum:
19
20         'sudo yum install cppcheck'
21
22
23 Usage
24 =====
25 In order to use this waftool simply add it to the 'options' and 'configure'
26 functions of your main waf script as shown in the example below:
27
28         def options(opt):
29                 opt.load('cppcheck', tooldir='./waftools')
30
31         def configure(conf):
32                 conf.load('cppcheck')
33
34 Note that example shown above assumes that the cppcheck waftool is located in
35 the sub directory named 'waftools'.
36
37 When configured as shown in the example above, cppcheck will automatically
38 perform a source code analysis on all C/C++ build tasks that have been
39 defined in your waf build system.
40
41 The example shown below for a C program will be used as input for cppcheck when
42 building the task.
43
44         def build(bld):
45                 bld.program(name='foo', src='foobar.c')
46
47 The result of the source code analysis will be stored both as xml and html
48 files in the build location for the task. Should any error be detected by
49 cppcheck the build will be aborted and a link to the html report will be shown.
50 By default, one index.html file is created for each task generator. A global
51 index.html file can be obtained by setting the following variable
52 in the configuration section:
53
54         conf.env.CPPCHECK_SINGLE_HTML = False
55
56 When needed source code checking by cppcheck can be disabled per task, per
57 detected error or warning for a particular task. It can be also be disabled for
58 all tasks.
59
60 In order to exclude a task from source code checking add the skip option to the
61 task as shown below:
62
63         def build(bld):
64                 bld.program(
65                                 name='foo',
66                                 src='foobar.c'
67                                 cppcheck_skip=True
68                 )
69
70 When needed problems detected by cppcheck may be suppressed using a file
71 containing a list of suppression rules. The relative or absolute path to this
72 file can be added to the build task as shown in the example below:
73
74                 bld.program(
75                                 name='bar',
76                                 src='foobar.c',
77                                 cppcheck_suppress='bar.suppress'
78                 )
79
80 A cppcheck suppress file should contain one suppress rule per line. Each of
81 these rules will be passed as an '--suppress=<rule>' argument to cppcheck.
82
83 Dependencies
84 ================
85 This waftool depends on the python pygments module, it is used for source code
86 syntax highlighting when creating the html reports. see http://pygments.org/ for
87 more information on this package.
88
89 Remarks
90 ================
91 The generation of the html report is originally based on the cppcheck-htmlreport.py
92 script that comes shipped with the cppcheck tool.
93 """
94
95 import sys
96 import xml.etree.ElementTree as ElementTree
97 from waflib import Task, TaskGen, Logs, Context, Options
98
99 PYGMENTS_EXC_MSG= '''
100 The required module 'pygments' could not be found. Please install it using your
101 platform package manager (e.g. apt-get or yum), using 'pip' or 'easy_install',
102 see 'http://pygments.org/download/' for installation instructions.
103 '''
104
105 try:
106         import pygments
107         from pygments import formatters, lexers
108 except ImportError as e:
109         Logs.warn(PYGMENTS_EXC_MSG)
110         raise e
111
112
113 def options(opt):
114         opt.add_option('--cppcheck-skip', dest='cppcheck_skip',
115                 default=False, action='store_true',
116                 help='do not check C/C++ sources (default=False)')
117
118         opt.add_option('--cppcheck-err-resume', dest='cppcheck_err_resume',
119                 default=False, action='store_true',
120                 help='continue in case of errors (default=False)')
121
122         opt.add_option('--cppcheck-bin-enable', dest='cppcheck_bin_enable',
123                 default='warning,performance,portability,style,unusedFunction', action='store',
124                 help="cppcheck option '--enable=' for binaries (default=warning,performance,portability,style,unusedFunction)")
125
126         opt.add_option('--cppcheck-lib-enable', dest='cppcheck_lib_enable',
127                 default='warning,performance,portability,style', action='store',
128                 help="cppcheck option '--enable=' for libraries (default=warning,performance,portability,style)")
129
130         opt.add_option('--cppcheck-std-c', dest='cppcheck_std_c',
131                 default='c99', action='store',
132                 help='cppcheck standard to use when checking C (default=c99)')
133
134         opt.add_option('--cppcheck-std-cxx', dest='cppcheck_std_cxx',
135                 default='c++03', action='store',
136                 help='cppcheck standard to use when checking C++ (default=c++03)')
137
138         opt.add_option('--cppcheck-check-config', dest='cppcheck_check_config',
139                 default=False, action='store_true',
140                 help='forced check for missing buildin include files, e.g. stdio.h (default=False)')
141
142         opt.add_option('--cppcheck-max-configs', dest='cppcheck_max_configs',
143                 default='20', action='store',
144                 help='maximum preprocessor (--max-configs) define iterations (default=20)')
145
146         opt.add_option('--cppcheck-jobs', dest='cppcheck_jobs',
147                 default='1', action='store',
148                 help='number of jobs (-j) to do the checking work (default=1)')
149
150 def configure(conf):
151         if conf.options.cppcheck_skip:
152                 conf.env.CPPCHECK_SKIP = [True]
153         conf.env.CPPCHECK_STD_C = conf.options.cppcheck_std_c
154         conf.env.CPPCHECK_STD_CXX = conf.options.cppcheck_std_cxx
155         conf.env.CPPCHECK_MAX_CONFIGS = conf.options.cppcheck_max_configs
156         conf.env.CPPCHECK_BIN_ENABLE = conf.options.cppcheck_bin_enable
157         conf.env.CPPCHECK_LIB_ENABLE = conf.options.cppcheck_lib_enable
158         conf.env.CPPCHECK_JOBS = conf.options.cppcheck_jobs
159         if conf.options.cppcheck_jobs != '1' and ('unusedFunction' in conf.options.cppcheck_bin_enable or 'unusedFunction' in conf.options.cppcheck_lib_enable or 'all' in conf.options.cppcheck_bin_enable or 'all' in conf.options.cppcheck_lib_enable):
160                 Logs.warn('cppcheck: unusedFunction cannot be used with multiple threads, cppcheck will disable it automatically')
161         conf.find_program('cppcheck', var='CPPCHECK')
162
163         # set to True to get a single index.html file
164         conf.env.CPPCHECK_SINGLE_HTML = False
165
166 @TaskGen.feature('c')
167 @TaskGen.feature('cxx')
168 def cppcheck_execute(self):
169         if hasattr(self.bld, 'conf'):
170                 return
171         if len(self.env.CPPCHECK_SKIP) or Options.options.cppcheck_skip:
172                 return
173         if getattr(self, 'cppcheck_skip', False):
174                 return
175         task = self.create_task('cppcheck')
176         task.cmd = _tgen_create_cmd(self)
177         task.fatal = []
178         if not Options.options.cppcheck_err_resume:
179                 task.fatal.append('error')
180
181
182 def _tgen_create_cmd(self):
183         features = getattr(self, 'features', [])
184         std_c = self.env.CPPCHECK_STD_C
185         std_cxx = self.env.CPPCHECK_STD_CXX
186         max_configs = self.env.CPPCHECK_MAX_CONFIGS
187         bin_enable = self.env.CPPCHECK_BIN_ENABLE
188         lib_enable = self.env.CPPCHECK_LIB_ENABLE
189         jobs = self.env.CPPCHECK_JOBS
190
191         cmd  = self.env.CPPCHECK
192         args = ['--inconclusive','--report-progress','--verbose','--xml','--xml-version=2']
193         args.append('--max-configs=%s' % max_configs)
194         args.append('-j %s' % jobs)
195
196         if 'cxx' in features:
197                 args.append('--language=c++')
198                 args.append('--std=%s' % std_cxx)
199         else:
200                 args.append('--language=c')
201                 args.append('--std=%s' % std_c)
202
203         if Options.options.cppcheck_check_config:
204                 args.append('--check-config')
205
206         if set(['cprogram','cxxprogram']) & set(features):
207                 args.append('--enable=%s' % bin_enable)
208         else:
209                 args.append('--enable=%s' % lib_enable)
210
211         for src in self.to_list(getattr(self, 'source', [])):
212                 args.append('%r' % src)
213         for inc in self.to_incnodes(self.to_list(getattr(self, 'includes', []))):
214                 args.append('-I%r' % inc)
215         for inc in self.to_incnodes(self.to_list(self.env.INCLUDES)):
216                 args.append('-I%r' % inc)
217         return cmd + args
218
219
220 class cppcheck(Task.Task):
221         quiet = True
222
223         def run(self):
224                 stderr = self.generator.bld.cmd_and_log(self.cmd, quiet=Context.STDERR, output=Context.STDERR)
225                 self._save_xml_report(stderr)
226                 defects = self._get_defects(stderr)
227                 index = self._create_html_report(defects)
228                 self._errors_evaluate(defects, index)
229                 return 0
230
231         def _save_xml_report(self, s):
232                 '''use cppcheck xml result string, add the command string used to invoke cppcheck
233                 and save as xml file.
234                 '''
235                 header = '%s\n' % s.splitlines()[0]
236                 root = ElementTree.fromstring(s)
237                 cmd = ElementTree.SubElement(root.find('cppcheck'), 'cmd')
238                 cmd.text = str(self.cmd)
239                 body = ElementTree.tostring(root).decode('us-ascii')
240                 body_html_name = 'cppcheck-%s.xml' % self.generator.get_name()
241                 if self.env.CPPCHECK_SINGLE_HTML:
242                         body_html_name = 'cppcheck.xml'
243                 node = self.generator.path.get_bld().find_or_declare(body_html_name)
244                 node.write(header + body)
245
246         def _get_defects(self, xml_string):
247                 '''evaluate the xml string returned by cppcheck (on sdterr) and use it to create
248                 a list of defects.
249                 '''
250                 defects = []
251                 for error in ElementTree.fromstring(xml_string).iter('error'):
252                         defect = {}
253                         defect['id'] = error.get('id')
254                         defect['severity'] = error.get('severity')
255                         defect['msg'] = str(error.get('msg')).replace('<','&lt;')
256                         defect['verbose'] = error.get('verbose')
257                         for location in error.findall('location'):
258                                 defect['file'] = location.get('file')
259                                 defect['line'] = str(int(location.get('line')) - 1)
260                         defects.append(defect)
261                 return defects
262
263         def _create_html_report(self, defects):
264                 files, css_style_defs = self._create_html_files(defects)
265                 index = self._create_html_index(files)
266                 self._create_css_file(css_style_defs)
267                 return index
268
269         def _create_html_files(self, defects):
270                 sources = {}
271                 defects = [defect for defect in defects if 'file' in defect]
272                 for defect in defects:
273                         name = defect['file']
274                         if not name in sources:
275                                 sources[name] = [defect]
276                         else:
277                                 sources[name].append(defect)
278
279                 files = {}
280                 css_style_defs = None
281                 bpath = self.generator.path.get_bld().abspath()
282                 names = list(sources.keys())
283                 for i in range(0,len(names)):
284                         name = names[i]
285                         if self.env.CPPCHECK_SINGLE_HTML:
286                                 htmlfile = 'cppcheck/%i.html' % (i)
287                         else:
288                                 htmlfile = 'cppcheck/%s%i.html' % (self.generator.get_name(),i)
289                         errors = sources[name]
290                         files[name] = { 'htmlfile': '%s/%s' % (bpath, htmlfile), 'errors': errors }
291                         css_style_defs = self._create_html_file(name, htmlfile, errors)
292                 return files, css_style_defs
293
294         def _create_html_file(self, sourcefile, htmlfile, errors):
295                 name = self.generator.get_name()
296                 root = ElementTree.fromstring(CPPCHECK_HTML_FILE)
297                 title = root.find('head/title')
298                 title.text = 'cppcheck - report - %s' % name
299
300                 body = root.find('body')
301                 for div in body.findall('div'):
302                         if div.get('id') == 'page':
303                                 page = div
304                                 break
305                 for div in page.findall('div'):
306                         if div.get('id') == 'header':
307                                 h1 = div.find('h1')
308                                 h1.text = 'cppcheck report - %s' % name
309                         if div.get('id') == 'menu':
310                                 indexlink = div.find('a')
311                                 if self.env.CPPCHECK_SINGLE_HTML:
312                                         indexlink.attrib['href'] = 'index.html'
313                                 else:
314                                         indexlink.attrib['href'] = 'index-%s.html' % name
315                         if div.get('id') == 'content':
316                                 content = div
317                                 srcnode = self.generator.bld.root.find_node(sourcefile)
318                                 hl_lines = [e['line'] for e in errors if 'line' in e]
319                                 formatter = CppcheckHtmlFormatter(linenos=True, style='colorful', hl_lines=hl_lines, lineanchors='line')
320                                 formatter.errors = [e for e in errors if 'line' in e]
321                                 css_style_defs = formatter.get_style_defs('.highlight')
322                                 lexer = pygments.lexers.guess_lexer_for_filename(sourcefile, "")
323                                 s = pygments.highlight(srcnode.read(), lexer, formatter)
324                                 table = ElementTree.fromstring(s)
325                                 content.append(table)
326
327                 s = ElementTree.tostring(root, method='html').decode('us-ascii')
328                 s = CCPCHECK_HTML_TYPE + s
329                 node = self.generator.path.get_bld().find_or_declare(htmlfile)
330                 node.write(s)
331                 return css_style_defs
332
333         def _create_html_index(self, files):
334                 name = self.generator.get_name()
335                 root = ElementTree.fromstring(CPPCHECK_HTML_FILE)
336                 title = root.find('head/title')
337                 title.text = 'cppcheck - report - %s' % name
338
339                 body = root.find('body')
340                 for div in body.findall('div'):
341                         if div.get('id') == 'page':
342                                 page = div
343                                 break
344                 for div in page.findall('div'):
345                         if div.get('id') == 'header':
346                                 h1 = div.find('h1')
347                                 h1.text = 'cppcheck report - %s' % name
348                         if div.get('id') == 'content':
349                                 content = div
350                                 self._create_html_table(content, files)
351                         if div.get('id') == 'menu':
352                                 indexlink = div.find('a')
353                                 if self.env.CPPCHECK_SINGLE_HTML:
354                                         indexlink.attrib['href'] = 'index.html'
355                                 else:
356                                         indexlink.attrib['href'] = 'index-%s.html' % name
357
358                 s = ElementTree.tostring(root, method='html').decode('us-ascii')
359                 s = CCPCHECK_HTML_TYPE + s
360                 index_html_name = 'cppcheck/index-%s.html' % name
361                 if self.env.CPPCHECK_SINGLE_HTML:
362                         index_html_name = 'cppcheck/index.html'
363                 node = self.generator.path.get_bld().find_or_declare(index_html_name)
364                 node.write(s)
365                 return node
366
367         def _create_html_table(self, content, files):
368                 table = ElementTree.fromstring(CPPCHECK_HTML_TABLE)
369                 for name, val in files.items():
370                         f = val['htmlfile']
371                         s = '<tr><td colspan="4"><a href="%s">%s</a></td></tr>\n' % (f,name)
372                         row = ElementTree.fromstring(s)
373                         table.append(row)
374
375                         errors = sorted(val['errors'], key=lambda e: int(e['line']) if 'line' in e else sys.maxint)
376                         for e in errors:
377                                 if not 'line' in e:
378                                         s = '<tr><td></td><td>%s</td><td>%s</td><td>%s</td></tr>\n' % (e['id'], e['severity'], e['msg'])
379                                 else:
380                                         attr = ''
381                                         if e['severity'] == 'error':
382                                                 attr = 'class="error"'
383                                         s = '<tr><td><a href="%s#line-%s">%s</a></td>' % (f, e['line'], e['line'])
384                                         s+= '<td>%s</td><td>%s</td><td %s>%s</td></tr>\n' % (e['id'], e['severity'], attr, e['msg'])
385                                 row = ElementTree.fromstring(s)
386                                 table.append(row)
387                 content.append(table)
388
389         def _create_css_file(self, css_style_defs):
390                 css = str(CPPCHECK_CSS_FILE)
391                 if css_style_defs:
392                         css = "%s\n%s\n" % (css, css_style_defs)
393                 node = self.generator.path.get_bld().find_or_declare('cppcheck/style.css')
394                 node.write(css)
395
396         def _errors_evaluate(self, errors, http_index):
397                 name = self.generator.get_name()
398                 fatal = self.fatal
399                 severity = [err['severity'] for err in errors]
400                 problems = [err for err in errors if err['severity'] != 'information']
401
402                 if set(fatal) & set(severity):
403                         exc  = "\n"
404                         exc += "\nccpcheck detected fatal error(s) in task '%s', see report for details:" % name
405                         exc += "\n    file://%r" % (http_index)
406                         exc += "\n"
407                         self.generator.bld.fatal(exc)
408
409                 elif len(problems):
410                         msg =  "\nccpcheck detected (possible) problem(s) in task '%s', see report for details:" % name
411                         msg += "\n    file://%r" % http_index
412                         msg += "\n"
413                         Logs.error(msg)
414
415
416 class CppcheckHtmlFormatter(pygments.formatters.HtmlFormatter):
417         errors = []
418
419         def wrap(self, source, outfile):
420                 line_no = 1
421                 for i, t in super(CppcheckHtmlFormatter, self).wrap(source, outfile):
422                         # If this is a source code line we want to add a span tag at the end.
423                         if i == 1:
424                                 for error in self.errors:
425                                         if int(error['line']) == line_no:
426                                                 t = t.replace('\n', CPPCHECK_HTML_ERROR % error['msg'])
427                                 line_no += 1
428                         yield i, t
429
430
431 CCPCHECK_HTML_TYPE = \
432 '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">\n'
433
434 CPPCHECK_HTML_FILE = """
435 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd" [<!ENTITY nbsp "&#160;">]>
436 <html>
437         <head>
438                 <title>cppcheck - report - XXX</title>
439                 <link href="style.css" rel="stylesheet" type="text/css" />
440                 <style type="text/css">
441                 </style>
442         </head>
443         <body class="body">
444                 <div id="page-header">&nbsp;</div>
445                 <div id="page">
446                         <div id="header">
447                                 <h1>cppcheck report - XXX</h1>
448                         </div>
449                         <div id="menu">
450                                 <a href="index.html">Defect list</a>
451                         </div>
452                         <div id="content">
453                         </div>
454                         <div id="footer">
455                                 <div>cppcheck - a tool for static C/C++ code analysis</div>
456                                 <div>
457                                 Internet: <a href="http://cppcheck.sourceforge.net">http://cppcheck.sourceforge.net</a><br/>
458                         Forum: <a href="http://apps.sourceforge.net/phpbb/cppcheck/">http://apps.sourceforge.net/phpbb/cppcheck/</a><br/>
459                                 IRC: #cppcheck at irc.freenode.net
460                                 </div>
461                                 &nbsp;
462                         </div>
463                 &nbsp;
464                 </div>
465                 <div id="page-footer">&nbsp;</div>
466         </body>
467 </html>
468 """
469
470 CPPCHECK_HTML_TABLE = """
471 <table>
472         <tr>
473                 <th>Line</th>
474                 <th>Id</th>
475                 <th>Severity</th>
476                 <th>Message</th>
477         </tr>
478 </table>
479 """
480
481 CPPCHECK_HTML_ERROR = \
482 '<span style="background: #ffaaaa;padding: 3px;">&lt;--- %s</span>\n'
483
484 CPPCHECK_CSS_FILE = """
485 body.body {
486         font-family: Arial;
487         font-size: 13px;
488         background-color: black;
489         padding: 0px;
490         margin: 0px;
491 }
492
493 .error {
494         font-family: Arial;
495         font-size: 13px;
496         background-color: #ffb7b7;
497         padding: 0px;
498         margin: 0px;
499 }
500
501 th, td {
502         min-width: 100px;
503         text-align: left;
504 }
505
506 #page-header {
507         clear: both;
508         width: 1200px;
509         margin: 20px auto 0px auto;
510         height: 10px;
511         border-bottom-width: 2px;
512         border-bottom-style: solid;
513         border-bottom-color: #aaaaaa;
514 }
515
516 #page {
517         width: 1160px;
518         margin: auto;
519         border-left-width: 2px;
520         border-left-style: solid;
521         border-left-color: #aaaaaa;
522         border-right-width: 2px;
523         border-right-style: solid;
524         border-right-color: #aaaaaa;
525         background-color: White;
526         padding: 20px;
527 }
528
529 #page-footer {
530         clear: both;
531         width: 1200px;
532         margin: auto;
533         height: 10px;
534         border-top-width: 2px;
535         border-top-style: solid;
536         border-top-color: #aaaaaa;
537 }
538
539 #header {
540         width: 100%;
541         height: 70px;
542         background-image: url(logo.png);
543         background-repeat: no-repeat;
544         background-position: left top;
545         border-bottom-style: solid;
546         border-bottom-width: thin;
547         border-bottom-color: #aaaaaa;
548 }
549
550 #menu {
551         margin-top: 5px;
552         text-align: left;
553         float: left;
554         width: 100px;
555         height: 300px;
556 }
557
558 #menu > a {
559         margin-left: 10px;
560         display: block;
561 }
562
563 #content {
564         float: left;
565         width: 1020px;
566         margin: 5px;
567         padding: 0px 10px 10px 10px;
568         border-left-style: solid;
569         border-left-width: thin;
570         border-left-color: #aaaaaa;
571 }
572
573 #footer {
574         padding-bottom: 5px;
575         padding-top: 5px;
576         border-top-style: solid;
577         border-top-width: thin;
578         border-top-color: #aaaaaa;
579         clear: both;
580         font-size: 10px;
581 }
582
583 #footer > div {
584         float: left;
585         width: 33%;
586 }
587
588 """
589