cppcheck.py 17 KB

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