Skip to content

Commit ca7ec25

Browse files
committed
Merge pull request cppcheck-opensource#708 from siemens/preprocessor-directives-for-addons-v2
Add preprocessor directives dump and Y2038 addon
2 parents 2f29efc + 38e70df commit ca7ec25

14 files changed

Lines changed: 715 additions & 21 deletions

Makefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,8 @@ tags:
287287
install: cppcheck
288288
install -d ${BIN}
289289
install cppcheck ${BIN}
290+
install addons/*.py ${BIN}
291+
install addons/*/*.py ${BIN}
290292
install htmlreport/cppcheck-htmlreport ${BIN}
291293
ifdef CFGDIR
292294
install -d ${DESTDIR}${CFGDIR}

addons/cppcheckdata.py

Lines changed: 97 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,35 @@
66
#
77

88
import xml.etree.ElementTree as ET
9+
import argparse
10+
11+
## Directive class. Contains information about each preprocessor directive in the source code.
12+
#
13+
# file and linenr denote the location where the directive is defined.
14+
#
15+
# To iterate through all directives use such code:
16+
# @code
17+
# data = cppcheckdata.parsedump(...)
18+
# for cfg in data.configurations:
19+
# for directive in cfg.directives:
20+
# print(directive.str)
21+
# @endcode
22+
#
23+
24+
25+
class Directive:
26+
## The directive line, with all C or C++ comments removed
27+
str = None
28+
## name of (possibly included) file where directive is defined
29+
file = None
30+
## line number in (possibly included) file where directive is defined
31+
linenr = None
32+
33+
def __init__(self, element):
34+
self.str = element.get('str')
35+
self.file = element.get('file')
36+
self.linenr = element.get('linenr')
37+
938

1039
## Token class. Contains information about each token in the source code.
1140
#
@@ -16,10 +45,11 @@
1645
# To iterate through all tokens use such code:
1746
# @code
1847
# data = cppcheckdata.parsedump(...)
19-
# code = ''
20-
# for token in data.tokenlist:
21-
# code = code + token.str + ' '
22-
# print(code)
48+
# for cfg in data.configurations:
49+
# code = ''
50+
# for token in cfg.tokenlist:
51+
# code = code + token.str + ' '
52+
# print(code)
2353
# @endcode
2454
#
2555

@@ -380,13 +410,15 @@ def __init__(self, element):
380410
self.values.append(ValueFlow.Value(value))
381411

382412
## Configuration class
383-
# This class contains the tokens, scopes, functions, variables and
384-
# value flows for one configuration.
413+
# This class contains the directives, tokens, scopes, functions,
414+
# variables and value flows for one configuration.
385415

386416

387417
class Configuration:
388418
## Name of the configuration, "" for default
389419
name = ''
420+
## List of Directive items
421+
directives = []
390422
## List of Token items
391423
tokenlist = []
392424
## List of Scope items
@@ -400,13 +432,18 @@ class Configuration:
400432

401433
def __init__(self, confignode):
402434
self.name = confignode.get('cfg')
435+
self.directives = []
403436
self.tokenlist = []
404437
self.scopes = []
405438
self.functions = []
406439
self.variables = []
407440
self.valueflow = []
408441

