Preprocessor directives for addons
This patch augments the XML dumps with a 'directivelist' subnode which lists all raw preprocessor directives met while reading the source code in each configuration. Also, the addons/cppcheckdata.py file has been extended to give easy access to the list of directives and to provide Python support for the --template (or short -t) option. Finally, an new addon, addons/y2038/y2038.py, is created to detect when a glibc symbol might be Y2038-sensitive, based on whether and how _TIME_BITS and _USE_TIME_BITS64 are defined when meeting the symbol.
This commit is contained in:
parent
5214406771
commit
38e70dfb74
2
Makefile
2
Makefile
|
@ -287,6 +287,8 @@ tags:
|
|||
install: cppcheck
|
||||
install -d ${BIN}
|
||||
install cppcheck ${BIN}
|
||||
install addons/*.py ${BIN}
|
||||
install addons/*/*.py ${BIN}
|
||||
install htmlreport/cppcheck-htmlreport ${BIN}
|
||||
ifdef CFGDIR
|
||||
install -d ${DESTDIR}${CFGDIR}
|
||||
|
|
|
@ -6,6 +6,35 @@
|
|||
#
|
||||
|
||||
import xml.etree.ElementTree as ET
|
||||
import argparse
|
||||
|
||||
## Directive class. Contains information about each preprocessor directive in the source code.
|
||||
#
|
||||
# file and linenr denote the location where the directive is defined.
|
||||
#
|
||||
# To iterate through all directives use such code:
|
||||
# @code
|
||||
# data = cppcheckdata.parsedump(...)
|
||||
# for cfg in data.configurations:
|
||||
# for directive in cfg.directives:
|
||||
# print(directive.str)
|
||||
# @endcode
|
||||
#
|
||||
|
||||
|
||||
class Directive:
|
||||
## The directive line, with all C or C++ comments removed
|
||||
str = None
|
||||
## name of (possibly included) file where directive is defined
|
||||
file = None
|
||||
## line number in (possibly included) file where directive is defined
|
||||
linenr = None
|
||||
|
||||
def __init__(self, element):
|
||||
self.str = element.get('str')
|
||||
self.file = element.get('file')
|
||||
self.linenr = element.get('linenr')
|
||||
|
||||
|
||||
## Token class. Contains information about each token in the source code.
|
||||
#
|
||||
|
@ -16,8 +45,9 @@ import xml.etree.ElementTree as ET
|
|||
# To iterate through all tokens use such code:
|
||||
# @code
|
||||
# data = cppcheckdata.parsedump(...)
|
||||
# for cfg in data.configurations:
|
||||
# code = ''
|
||||
# for token in data.tokenlist:
|
||||
# for token in cfg.tokenlist:
|
||||
# code = code + token.str + ' '
|
||||
# print(code)
|
||||
# @endcode
|
||||
|
@ -380,13 +410,15 @@ class ValueFlow:
|
|||
self.values.append(ValueFlow.Value(value))
|
||||
|
||||
## Configuration class
|
||||
# This class contains the tokens, scopes, functions, variables and
|
||||
# value flows for one configuration.
|
||||
# This class contains the directives, tokens, scopes, functions,
|
||||
# variables and value flows for one configuration.
|
||||
|
||||
|
||||
class Configuration:
|
||||
## Name of the configuration, "" for default
|
||||
name = ''
|
||||
## List of Directive items
|
||||
directives = []
|
||||
## List of Token items
|
||||
tokenlist = []
|
||||
## List of Scope items
|
||||
|
@ -400,6 +432,7 @@ class Configuration:
|
|||
|
||||
def __init__(self, confignode):
|
||||
self.name = confignode.get('cfg')
|
||||
self.directives = []
|
||||
self.tokenlist = []
|
||||
self.scopes = []
|
||||
self.functions = []
|
||||
|
@ -407,6 +440,10 @@ class Configuration:
|
|||
self.valueflow = []
|
||||
|
||||
for element in confignode:
|
||||
if element.tag == 'directivelist':
|
||||
for directive in element:
|
||||
self.directives.append(Directive(directive))
|
||||
|
||||
if element.tag == 'tokenlist':
|
||||
for token in element:
|
||||
self.tokenlist.append(Token(token))
|
||||
|
@ -534,3 +571,57 @@ def astIsFloat(token):
|
|||
if typeToken.str == 'float' or typeToken.str == 'double':
|
||||
return True
|
||||
return False
|
||||
|
||||
# Create a cppcheck parser
|
||||
|
||||
class CppCheckFormatter(argparse.HelpFormatter):
|
||||
'''
|
||||
Properly formats multiline argument helps
|
||||
'''
|
||||
def _split_lines(self, text, width):
|
||||
# this is the RawTextHelpFormatter._split_lines
|
||||
if text.startswith('R|'):
|
||||
return text[2:].splitlines()
|
||||
return argparse.HelpFormatter._split_lines(self, text, width)
|
||||
|
||||
def ArgumentParser():
|
||||
'''
|
||||
Returns an argparse argument parser with an already-added
|
||||
argument definition for -t/--template
|
||||
'''
|
||||
parser = argparse.ArgumentParser(formatter_class=CppCheckFormatter)
|
||||
parser.add_argument('-t', '--template', metavar='<text>',
|
||||
default='{callstack}: ({severity}) {message}',
|
||||
help="R|Format the error messages. E.g.\n" \
|
||||
"'{file}:{line},{severity},{id},{message}' or\n" \
|
||||
"'{file}({line}):({severity}) {message}' or\n" \
|
||||
"'{callstack} {message}'\n" \
|
||||
"Pre-defined templates: gcc, vs, edit")
|
||||
return parser
|
||||
|
||||
# Format an error message.
|
||||
|
||||
def reportError(template, callstack=[], severity='', message='', id=''):
|
||||
'''
|
||||
Format an error message according to the template.
|
||||
|
||||
:param template: format string, or 'gcc', 'vs' or 'edit'.
|
||||
:param callstack: e.g. [['file1.cpp',10],['file2.h','20'], ... ]
|
||||
:param severity: e.g. 'error', 'warning' ...
|
||||
:param id: message ID.
|
||||
:param message: message text.
|
||||
'''
|
||||
# expand predefined templates
|
||||
if template == 'gcc':
|
||||
template = '{file}:{line}: {severity}: {message}'
|
||||
elif template == 'vs':
|
||||
template = '{file}({line}): {severity}: {message}'
|
||||
elif template == 'edit':
|
||||
template = '{file} +{line}: {severity}: {message}'
|
||||
# compute 'callstack}, {file} and {line} replacements
|
||||
stack = ' -> '.join(['['+f+':'+str(l)+']' for (f,l) in callstack])
|
||||
file = callstack[-1][0]
|
||||
line = str(callstack[-1][1])
|
||||
# format message
|
||||
return template.format(callstack=stack, file=file, line=line,
|
||||
severity=severity, message=message, id=id)
|
||||
|
|
|
@ -0,0 +1,151 @@
|
|||
README of the Y2038 cppcheck addon
|
||||
==================================
|
||||
|
||||
Contents
|
||||
|
||||
1. What is Y2038?
|
||||
2. What is the Y2038 ccpcheck addon?
|
||||
3. How does the Y2038 cppcheck addon work?
|
||||
4. How to use the Y2038 cppcheck addon
|
||||
|
||||
---
|
||||
|
||||
1. What is Y2038?
|
||||
|
||||
In a few words:
|
||||
|
||||
In Linux, the current date and time is kept as the number of seconds elapsed
|
||||
since the Unich epoch, that is, since January 1st, 1970 at 00:00:00 GMT.
|
||||
|
||||
Most of the time, this representation is stored as a 32-bit signed quantity.
|
||||
|
||||
On January 19th, 2038 at 03:14:07 GMT, such 32-bit representations will reach
|
||||
their maximum positive value.
|
||||
|
||||
What happens then is unpredictable: system time might roll back to December
|
||||
13th, 1901 at 19:55:13, or it might keep running on until February 7th, 2106
|
||||
at 06:28:15 GMT, or the computer may freeze, or just about anything you can
|
||||
think of, plus a few ones you can't.
|
||||
|
||||
The workaround for this is to switch to a 64-bit signed representation of time
|
||||
as seconds from the Unix epoch. This representation will work for more than 250
|
||||
billion years.
|
||||
|
||||
Working around Y2038 requires fixing the Linux kernel, the C libraries, and
|
||||
any user code around which uses 32-bit epoch representations.
|
||||
|
||||
There is Y2038-proofing work in progress on the Linux and GNU glibc front.
|
||||
|
||||
2. What is the Y2038 ccpcheck addon?
|
||||
|
||||
The Y2038 cppcheck addon is a tool to help detect code which might need fixing
|
||||
because it is Y2038-sensitive. This may be because it uses types or functions
|
||||
from libc or from the Linux kernel which are known not to be Y2038-proof.
|
||||
|
||||
3. How does the Y2038 cppcheck addon work?
|
||||
|
||||
The Y2038 cppcheck addon takes XML dumps produced by cppcheck from source code
|
||||
files and looks for the names of types or functions which are known to be Y2038-
|
||||
sensitive, and emits diagnostics whenever it finds one.
|
||||
|
||||
Of course, this is of little use if your code uses a Y2038-proof glibc and
|
||||
correctly configured Y2038-proof time support.
|
||||
|
||||
This is why y2038.py takes into account two preprocessor directives:
|
||||
_TIME_BITS and __USE_TIME_BITS64.
|
||||
|
||||
_TIME_BITS is defined equal to 64 by user code when it wants 64-bit time
|
||||
support from the GNU glibc. Code which does not define _TIME_BITS equal to 64
|
||||
(or defines it to something else than 64) runs a risk of not being Y2038-proof.
|
||||
|
||||
__USE_TIME_BITS64 is defined by the GNU glibc when it actualy provides 64-bit
|
||||
time support. When this is defined, then all glibc symbols, barring bugs, are
|
||||
Y2038-proof (but your code might have its own Y2038 bugs, if it handles signed
|
||||
32-bit Unix epoch values).
|
||||
|
||||
The Y2038 cppcheck performs the following checks:
|
||||
|
||||
1. Upon meeting a definition for _TIME_BITS, if that definition does not
|
||||
set it equal to 64, this error diagnostic is emitted:
|
||||
|
||||
Error: _TIME_BITS must be defined equal to 64
|
||||
|
||||
This case is very unlikely but might result from a typo, so pointing
|
||||
it out is quite useful. Note that definitions of _TIME_BITS as an
|
||||
expression evaluating to 64 will be flagged too.
|
||||
|
||||
2. Upon meeting a definition for _USE_TIME_BITS64, if _TIME_BITS is not
|
||||
defined equal to 64, this information diagnostic is emitted:
|
||||
|
||||
Warning: _USE_TIME_BITS64 is defined but _TIME_BITS was not
|
||||
|
||||
This reflects the fact that even though the glibc checked default to
|
||||
64-bit time support, this was not requested by the user code, and
|
||||
therefore the user code might fail Y2038 if built against a glibc
|
||||
which defaults to 32-bit time support.
|
||||
|
||||
3. Upon meeting a symbol (type or function) which is known to be Y2038-
|
||||
sensitive, if _USE_TIME_BITS64 is undefined or _TIME_BITS not properly
|
||||
defined, this warning diagnostic is emitted:
|
||||
|
||||
Warning: <symbol> might be Y2038-sensitive
|
||||
|
||||
This reflects the fact that the user code is referring to a symbol
|
||||
which, when glibc defaults to 32-bit time support, might fail Y2038.
|
||||
|
||||
General note: y2038.py will handle multiple configurations, and will
|
||||
emit diagnostics for each configuration in turn.
|
||||
|
||||
4. How to use the Y2038 cppcheck addon
|
||||
|
||||
The Y2038 cppcheck addon is used like any other cppcheck addon:
|
||||
|
||||
cppcheck --dump file1.c [ file2.c [...]]]
|
||||
y2038.py file1.c [ file2.c [...]]]
|
||||
|
||||
Sample test C file is provided:
|
||||
|
||||
test/y2038-test-1-bad-time-bits.c
|
||||
test/y2038-test-2-no-time-bits.c
|
||||
test/y2038-test-3-no-use-time-bits.c
|
||||
test/y2038-test-4-good.c
|
||||
|
||||
These cover the cases described above. You can run them through cppcheck
|
||||
and y2038.py to see for yourself how the addon diagnostics look like. If
|
||||
this README is not outdated (and if it is, feel free to submit a patch),
|
||||
you can run cppcheck on these files as on any others:
|
||||
|
||||
cppcheck --dump addons/y2038/test/y2038-*.c
|
||||
y2038.py addons/y2038/test/y2038-*.dump
|
||||
|
||||
If you havve not installed cppcheck yet, you will have to run these
|
||||
commands from the root of the cppcheck repository:
|
||||
|
||||
make
|
||||
sudo make install
|
||||
./cppcheck --dump addons/y2038/test/y2038-*.c
|
||||
PYTHONPATH=addons python addons/y2038/y2038.py addons/y2038/test/y2038-*.c.dump
|
||||
|
||||
In both cases, y2038.py execution should result in the following:
|
||||
|
||||
Checking addons/y2038/test/y2038-test-1-bad-time-bits.c.dump...
|
||||
Checking addons/y2038/test/y2038-test-1-bad-time-bits.c.dump, config ""...
|
||||
Checking addons/y2038/test/y2038-test-2-no-time-bits.c.dump...
|
||||
Checking addons/y2038/test/y2038-test-2-no-time-bits.c.dump, config ""...
|
||||
Checking addons/y2038/test/y2038-test-3-no-use-time-bits.c.dump...
|
||||
Checking addons/y2038/test/y2038-test-3-no-use-time-bits.c.dump, config ""...
|
||||
Checking addons/y2038/test/y2038-test-4-good.c.dump...
|
||||
Checking addons/y2038/test/y2038-test-4-good.c.dump, config ""...
|
||||
# Configuration "":
|
||||
# Configuration "":
|
||||
[addons/y2038/test/y2038-test-1-bad-time-bits.c:8]: (error) _TIME_BITS must be defined equal to 64
|
||||
[addons/y2038/test/y2038-inc.h:9]: (warning) _USE_TIME_BITS64 is defined but _TIME_BITS was not
|
||||
[addons/y2038/test/y2038-test-1-bad-time-bits.c:10]: (information) addons/y2038/test/y2038-inc.h was included from here
|
||||
[addons/y2038/test/y2038-inc.h:9]: (warning) _USE_TIME_BITS64 is defined but _TIME_BITS was not
|
||||
[addons/y2038/test/y2038-test-2-no-time-bits.c:8]: (information) addons/y2038/test/y2038-inc.h was included from here
|
||||
[addons/y2038/test/y2038-test-3-no-use-time-bits.c:13]: (warning) timespec might be Y2038-sensitive
|
||||
[addons/y2038/test/y2038-test-3-no-use-time-bits.c:15]: (warning) clock_gettime might be Y2038-sensitive
|
||||
|
||||
Note: y2038.py recognizes option --template as cppcheck does, including
|
||||
pre-defined templates 'gcc', 'vs' and 'edit'. The short form -t is also
|
||||
recognized.
|
|
@ -0,0 +1,11 @@
|
|||
#ifndef __INC2038
|
||||
#define _INC2038
|
||||
|
||||
/*
|
||||
* This file defines _USE_TIME_BITS64.
|
||||
* It plays the role of a Y2038-proof glibc.
|
||||
*/
|
||||
|
||||
#define _USE_TIME_BITS64
|
||||
|
||||
#endif /* INC2038 */
|
|
@ -0,0 +1,18 @@
|
|||
#include <stdio.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
/*
|
||||
* Define _TIME_BITS unequal to 64 to trigger error
|
||||
*/
|
||||
|
||||
#define _TIME_BITS 62
|
||||
|
||||
#include "y2038-inc.h"
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
clockid_t my_clk_id;
|
||||
struct timespec *my_tp;
|
||||
|
||||
return clock_gettime( my_clk_id, &my_tp);
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
#include <stdio.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
/*
|
||||
* Do not define _TIME_BITS but have _USE_TIME_BITS64 defined
|
||||
*/
|
||||
|
||||
#include "y2038-inc.h"
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
clockid_t my_clk_id;
|
||||
struct timespec *my_tp;
|
||||
|
||||
return clock_gettime( my_clk_id, &my_tp);
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
#include <stdio.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
/*
|
||||
* Include bad _USE_TIME_BITS64 definition to trigger error
|
||||
*/
|
||||
|
||||
#define _TIME_BITS 64
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
clockid_t my_clk_id;
|
||||
struct timespec *my_tp;
|
||||
|
||||
return clock_gettime( my_clk_id, &my_tp);
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* Define _TIME_BITS equal to 64 so that glibc knows we want Y2038 support.
|
||||
*/
|
||||
|
||||
#define _TIME_BITS 64
|
||||
|
||||
#include "y2038-inc.h"
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
clockid_t my_clk_id;
|
||||
struct timespec *my_tp;
|
||||
|
||||
return clock_gettime( my_clk_id, &my_tp);
|
||||
}
|
|
@ -0,0 +1,188 @@
|
|||
#! /usr/bin/python
|
||||
#
|
||||
# cppcheck addon for Y2038 safeness detection
|
||||
#
|
||||
# Detects:
|
||||
#
|
||||
# 1. _TIME_BITS being defined to something else than 64 bits
|
||||
# 2. _USE_TIME_BITS64 being defined when _TIME_BITS is not
|
||||
# 3. Any Y2038-sensitive symbol when _USE_TIME_BITS64 is not defined.
|
||||
#
|
||||
# Example usage:
|
||||
# $ cppcheck --dump path-to-src/
|
||||
# $ y2038.py path-to-src/
|
||||
#
|
||||
# y2038.py will walk the source tree for .dump files.
|
||||
|
||||
import cppcheckdata
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
import argparse
|
||||
|
||||
#---------------
|
||||
# Error handling
|
||||
#---------------
|
||||
|
||||
diagnostics = {}
|
||||
|
||||
|
||||
def reportDiagnostic(template, configuration, file, line, severity, message):
|
||||
# collect diagnostics by configuration
|
||||
if not configuration in diagnostics:
|
||||
diagnostics[configuration] = []
|
||||
# add error to this configuration
|
||||
diagnostics[configuration].append(
|
||||
cppcheckdata.reportError(template, [[file, line]], severity, message))
|
||||
|
||||
|
||||
def printDiagnostics():
|
||||
for cfg in diagnostics:
|
||||
sys.stderr.write('# Configuration "' + cfg + '":\n')
|
||||
for diag in diagnostics[cfg]:
|
||||
sys.stderr.write(diag + '\n')
|
||||
|
||||
|
||||
def reportDirDiag(template, cfg, filename, linenr, directive, severity, msg):
|
||||
reportDiagnostic(template, cfg.name,
|
||||
directive.file, directive.linenr,
|
||||
severity, msg)
|
||||
if (filename != directive.file) or (linenr != directive.linenr):
|
||||
reportDiagnostic(template, cfg.name,
|
||||
filename, linenr, 'information',
|
||||
directive.file + ' was included from here')
|
||||
|
||||
|
||||
def reportTokDiag(template, cfg, token, severity, msg):
|
||||
reportDiagnostic(template, cfg.name,
|
||||
token.file, token.linenr,
|
||||
severity, msg)
|
||||
|
||||
#---------------------------------------------
|
||||
# #define/#undef detection regular expressions
|
||||
#---------------------------------------------
|
||||
|
||||
# test for '#define _TIME_BITS 64'
|
||||
re_define_time_bits_64 = re.compile(r'^\s*#\s*define\s+_TIME_BITS\s+64\s*$')
|
||||
|
||||
# test for '#define _TIME_BITS ...' (combine w/ above to test for 'not 64')
|
||||
re_define_time_bits = re.compile(r'^\s*#\s*define\s+_TIME_BITS\s+.*$')
|
||||
|
||||
# test for '#undef _TIME_BITS' (if it ever happens)
|
||||
re_undef_time_bits = re.compile(r'^\s*#\s*undef\s+_TIME_BITS\s*$')
|
||||
|
||||
# test for '#define _USE_TIME_BITS64'
|
||||
re_define_use_time_bits64 = re.compile(r'^\s*#\s*define\s+_USE_TIME_BITS64\s*$')
|
||||
|
||||
# test for '#undef _USE_TIME_BITS64' (if it ever happens)
|
||||
re_undef_use_time_bits64 = re.compile(r'^\s*#\s*undef\s+_USE_TIME_BITS64\s*$')
|
||||
|
||||
#------------------------------------
|
||||
# List of Y2038-sensitive identifiers
|
||||
#------------------------------------
|
||||
|
||||
# This is WIP. Eventually it should contain all identifiers (types
|
||||
# and functions) which would be affected by the Y2038 bug.
|
||||
|
||||
id_Y2038 = [
|
||||
'time_t',
|
||||
'timespec',
|
||||
'clock_gettime'
|
||||
]
|
||||
|
||||
# return all files ending in .dump among or under the given paths
|
||||
|
||||
def find_dump_files(paths):
|
||||
dumpfiles = []
|
||||
for path in paths:
|
||||
if path.endswith('.dump'):
|
||||
if not path in dumpfiles:
|
||||
dumpfiles.append(path)
|
||||
else:
|
||||
for (top, subdirs, files) in os.walk(path):
|
||||
for file in files:
|
||||
if file.endswith('.dump'):
|
||||
f = top+'/'+file
|
||||
if not f in dumpfiles:
|
||||
dumpfiles.append(f)
|
||||
dumpfiles.sort()
|
||||
return dumpfiles
|
||||
|
||||
#------------------
|
||||
# Let's get to work
|
||||
#------------------
|
||||
|
||||
# extend cppcheck parser with our own options
|
||||
|
||||
parser = cppcheckdata.ArgumentParser()
|
||||
parser.add_argument('-q', '--quiet', action='store_true',
|
||||
help='do not print "Checking ..." lines')
|
||||
parser.add_argument('paths', nargs='+', metavar='path',
|
||||
help='path to dump file or directory')
|
||||
|
||||
# parse command line
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# now operate on each file in turn
|
||||
|
||||
dumpfiles = find_dump_files(args.paths)
|
||||
|
||||
for dumpfile in dumpfiles:
|
||||
if not args.quiet:
|
||||
print('Checking ' + dumpfile + '...')
|
||||
srcfile = dumpfile.rstrip('.dump')
|
||||
# at the start of the check, we don't know if code is Y2038 safe
|
||||
y2038safe = False
|
||||
# load XML from .dump file
|
||||
data = cppcheckdata.parsedump(dumpfile)
|
||||
# go through each configuration
|
||||
for cfg in data.configurations:
|
||||
if not args.quiet:
|
||||
print('Checking ' + dumpfile + ', config "' + cfg.name + '"...')
|
||||
safe_ranges = []
|
||||
safe = -1
|
||||
time_bits_defined = False
|
||||
for directive in cfg.directives:
|
||||
# track source line number
|
||||
if directive.file == srcfile:
|
||||
srclinenr = directive.linenr
|
||||
# check for correct _TIME_BITS if present
|
||||
if re_define_time_bits_64.match(directive.str):
|
||||
time_bits_defined = True
|
||||
elif re_define_time_bits.match(directive.str):
|
||||
reportDirDiag(args.template, cfg, srcfile, srclinenr,
|
||||
directive, 'error',
|
||||
'_TIME_BITS must be defined equal to 64')
|
||||
time_bits_defined = False
|
||||
elif re_undef_time_bits.match(directive.str):
|
||||
time_bits_defined = False
|
||||
# check for _USE_TIME_BITS64 (un)definition
|
||||
if re_define_use_time_bits64.match(directive.str):
|
||||
safe = int(srclinenr)
|
||||
# warn about _TIME_BITS not being defined
|
||||
if time_bits_defined == False:
|
||||
reportDirDiag(args.template,
|
||||
cfg, srcfile, srclinenr, directive, 'warning',
|
||||
'_USE_TIME_BITS64 is defined but _TIME_BITS was not')
|
||||
elif re_undef_use_time_bits64.match(directive.str):
|
||||
unsafe = int(srclinenr)
|
||||
# do we have a safe..unsafe area?
|
||||
if safe > 0 and unsafe > safe:
|
||||
safe_ranges.append((safe, unsafe))
|
||||
safe = -1
|
||||
# check end of source beyond last directive
|
||||
if len(cfg.tokenlist) > 0:
|
||||
unsafe = int(cfg.tokenlist[-1].linenr)
|
||||
if safe > 0 and unsafe > safe:
|
||||
safe_ranges.append((safe, unsafe))
|
||||
# go through all tokens
|
||||
for token in cfg.tokenlist:
|
||||
if token.str in id_Y2038:
|
||||
if not any(lower <= int(token.linenr) <= upper
|
||||
for (lower, upper) in safe_ranges):
|
||||
reportTokDiag(args.template, cfg, token, 'warning',
|
||||
token.str + ' might be Y2038-sensitive')
|
||||
token = token.next
|
||||
|
||||
printDiagnostics()
|
|
@ -233,6 +233,7 @@ unsigned int CppCheck::processFile(const std::string& filename, std::istream& fi
|
|||
// dump xml if --dump
|
||||
if (_settings.dump && fdump.is_open()) {
|
||||
fdump << "<dump cfg=\"" << cfg << "\">" << std::endl;
|
||||
preprocessor.dump(fdump);
|
||||
_tokenizer.dump(fdump);
|
||||
fdump << "</dump>" << std::endl;
|
||||
}
|
||||
|
|
|
@ -33,6 +33,41 @@
|
|||
#include <set>
|
||||
#include <stack>
|
||||
|
||||
/**
|
||||
* Remove heading and trailing whitespaces from the input parameter.
|
||||
* If string is all spaces/tabs, return empty string.
|
||||
* @param s The string to trim.
|
||||
*/
|
||||
static std::string trim(const std::string& s)
|
||||
{
|
||||
const std::string::size_type beg = s.find_first_not_of(" \t");
|
||||
if (beg == std::string::npos)
|
||||
return "";
|
||||
const std::string::size_type end = s.find_last_not_of(" \t");
|
||||
return s.substr(beg, end - beg + 1);
|
||||
}
|
||||
|
||||
Directive::Directive(const std::string &_file, const int _linenr, const std::string &_str):
|
||||
file(_file),
|
||||
linenr(_linenr),
|
||||
str(_str)
|
||||
{
|
||||
// strip C++ comment if there is one
|
||||
std::size_t pos = str.find("//");
|
||||
if (pos != std::string::npos)
|
||||
str.erase(pos);
|
||||
// strip any C comments
|
||||
while ((pos = str.find("/*")) != std::string::npos) {
|
||||
std::size_t end = str.find("*/", pos+2);
|
||||
if (end != std::string::npos) {
|
||||
str.erase(pos, end + 2 - pos);
|
||||
} else { // treat '/*' as '//' if '*/' is missing
|
||||
str.erase(pos);
|
||||
}
|
||||
}
|
||||
str = trim(str);
|
||||
}
|
||||
|
||||
bool Preprocessor::missingIncludeFlag;
|
||||
bool Preprocessor::missingSystemIncludeFlag;
|
||||
|
||||
|
@ -1749,7 +1784,9 @@ bool Preprocessor::match_cfg_def(std::map<std::string, std::string> cfg, std::st
|
|||
|
||||
std::string Preprocessor::getcode(const std::string &filedata, const std::string &cfg, const std::string &filename)
|
||||
{
|
||||
// For the error report
|
||||
// For the error report and preprocessor dump:
|
||||
// line number relative to current (included) file
|
||||
// (may decrease when popping back from an included file)
|
||||
unsigned int lineno = 0;
|
||||
|
||||
std::ostringstream ret;
|
||||
|
@ -1766,9 +1803,28 @@ std::string Preprocessor::getcode(const std::string &filedata, const std::string
|
|||
std::stack<unsigned int> lineNumbers;
|
||||
std::istringstream istr(filedata);
|
||||
std::string line;
|
||||
directives.clear();
|
||||
while (std::getline(istr, line)) {
|
||||
++lineno;
|
||||
|
||||
if (line.empty()) {
|
||||
ret << '\n';
|
||||
continue;
|
||||
}
|
||||
|
||||
// record directive for addons / checkers
|
||||
if ((line[0] == '#')
|
||||
&& (line.compare(0, 6, "#line ") != 0)
|
||||
&& (line.compare(0, 8, "#endfile") != 0)) {
|
||||
// for clarity, turn "#file ..." back into "#include ..."
|
||||
std::string orig_line = line;
|
||||
if (orig_line.compare(0, 6, "#file ")==0)
|
||||
orig_line.replace(1, 4, "include");
|
||||
// record directive and extra
|
||||
directives.push_back(Directive(filenames.top(), lineno,
|
||||
orig_line));
|
||||
}
|
||||
|
||||
if (_settings.terminated())
|
||||
return "";
|
||||
|
||||
|
@ -2412,20 +2468,6 @@ static void skipstring(const std::string &line, std::string::size_type &pos)
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove heading and trailing whitespaces from the input parameter.
|
||||
* If string is all spaces/tabs, return empty string.
|
||||
* @param s The string to trim.
|
||||
*/
|
||||
static std::string trim(const std::string& s)
|
||||
{
|
||||
const std::string::size_type beg = s.find_first_not_of(" \t");
|
||||
if (beg == std::string::npos)
|
||||
return "";
|
||||
const std::string::size_type end = s.find_last_not_of(" \t");
|
||||
return s.substr(beg, end - beg + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief get parameters from code. For example 'foo(1,2)' => '1','2'
|
||||
* @param line in: The code
|
||||
|
@ -3271,3 +3313,23 @@ void Preprocessor::getErrorMessages(ErrorLogger *errorLogger, const Settings *se
|
|||
preprocessor.validateCfgError("X", "X");
|
||||
preprocessor.error("", 1, "#error message"); // #error ..
|
||||
}
|
||||
|
||||
void Preprocessor::dump(std::ostream &out) const
|
||||
{
|
||||
// Create a xml directive dump.
|
||||
// The idea is not that this will be readable for humans. It's a
|
||||
// data dump that 3rd party tools could load and get useful info from.
|
||||
std::list<Directive>::const_iterator it;
|
||||
|
||||
out << " <directivelist>" << std::endl;
|
||||
|
||||
for (it = directives.begin(); it != directives.end(); ++it) {
|
||||
out << " <directive "
|
||||
<< "file=\"" << it->file << "\" "
|
||||
<< "linenr=\"" << it->linenr << "\" "
|
||||
// str might contain characters such as '"', '<' or '>' which
|
||||
// could result in invalid XML, so run it through toxml().
|
||||
<< "str=\"" << ErrorLogger::toxml(it->str) << "\"/>" << std::endl;
|
||||
}
|
||||
out << " </directivelist>" << std::endl;
|
||||
}
|
||||
|
|
|
@ -31,6 +31,30 @@
|
|||
class ErrorLogger;
|
||||
class Settings;
|
||||
|
||||
/**
|
||||
* @brief A preprocessor directive
|
||||
* Each preprocessor directive (#include, #define, #undef,#if, #ifdef,
|
||||
* #else, #endif) will be recorded as an instance of this class.
|
||||
*
|
||||
* file and linenr denote the location where where the directive is defined.
|
||||
*
|
||||
*/
|
||||
|
||||
class CPPCHECKLIB Directive {
|
||||
public:
|
||||
/** name of (possibly included) file where directive is defined */
|
||||
std::string file;
|
||||
|
||||
/** line number in (possibly included) file where directive is defined */
|
||||
int linenr;
|
||||
|
||||
/** the actual directive text */
|
||||
std::string str;
|
||||
|
||||
/** record a directive (possibly filtering src) */
|
||||
Directive(const std::string &_file, const int _linenr, const std::string &_str);
|
||||
};
|
||||
|
||||
/// @addtogroup Core
|
||||
/// @{
|
||||
|
||||
|
@ -250,6 +274,11 @@ public:
|
|||
file0 = f;
|
||||
}
|
||||
|
||||
/**
|
||||
* dump all directives present in source file
|
||||
*/
|
||||
void dump(std::ostream &out) const;
|
||||
|
||||
private:
|
||||
void missingInclude(const std::string &filename, unsigned int linenr, const std::string &header, HeaderTypes headerType);
|
||||
|
||||
|
@ -271,6 +300,9 @@ private:
|
|||
Settings& _settings;
|
||||
ErrorLogger *_errorLogger;
|
||||
|
||||
/** list of all directives met while preprocessing file */
|
||||
std::list<Directive> directives;
|
||||
|
||||
/** filename for cpp/c file - useful when reporting errors */
|
||||
std::string file0;
|
||||
};
|
||||
|
|
|
@ -304,6 +304,10 @@ private:
|
|||
|
||||
TEST_CASE(wrongPathOnUnicodeError); // see #6773
|
||||
TEST_CASE(wrongPathOnErrorDirective);
|
||||
|
||||
TEST_CASE(testDirectiveIncludeTypes);
|
||||
TEST_CASE(testDirectiveIncludeLocations);
|
||||
TEST_CASE(testDirectiveIncludeComments);
|
||||
}
|
||||
|
||||
std::string preprocessorRead(const char* code) {
|
||||
|
@ -3743,6 +3747,91 @@ private:
|
|||
ASSERT_EQUALS("[test.c:1]: (error) #error hello world!\n", errout.str());
|
||||
}
|
||||
|
||||
void testDirectiveIncludeTypes() {
|
||||
const char filedata[] = "#define macro some definition\n"
|
||||
"#undef macro\n"
|
||||
"#ifdef macro\n"
|
||||
"#elif some (complex) condition\n"
|
||||
"#else\n"
|
||||
"#endif\n"
|
||||
"#if some other condition\n"
|
||||
"#pragma some proprietary content\n"
|
||||
"#\n" /* may appear in old C code */
|
||||
"#ident some text\n" /* may appear in old C code */
|
||||
"#unknownmacro some unpredictable text\n"
|
||||
"#warning some warning message\n"
|
||||
"#error some error message\n";
|
||||
const char dumpdata[] = " <directivelist>\n"
|
||||
|
||||
" <directive file=\"test.c\" linenr=\"1\" str=\"#define macro some definition\"/>\n"
|
||||
" <directive file=\"test.c\" linenr=\"2\" str=\"#undef macro\"/>\n"
|
||||
" <directive file=\"test.c\" linenr=\"3\" str=\"#ifdef macro\"/>\n"
|
||||
" <directive file=\"test.c\" linenr=\"4\" str=\"#elif some (complex) condition\"/>\n"
|
||||
" <directive file=\"test.c\" linenr=\"5\" str=\"#else\"/>\n"
|
||||
" <directive file=\"test.c\" linenr=\"6\" str=\"#endif\"/>\n"
|
||||
" <directive file=\"test.c\" linenr=\"7\" str=\"#if some other condition\"/>\n"
|
||||
" <directive file=\"test.c\" linenr=\"8\" str=\"#pragma some proprietary content\"/>\n"
|
||||
" <directive file=\"test.c\" linenr=\"9\" str=\"#\"/>\n"
|
||||
" <directive file=\"test.c\" linenr=\"10\" str=\"#ident some text\"/>\n"
|
||||
" <directive file=\"test.c\" linenr=\"11\" str=\"#unknownmacro some unpredictable text\"/>\n"
|
||||
" <directive file=\"test.c\" linenr=\"12\" str=\"#warning some warning message\"/>\n"
|
||||
" <directive file=\"test.c\" linenr=\"13\" str=\"#error some error message\"/>\n"
|
||||
" </directivelist>\n";
|
||||
|
||||
std::ostringstream ostr;
|
||||
Settings settings;
|
||||
Preprocessor preprocessor(settings, this);
|
||||
preprocessor.getcode(filedata, "", "test.c");
|
||||
preprocessor.dump(ostr);
|
||||
ASSERT_EQUALS(dumpdata, ostr.str());
|
||||
}
|
||||
|
||||
void testDirectiveIncludeLocations() {
|
||||
const char filedata[] = "#define macro1 val\n"
|
||||
"#file \"inc1.h\"\n"
|
||||
"#define macro2 val\n"
|
||||
"#file \"inc2.h\"\n"
|
||||
"#define macro3 val\n"
|
||||
"#endfile\n"
|
||||
"#define macro4 val\n"
|
||||
"#endfile\n"
|
||||
"#define macro5 val\n";
|
||||
const char dumpdata[] = " <directivelist>\n"
|
||||
" <directive file=\"test.c\" linenr=\"1\" str=\"#define macro1 val\"/>\n"
|
||||
" <directive file=\"test.c\" linenr=\"2\" str=\"#include "inc1.h"\"/>\n"
|
||||
" <directive file=\"inc1.h\" linenr=\"1\" str=\"#define macro2 val\"/>\n"
|
||||
" <directive file=\"inc1.h\" linenr=\"2\" str=\"#include "inc2.h"\"/>\n"
|
||||
" <directive file=\"inc2.h\" linenr=\"1\" str=\"#define macro3 val\"/>\n"
|
||||
" <directive file=\"inc1.h\" linenr=\"3\" str=\"#define macro4 val\"/>\n"
|
||||
" <directive file=\"test.c\" linenr=\"3\" str=\"#define macro5 val\"/>\n"
|
||||
" </directivelist>\n";
|
||||
|
||||
std::ostringstream ostr;
|
||||
Settings settings;
|
||||
Preprocessor preprocessor(settings, this);
|
||||
preprocessor.getcode(filedata, "", "test.c");
|
||||
preprocessor.dump(ostr);
|
||||
ASSERT_EQUALS(dumpdata, ostr.str());
|
||||
}
|
||||
|
||||
void testDirectiveIncludeComments() {
|
||||
const char filedata[] = "#ifdef macro2 /* this will be removed */\n"
|
||||
"#else /* this will be removed too */\n"
|
||||
"#endif /* this will also be removed */\n";
|
||||
const char dumpdata[] = " <directivelist>\n"
|
||||
" <directive file=\"test.c\" linenr=\"1\" str=\"#ifdef macro2\"/>\n"
|
||||
" <directive file=\"test.c\" linenr=\"2\" str=\"#else\"/>\n"
|
||||
" <directive file=\"test.c\" linenr=\"3\" str=\"#endif\"/>\n"
|
||||
" </directivelist>\n";
|
||||
|
||||
std::ostringstream ostr;
|
||||
Settings settings;
|
||||
Preprocessor preprocessor(settings, this);
|
||||
preprocessor.getcode(filedata, "", "test.c");
|
||||
preprocessor.dump(ostr);
|
||||
ASSERT_EQUALS(dumpdata, ostr.str());
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
REGISTER_TEST(TestPreprocessor)
|
||||
|
|
|
@ -430,6 +430,8 @@ int main(int argc, char **argv)
|
|||
fout << "install: cppcheck\n";
|
||||
fout << "\tinstall -d ${BIN}\n";
|
||||
fout << "\tinstall cppcheck ${BIN}\n";
|
||||
fout << "\tinstall addons/*.py ${BIN}\n";
|
||||
fout << "\tinstall addons/*/*.py ${BIN}\n";
|
||||
fout << "\tinstall htmlreport/cppcheck-htmlreport ${BIN}\n";
|
||||
fout << "ifdef CFGDIR \n";
|
||||
fout << "\tinstall -d ${DESTDIR}${CFGDIR}\n";
|
||||
|
|
Loading…
Reference in New Issue