409442
for element in confignode:
443+
if element.tag == 'directivelist':
444+
for directive in element:
445+
self.directives.append(Directive(directive))
446+
410447
if element.tag == 'tokenlist':
411448
for token in element:
412449
self.tokenlist.append(Token(token))
@@ -534,3 +571,57 @@ def astIsFloat(token):
534571
if typeToken.str == 'float' or typeToken.str == 'double':
535572
return True
536573
return False
574+
575+
# Create a cppcheck parser
576+
577+
class CppCheckFormatter(argparse.HelpFormatter):
578+
'''
579+
Properly formats multiline argument helps
580+
'''
581+
def _split_lines(self, text, width):
582+
# this is the RawTextHelpFormatter._split_lines
583+
if text.startswith('R|'):
584+
return text[2:].splitlines()
585+
return argparse.HelpFormatter._split_lines(self, text, width)
586+
587+
def ArgumentParser():
588+
'''
589+
Returns an argparse argument parser with an already-added
590+
argument definition for -t/--template
591+
'''
592+
parser = argparse.ArgumentParser(formatter_class=CppCheckFormatter)
593+
parser.add_argument('-t', '--template', metavar='<text>',
594+
default='{callstack}: ({severity}) {message}',
595+
help="R|Format the error messages. E.g.\n" \
596+
"'{file}:{line},{severity},{id},{message}' or\n" \
597+
"'{file}({line}):({severity}) {message}' or\n" \
598+
"'{callstack} {message}'\n" \
599+
"Pre-defined templates: gcc, vs, edit")
600+
return parser
601+
602+
# Format an error message.
603+
604+
def reportError(template, callstack=[], severity='', message='', id=''):
605+
'''
606+
Format an error message according to the template.
607+
608+
:param template: format string, or 'gcc', 'vs' or 'edit'.
609+
:param callstack: e.g. [['file1.cpp',10],['file2.h','20'], ... ]
610+
:param severity: e.g. 'error', 'warning' ...
611+
:param id: message ID.
612+
:param message: message text.
613+
'''
614+
# expand predefined templates
615+
if template == 'gcc':
616+
template = '{file}:{line}: {severity}: {message}'
617+
elif template == 'vs':
618+
template = '{file}({line}): {severity}: {message}'
619+
elif template == 'edit':
620+
template = '{file} +{line}: {severity}: {message}'
621+
# compute 'callstack}, {file} and {line} replacements
622+
stack = ' -> '.join(['['+f+':'+str(l)+']' for (f,l) in callstack])
623+
file = callstack[-1][0]
624+
line = str(callstack[-1][1])
625+
# format message
626+
return template.format(callstack=stack, file=file, line=line,
627+
severity=severity, message=message, id=id)

addons/y2038/README

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
README of the Y2038 cppcheck addon
2+
==================================
3+
4+
Contents
5+
6+
1. What is Y2038?
7+
2. What is the Y2038 ccpcheck addon?
8+
3. How does the Y2038 cppcheck addon work?
9+
4. How to use the Y2038 cppcheck addon
10+
11+
---
12+
13+
1. What is Y2038?
14+
15+
In a few words:
16+
17+
In Linux, the current date and time is kept as the number of seconds elapsed
18+
since the Unich epoch, that is, since January 1st, 1970 at 00:00:00 GMT.
19+
20+
Most of the time, this representation is stored as a 32-bit signed quantity.
21+
22+
On January 19th, 2038 at 03:14:07 GMT, such 32-bit representations will reach
23+
their maximum positive value.
24+
25+
What happens then is unpredictable: system time might roll back to December
26+
13th, 1901 at 19:55:13, or it might keep running on until February 7th, 2106
27+
at 06:28:15 GMT, or the computer may freeze, or just about anything you can
28+
think of, plus a few ones you can't.
29+
30+
The workaround for this is to switch to a 64-bit signed representation of time
31+
as seconds from the Unix epoch. This representation will work for more than 250
32+
billion years.
33+
34+
Working around Y2038 requires fixing the Linux kernel, the C libraries, and
35+
any user code around which uses 32-bit epoch representations.
36+
37+
There is Y2038-proofing work in progress on the Linux and GNU glibc front.
38+
39+
2. What is the Y2038 ccpcheck addon?
40+
41+
The Y2038 cppcheck addon is a tool to help detect code which might need fixing
42+
because it is Y2038-sensitive. This may be because it uses types or functions
43+
from libc or from the Linux kernel which are known not to be Y2038-proof.
44+
45+
3. How does the Y2038 cppcheck addon work?
46+
47+
The Y2038 cppcheck addon takes XML dumps produced by cppcheck from source code
48+
files and looks for the names of types or functions which are known to be Y2038-
49+
sensitive, and emits diagnostics whenever it finds one.
50+
51+
Of course, this is of little use if your code uses a Y2038-proof glibc and
52+
correctly configured Y2038-proof time support.
53+
54+
This is why y2038.py takes into account two preprocessor directives:
55+
_TIME_BITS and __USE_TIME_BITS64.
56+
57+
_TIME_BITS is defined equal to 64 by user code when it wants 64-bit time
58+
support from the GNU glibc. Code which does not define _TIME_BITS equal to 64
59+
(or defines it to something else than 64) runs a risk of not being Y2038-proof.
60+
61+
__USE_TIME_BITS64 is defined by the GNU glibc when it actualy provides 64-bit
62+
time support. When this is defined, then all glibc symbols, barring bugs, are
63+
Y2038-proof (but your code might have its own Y2038 bugs, if it handles signed
64+
32-bit Unix epoch values).
65+
66+
The Y2038 cppcheck performs the following checks:
67+
68+
1. Upon meeting a definition for _TIME_BITS, if that definition does not
69+
set it equal to 64, this error diagnostic is emitted:
70+
71+
Error: _TIME_BITS must be defined equal to 64
72+
73+
This case is very unlikely but might result from a typo, so pointing
74+
it out is quite useful. Note that definitions of _TIME_BITS as an
75+
expression evaluating to 64 will be flagged too.
76+
77+
2. Upon meeting a definition for _USE_TIME_BITS64, if _TIME_BITS is not
78+
defined equal to 64, this information diagnostic is emitted:
79+
80+
Warning: _USE_TIME_BITS64 is defined but _TIME_BITS was not
81+
82+
This reflects the fact that even though the glibc checked default to
83+
64-bit time support, this was not requested by the user code, and
84+
therefore the user code might fail Y2038 if built against a glibc
85+
which defaults to 32-bit time support.
86+
87+
3. Upon meeting a symbol (type or function) which is known to be Y2038-
88+
sensitive, if _USE_TIME_BITS64 is undefined or _TIME_BITS not properly
89+
defined, this warning diagnostic is emitted:
90+
91+
Warning: <symbol> might be Y2038-sensitive
92+
93+
This reflects the fact that the user code is referring to a symbol
94+
which, when glibc defaults to 32-bit time support, might fail Y2038.
95+
96+
General note: y2038.py will handle multiple configurations, and will
97+
emit diagnostics for each configuration in turn.
98+
99+
4. How to use the Y2038 cppcheck addon
100+
101+
The Y2038 cppcheck addon is used like any other cppcheck addon:
102+
103+
cppcheck --dump file1.c [ file2.c [...]]]
104+
y2038.py file1.c [ file2.c [...]]]
105+
106+
Sample test C file is provided:
107+
108+
test/y2038-test-1-bad-time-bits.c
109+
test/y2038-test-2-no-time-bits.c
110+
test/y2038-test-3-no-use-time-bits.c
111+
test/y2038-test-4-good.c
112+
113+
These cover the cases described above. You can run them through cppcheck
114+
and y2038.py to see for yourself how the addon diagnostics look like. If
115+
this README is not outdated (and if it is, feel free to submit a patch),
116+
you can run cppcheck on these files as on any others:
117+
118+
cppcheck --dump addons/y2038/test/y2038-*.c
119+
y2038.py addons/y2038/test/y2038-*.dump
120+
121+
If you havve not installed cppcheck yet, you will have to run these
122+
commands from the root of the cppcheck repository:
123+
124+
make
125+
sudo make install
126+
./cppcheck --dump addons/y2038/test/y2038-*.c
127+
PYTHONPATH=addons python addons/y2038/y2038.py addons/y2038/test/y2038-*.c.dump
128+
129+
In both cases, y2038.py execution should result in the following:
130+
131+
Checking addons/y2038/test/y2038-test-1-bad-time-bits.c.dump...
132+
Checking addons/y2038/test/y2038-test-1-bad-time-bits.c.dump, config ""...
133+
Checking addons/y2038/test/y2038-test-2-no-time-bits.c.dump...
134+
Checking addons/y2038/test/y2038-test-2-no-time-bits.c.dump, config ""...
135+
Checking addons/y2038/test/y2038-test-3-no-use-time-bits.c.dump...
136+
Checking addons/y2038/test/y2038-test-3-no-use-time-bits.c.dump, config ""...
137+
Checking addons/y2038/test/y2038-test-4-good.c.dump...
138+
Checking addons/y2038/test/y2038-test-4-good.c.dump, config ""...
139+
# Configuration "":
140+
# Configuration "":
141+
[addons/y2038/test/y2038-test-1-bad-time-bits.c:8]: (error) _TIME_BITS must be defined equal to 64
142+
[addons/y2038/test/y2038-inc.h:9]: (warning) _USE_TIME_BITS64 is defined but _TIME_BITS was not
143+
[addons/y2038/test/y2038-test-1-bad-time-bits.c:10]: (information) addons/y2038/test/y2038-inc.h was included from here
144+
[addons/y2038/test/y2038-inc.h:9]: (warning) _USE_TIME_BITS64 is defined but _TIME_BITS was not
145+
[addons/y2038/test/y2038-test-2-no-time-bits.c:8]: (information) addons/y2038/test/y2038-inc.h was included from here
146+
[addons/y2038/test/y2038-test-3-no-use-time-bits.c:13]: (warning) timespec might be Y2038-sensitive
147+
[addons/y2038/test/y2038-test-3-no-use-time-bits.c:15]: (warning) clock_gettime might be Y2038-sensitive
148+
149+
Note: y2038.py recognizes option --template as cppcheck does, including
150+
pre-defined templates 'gcc', 'vs' and 'edit'. The short form -t is also
151+
recognized.

addons/y2038/test/y2038-inc.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#ifndef __INC2038
2+
#define _INC2038
3+
4+
/*
5+
* This file defines _USE_TIME_BITS64.
6+
* It plays the role of a Y2038-proof glibc.
7+
*/
8+
9+
#define _USE_TIME_BITS64
10+
11+
#endif /* INC2038 */
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#include <stdio.h>
2+
#include <fcntl.h>
3+
4+
/*
5+
* Define _TIME_BITS unequal to 64 to trigger error
6+
*/
7+
8+
#define _TIME_BITS 62
9+
10+
#include "y2038-inc.h"
11+
12+
int main(int argc, char **argv)
13+
{
14+
clockid_t my_clk_id;
15+
struct timespec *my_tp;
16+
17+
return clock_gettime( my_clk_id, &my_tp);
18+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#include <stdio.h>
2+
#include <fcntl.h>
3+
4+
/*
5+
* Do not define _TIME_BITS but have _USE_TIME_BITS64 defined
6+
*/
7+
8+
#include "y2038-inc.h"
9+
10+
int main(int argc, char **argv)
11+
{
12+
clockid_t my_clk_id;
13+
struct timespec *my_tp;
14+
15+
return clock_gettime( my_clk_id, &my_tp);
16+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#include <stdio.h>
2+
#include <fcntl.h>
3+
4+
/*
5+
* Include bad _USE_TIME_BITS64 definition to trigger error
6+
*/
7+
8+
#define _TIME_BITS 64
9+
10+
int main(int argc, char **argv)
11+
{
12+
clockid_t my_clk_id;
13+
struct timespec *my_tp;
14+
15+
return clock_gettime( my_clk_id, &my_tp);
16+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/*
2+
* Define _TIME_BITS equal to 64 so that glibc knows we want Y2038 support.
3+
*/
4+
5+
#define _TIME_BITS 64
6+
7+
#include "y2038-inc.h"
8+
9+
int main(int argc, char **argv)
10+
{
11+
clockid_t my_clk_id;
12+
struct timespec *my_tp;
13+
14+
return clock_gettime( my_clk_id, &my_tp);
15+
}

0 commit comments

Comments
 (0)