Remove CERT addon from open source Cppcheck. The plan is to provide complete CERT C checking in Cppcheck Premium during this year.

This commit is contained in:
Daniel Marjamäki 2022-03-23 20:30:43 +01:00
parent a0bab85bf9
commit 92316b07c8
9 changed files with 3 additions and 767 deletions

View File

@ -62,13 +62,6 @@ matrix:
- ${CPPCHECK} --dump misc-test.cpp
- python3 ../misc.py -verify misc-test.cpp.dump
- cd ../../
# check addons/cert.py
- cd addons/test
- ${CPPCHECK} --dump cert-test.c
- python3 ../cert.py -verify cert-test.c.dump
- ${CPPCHECK} --dump cert-test.cpp
- python3 ../cert.py -verify cert-test.cpp.dump
- cd ../../
# check addons/misra.py
- cd addons/test
# We'll force C89 standard to enable an additional verification for

View File

@ -4,8 +4,6 @@ Addons are scripts that analyses Cppcheck dump files to check compatibility with
## Supported addons
+ [cert.py](https://github.com/danmar/cppcheck/blob/main/addons/cert.py)
Checks for compliance with the safe programming standard [CERT](http://www.cert.org/secure-coding/).
+ [misra.py](https://github.com/danmar/cppcheck/blob/main/addons/misra.py)
Used to verify compliance with MISRA C 2012 - a proprietary set of guidelines to avoid such questionable code, developed for embedded systems. Since this standard is proprietary, cppcheck does not display error text by specifying only the number of violated rules (for example, [c2012-21.3]). If you want to display full texts for violated rules, you will need to create a text file containing MISRA rules, which you will have to pass when calling the script with `--rule-texts` key. Some examples of rule texts files available in [tests directory](https://github.com/danmar/cppcheck/blob/main/addons/test/misra/).
+ [y2038.py](https://github.com/danmar/cppcheck/blob/main/addons/y2038.py)
@ -18,13 +16,13 @@ Addons are scripts that analyses Cppcheck dump files to check compatibility with
### Command line interface
```bash
cppcheck --addon=cert --addon=y2038 src/test.c
cppcheck --addon=misc src/test.c
```
It is also possible to call scripts as follows:
```bash
cppcheck --dump --quiet src/test.c
python cert.py src/test.c.dump
python misc.py src/test.c.dump
python misra.py --rule-texts=~/misra_rules.txt src/test.c.dump
```

View File

@ -1,475 +0,0 @@
#!/usr/bin/env python3
#
# Cert: Some extra CERT checkers
#
# Cppcheck itself handles many CERT rules. Cppcheck warns when there is undefined behaviour.
# CERT Homepage: https://www.cert.org/secure-coding/
#
# Example usage of this addon (scan a sourcefile main.cpp)
# cppcheck --dump main.cpp
# python cert.py main.cpp.dump
import argparse
import cppcheckdata
import sys
import re
import subprocess
VERIFY = ('-verify' in sys.argv)
VERIFY_EXPECTED = []
VERIFY_ACTUAL = []
def reportError(token, severity, msg, id):
if VERIFY:
VERIFY_ACTUAL.append(str(token.linenr) + ':cert-' + id)
else:
cppcheckdata.reportError(token, severity, msg, 'cert', id)
def simpleMatch(token, pattern):
return cppcheckdata.simpleMatch(token, pattern)
def isUnpackedStruct(token):
if token.valueType is None:
return False
if token.valueType.typeScope is None:
return False
if token.valueType.typeScope.type != "Struct":
return False
startToken = token.valueType.typeScope.bodyStart
linenr = int(startToken.linenr)
for line in open(startToken.file):
linenr -= 1
if linenr == 0:
return True
if linenr < 3 and re.match(r'#pragma\s+pack\s*\(', line):
return False
return True
def isLocalUnpackedStruct(arg):
if arg and arg.str == '&' and not arg.astOperand2:
arg = arg.astOperand1
return arg and arg.variable and (arg.variable.isLocal or arg.variable.isArgument) and isUnpackedStruct(arg)
def isBitwiseOp(token):
return token and (token.str in {'&', '|', '^'})
def isComparisonOp(token):
return token and (token.str in {'==', '!=', '>', '>=', '<', '<='})
def isCast(expr):
if not expr or expr.str != '(' or not expr.astOperand1 or expr.astOperand2:
return False
if simpleMatch(expr, '( )'):
return False
return True
def isStandardFunction(token):
if token.function:
return False
prev = token.previous
if prev:
if prev.str == '.':
return False
if prev.str == '::':
prevprev = prev.previous
if prevprev and not prevprev.str == 'std':
return False
return True
# Is this a function call
def isFunctionCall(token, function_names, number_of_arguments=None):
if not token.isName:
return False
if token.str not in function_names:
return False
if (token.next is None) or token.next.str != '(' or token.next != token.astParent:
return False
if number_of_arguments is None:
return True
return len(cppcheckdata.getArguments(token)) == number_of_arguments
# EXP05-C
# do not attempt to cast away const
def exp05(data):
# TODO Reuse code in misra rule 11.8
for token in data.tokenlist:
if isCast(token):
# C-style cast
if not token.valueType:
continue
if not token.astOperand1.valueType:
continue
if token.valueType.pointer == 0:
continue
if token.astOperand1.valueType.pointer == 0:
continue
const1 = token.valueType.constness
const2 = token.astOperand1.valueType.constness
if (const1 % 2) < (const2 % 2):
reportError(token, 'style', "Attempt to cast away const", 'EXP05-C')
elif token.str == '(' and token.astOperand1 and token.astOperand2 and token.astOperand1.function:
function = token.astOperand1.function
arguments = cppcheckdata.getArguments(token.previous)
if not arguments:
continue
for argnr, argvar in function.argument.items():
if argnr < 1 or argnr > len(arguments):
continue
if not argvar.isPointer:
continue
if (argvar.constness % 2) == 1: # data is const
continue
argtok = arguments[argnr - 1]
if not argtok.valueType:
continue
if argtok.valueType.pointer == 0:
continue
const2 = arguments[argnr - 1].valueType.constness
if (const2 % 2) == 1:
reportError(token, 'style', "Attempt to cast away const", 'EXP05-C')
# EXP42-C
# do not compare padding data
def exp42(data):
for token in data.tokenlist:
if token.str != '(' or not token.astOperand1 or token.astOperand1.str != 'memcmp':
continue
arg1 = None
arg2 = None
if token.astOperand2 and token.astOperand2.str == ',':
if token.astOperand2.astOperand1 and token.astOperand2.astOperand1.str == ',':
arg1 = token.astOperand2.astOperand1.astOperand1
arg2 = token.astOperand2.astOperand1.astOperand2
if isLocalUnpackedStruct(arg1) or isLocalUnpackedStruct(arg2):
reportError(
token, 'style', "Comparison of struct padding data " +
"(fix either by packing the struct using '#pragma pack' or by rewriting the comparison)", 'EXP42-C')
# EXP15-C
# Do not place a semicolon on the same line as an if, for or while statement
def exp15(data):
for scope in data.scopes:
if scope.type in ('If', 'For', 'While'):
token = scope.bodyStart.next
if token.str==';' and token.linenr==scope.bodyStart.linenr:
reportError(token, 'style', 'Do not place a semicolon on the same line as an IF, FOR or WHILE', 'EXP15-C')
# EXP46-C
# Do not use a bitwise operator with a Boolean-like operand
# int x = (a == b) & c;
def exp46(data):
for token in data.tokenlist:
if isBitwiseOp(token) and (isComparisonOp(token.astOperand1) or isComparisonOp(token.astOperand2)):
reportError(
token, 'style', 'Bitwise operator is used with a Boolean-like operand', 'EXP46-c')
# INT31-C
# Ensure that integer conversions do not result in lost or misinterpreted data
def int31(data, platform):
if not platform:
return
for token in data.tokenlist:
to_value_type = None
from_values = None
action = ''
if isCast(token):
to_value_type = token.valueType
from_values = token.astOperand1.values
action = 'casting'
elif token.str == '=' and token.astOperand1 and token.astOperand2:
to_value_type = token.astOperand1.valueType
from_values = token.astOperand2.values
action = 'assign'
else:
continue
if to_value_type is None or not from_values:
continue
bits = None
if token.valueType.pointer > 0:
bits = platform.pointer_bit
elif to_value_type.type == 'char':
bits = platform.char_bit
elif to_value_type.type == 'short':
bits = platform.short_bit
elif to_value_type.type == 'int':
bits = platform.int_bit
elif to_value_type.type == 'long':
bits = platform.long_bit
elif to_value_type.type == 'long long':
bits = platform.long_long_bit
else:
continue
if to_value_type.sign == 'unsigned':
found = False
for value in from_values:
if value.intvalue and value.intvalue < 0:
found = True
reportError(
token,
'style',
'Ensure that integer conversions do not result in lost or misinterpreted data (' + action + ' ' + str(value.intvalue) + ' to unsigned ' + token.valueType.type + ')',
'INT31-c')
break
if found:
continue
if bits >= 64:
continue
minval = 0
maxval = 1
if token.valueType.sign == 'signed':
minval = -(1 << (bits - 1))
maxval = ((1 << (bits - 1)) - 1)
else:
minval = 0
maxval = ((1 << bits) - 1)
for value in from_values:
if value.intvalue and (value.intvalue < minval or value.intvalue > maxval):
destType = ''
if token.valueType.sign:
destType = token.valueType.sign + ' ' + token.valueType.type
else:
destType = token.valueType.type
reportError(
token,
'style',
'Ensure that integer conversions do not result in lost or misinterpreted data (' + action + ' ' + str(value.intvalue) + ' to ' + destType + ')',
'INT31-c')
break
# ENV33-C
# Do not call system()
def env33(data):
for token in data.tokenlist:
if isFunctionCall(token, ('system',), 1):
# Invalid syntax
if not token.next.astOperand2:
continue
# ENV33-C-EX1: It is permissible to call system() with a null
# pointer argument to determine the presence of a command processor
# for the system.
argValue = token.next.astOperand2.getValue(0)
if argValue and argValue.intvalue == 0 and argValue.isKnown():
continue
reportError(token, 'style', 'Do not call system()', 'ENV33-C')
# MSC24-C
# Do not use deprecated or obsolescent functions
def msc24(data):
for token in data.tokenlist:
if isFunctionCall(token, ('asctime',), 1):
reportError(token,'style','Do not use asctime() better use asctime_s()', 'MSC24-C')
elif isFunctionCall(token, ('atof',), 1):
reportError(token,'style','Do not use atof() better use strtod()', 'MSC24-C')
elif isFunctionCall(token, ('atoi',), 1):
reportError(token,'style','Do not use atoi() better use strtol()', 'MSC24-C')
elif isFunctionCall(token, ('atol',), 1):
reportError(token,'style','Do not use atol() better use strtol()', 'MSC24-C')
elif isFunctionCall(token, ('atoll',), 1):
reportError(token,'style','Do not use atoll() better use strtoll()', 'MSC24-C')
elif isFunctionCall(token, ('ctime',), 1):
reportError(token,'style','Do not use ctime() better use ctime_s()', 'MSC24-C')
elif isFunctionCall(token, ('fopen',), 2):
reportError(token,'style','Do not use fopen() better use fopen_s()', 'MSC24-C')
elif isFunctionCall(token, ('freopen',), 3):
reportError(token,'style','Do not use freopen() better use freopen_s()', 'MSC24-C')
elif isFunctionCall(token, ('rewind',), 1):
reportError(token,'style','Do not use rewind() better use fseek()', 'MSC24-C')
elif isFunctionCall(token, ('setbuf',), 2):
reportError(token,'style','Do not use setbuf() better use setvbuf()', 'MSC24-C')
# MSC30-C
# Do not use the rand() function for generating pseudorandom numbers
def msc30(data):
for token in data.tokenlist:
if simpleMatch(token, "rand ( )") and isStandardFunction(token):
reportError(token, 'style', 'Do not use the rand() function for generating pseudorandom numbers', 'MSC30-c')
# STR03-C
# Do not inadvertently truncate a string
def str03(data):
for token in data.tokenlist:
if not isFunctionCall(token, 'strncpy'):
continue
arguments = cppcheckdata.getArguments(token)
if len(arguments)!=3:
continue
if arguments[2].str=='(' and arguments[2].astOperand1.str=='sizeof':
reportError(token, 'style', 'Do not inadvertently truncate a string', 'STR03-C')
# STR05-C
# Use pointers to const when referring to string literals
def str05(data):
for token in data.tokenlist:
if token.isString:
parent = token.astParent
if parent is None:
continue
parentOp1 = parent.astOperand1
if parent.isAssignmentOp and parentOp1.valueType:
if (parentOp1.valueType.type in ('char', 'wchar_t')) and parentOp1.valueType.pointer and not parentOp1.valueType.constness:
reportError(parentOp1, 'style', 'Use pointers to const when referring to string literals', 'STR05-C')
# STR07-C
# Use the bounds-checking interfaces for string manipulation
def str07(data):
if data.standards.c=='c89' or data.standards.c=='c99':
return
for token in data.tokenlist:
if not isFunctionCall(token, ('strcpy', 'strcat')):
continue
args = cppcheckdata.getArguments(token)
if len(args)!=2:
continue
if args[1].isString:
continue
reportError(token, 'style', 'Use the bounds-checking interfaces %s_s()' % token.str, 'STR07-C')
# STR11-C
# Do not specify the bound of a character array initialized with a string literal
def str11(data):
for token in data.tokenlist:
if not token.isString:
continue
strlen = token.strlen
parent = token.astParent
if parent is None:
continue
parentOp1 = parent.astOperand1
if parentOp1 is None or parentOp1.str!='[':
continue
if not parent.isAssignmentOp:
continue
varToken = parentOp1.astOperand1
if varToken is None or not varToken.isName:
continue
if varToken.variable is None:
continue
if varToken != varToken.variable.nameToken:
continue
valueToken = parentOp1.astOperand2
if valueToken is None:
continue
if valueToken.isNumber and int(valueToken.str)==strlen:
reportError(valueToken, 'style', 'Do not specify the bound of a character array initialized with a string literal', 'STR11-C')
# API01-C
# Avoid laying out strings in memory directly before sensitive data
def api01(data):
for scope in data.scopes:
if scope.type!='Struct':
continue
token = scope.bodyStart
string_found = False
# loop through the complete struct
while token != scope.bodyEnd:
if token.isName and token.variable:
is_string = False
if token.variable.isArray:
type_token = token.variable.typeStartToken
while type_token and type_token.isName:
if type_token.str in ('char', 'wchar_t') and not type_token.isExpandedMacro:
is_string = True
type_token = type_token.next
if is_string:
string_found = True
elif string_found and not token.variable.isConst:
reportError(token, 'style', 'Avoid laying out strings in memory directly before sensitive data', 'API01-C')
# reset flags to report other positions in the same struct
string_found = False
token = token.next
def get_args_parser():
parser = cppcheckdata.ArgumentParser()
parser.add_argument("-verify", help=argparse.SUPPRESS, action="store_true")
return parser
if __name__ == '__main__':
parser = get_args_parser()
args = parser.parse_args()
path_premium_addon = cppcheckdata.get_path_premium_addon()
if args.verify:
VERIFY = True
if not args.dumpfile:
if not args.quiet:
print("no input files.")
sys.exit(0)
for dumpfile in args.dumpfile:
if not args.quiet:
print('Checking %s...' % dumpfile)
if path_premium_addon:
premium_command = [path_premium_addon, '--cert', dumpfile]
if args.cli:
premium_command.append('--cli')
for line in subprocess.check_output(premium_command).decode('ascii').split('\n'):
if line.find('cert-') > 0:
print(line.strip())
data = cppcheckdata.CppcheckData(dumpfile)
if VERIFY:
VERIFY_ACTUAL = []
VERIFY_EXPECTED = []
for tok in data.rawTokens:
if tok.str.startswith('//') and 'TODO' not in tok.str:
for word in tok.str[2:].split(' '):
if re.match(r'cert-[A-Z][A-Z][A-Z][0-9][0-9].*',word):
VERIFY_EXPECTED.append(str(tok.linenr) + ':' + word)
for cfg in data.iterconfigurations():
if not args.quiet:
print('Checking %s, config %s...' % (dumpfile, cfg.name))
exp05(cfg)
exp42(cfg)
exp46(cfg)
exp15(cfg)
int31(cfg, data.platform)
str03(cfg)
str05(cfg)
str07(cfg)
str11(cfg)
env33(cfg)
msc24(cfg)
msc30(cfg)
api01(cfg)
if VERIFY:
fail = False
for expected in VERIFY_EXPECTED:
if expected not in VERIFY_ACTUAL:
print('Expected but not seen: ' + expected)
fail = True
for actual in VERIFY_ACTUAL:
if actual not in VERIFY_EXPECTED:
print('Not expected: ' + actual)
fail = True
if fail:
sys.exit(1)
sys.exit(cppcheckdata.EXIT_CODE)

View File

@ -1,207 +0,0 @@
// To test:
// ~/cppcheck/cppcheck --dump cert-test.c && python ../cert.py -verify cert-test.c.dump
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
struct S {
short a;
short b;
};
#pragma pack()
struct PackedStruct {
short a;
short b;
};
struct api01_bad_node_s
{
char name[10];
struct api01_bad_node_s* next; // cert-API01-C
};
struct api01_good_node_s
{
struct api01_good_node_s* next;
char name[String_Size];
};
struct api01_also_good_node_s
{
struct api01_also_good_node_s* next;
char *name;
};
struct api01_no_string_1 {
int data[10];
int x;
};
struct api01_no_string_2 {
int8_t data[10];
int x;
};
void dostuff(int *data);
void exp05()
{
const int x = 42;
int y = (int)x;
int *p;
p = (int *)&x; // cert-EXP05-C
const int data[] = {1,2,3,4};
dostuff(data); // cert-EXP05-C
}
void print(const char *p);
void exp05_fp() {
print("hello");
}
void exp42()
{
struct S s1 = {1,2};
struct S s2 = {1,2};
memcmp(&s1, &s2, sizeof(struct S)); // cert-EXP42-C
struct PackedStruct s3 = {1,2};
struct PackedStruct s4 = {1,2};
memcmp(&s3, &s4, sizeof(struct S));
}
void exp46(int x, int y, int z)
{
if ((x == y) & z) {} // cert-EXP46-c
}
void int31(int x)
{
x = (unsigned char)1000; // cert-INT31-c
x = (signed char)0xff; // cert-INT31-c
x = (unsigned char)-1; // cert-INT31-c
x = (unsigned long long)-1; // cert-INT31-c
unsigned char c;
c = 256; // cert-INT31-c
c = -1; // cert-INT31-c
// issue #10782
uint16_t * ptr;
uint32_t var = 65536;
ptr = (uint16_t *)var;
}
void env33()
{
system("chmod -x $(which chmod)"); // cert-ENV33-C
system(""); // cert-ENV33-C
system(NULL); // no-warning
system(0); // no-warning
const int *np = NULL;
system(np); // no-warning
int system;
}
void msc24()
{
struct S {
int x; int fopen;
};
struct S s;
time_t rawtime;
struct tm *timeinfo;
char buffer[256];
int i;
long int li;
long long int lli;
FILE *f;
s.fopen = 123;
f = fopen ("myfile.txt","w+"); //cert-MSC24-C
setbuf ( f , buffer ); //cert-MSC24-C
for ( i='A' ; i<='Z' ; i++)
fputc ( i, f);
rewind (f); //cert-MSC24-C
fclose (f);
time ( &rawtime );
timeinfo = localtime ( &rawtime );
printf ( "The current date/time is: %s", asctime (timeinfo) ); //cert-MSC24-C
float n = atof (buffer); //cert-MSC24-C
float m = sin (n*M_PI/180);
i = atoi (buffer); //cert-MSC24-C
li = atol(buffer); //cert-MSC24-C
lli = atoll(buffer); //cert-MSC24-C
time (&rawtime);
printf ("The current local time is: %s", ctime (&rawtime)); //cert-MSC24-C
freopen ("myfile.txt","w",stdout); //cert-MSC24-C
printf ("This sentence is redirected to a file.");
fclose (stdout);
}
void msc30()
{
unsigned int num = rand(); // cert-MSC30-c
int rand = 5;
int a = rand;
}
void exp15()
{
int x=5, y=7;
if(x==y); //cert-EXP15-C
{
printf("not working\n");
}
if(x)
;
}
void str03()
{
char *string_data=(char*)malloc(16);
char a[16];
int d;
strncpy(a, string_data, sizeof(a)); //cert-STR03-C
strncpy(a, string_data, 5); d=sizeof(int);
}
void str05()
{
int x=5, y=7;
if(x==y); //cert-EXP15-C
{
printf("not working\n");
}
if(x)
;
}
void str07(char *buf, const char *newBuf)
{
const char *str = "test";
strcat(buf,"bla");
strcat(buf, str); //cert-STR07-C
strcat(buf, newBuf); //cert-STR07-C
strcpy(buf, newBuf); //cert-STR07-C
}
void str11()
{
const char str[3]="abc"; //cert-STR11-C
const char *x[10]; x[3]="def";
}

View File

@ -1,26 +0,0 @@
// To test:
// ~/cppcheck/cppcheck --dump cert-test.cpp && python ../cert.py -verify cert-test.cpp.dump
#include <cstdlib>
class msc30TestClass {
public:
static int rand();
};
namespace exp05c {
using uint32 = std::uint32_t;
static const uint32 a = static_cast<uint32>(0xFFFFFFFF);
}
void msc30(msc30TestClass & testClass)
{
unsigned int num = rand(); // cert-MSC30-c
num = std::rand(); // cert-MSC30-c
num = msc30TestClass::rand();
num = unknownClass::rand();
num = testClass.rand();
num = unknownClass.rand();
int rand = 5;
int a = rand;
}

View File

@ -1,37 +0,0 @@
# Running the test with Python 2:
# Be sure to install pytest version 4.6.4 (newer should also work)
# Command in cppcheck directory:
# python -m pytest addons/test/test-cert.py
#
# Running the test with Python 3:
# Command in cppcheck directory:
# PYTHONPATH=./addons python3 -m pytest addons/test/test-cert.py
import sys
import pytest
def test_arguments_regression():
args_ok = ["-q", "--quiet",
"-verify",
"--cli"]
# Arguments with expected SystemExit
args_exit = ["--non-exists", "--non-exists-param=42", "-h", "--help"]
from addons.cert import get_args_parser
for arg in args_exit:
sys.argv.append(arg)
with pytest.raises(SystemExit):
parser = get_args_parser()
parser.parse_args()
sys.argv.remove(arg)
for arg in args_ok:
sys.argv.append(arg)
try:
parser = get_args_parser()
parser.parse_args()
except SystemExit:
pytest.fail("Unexpected SystemExit with '%s'" % arg)
sys.argv.remove(arg)

View File

@ -359,7 +359,7 @@ void CheckThread::parseClangErrors(const QString &tool, const QString &file0, QS
errorItem.severity = Severity::SeverityType::performance;
else if (id1.startsWith("portability"))
errorItem.severity = Severity::SeverityType::portability;
else if (id1.startsWith("cert") || (id1.startsWith("misc") && !id1.contains("unused")))
else if (id1.startsWith("misc") && !id1.contains("unused"))
errorItem.severity = Severity::SeverityType::warning;
else
errorItem.severity = Severity::SeverityType::style;

View File

@ -797,13 +797,6 @@
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="mAddonCert">
<property name="text">
<string>CERT</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="mAddonMisra">
<property name="text">

View File

@ -335,7 +335,6 @@ void ProjectFileDialog::loadFromProjectFile(const ProjectFile *projectFile)
const QString dataDir = getDataDir();
updateAddonCheckBox(mUI->mAddonThreadSafety, projectFile, dataDir, "threadsafety");
updateAddonCheckBox(mUI->mAddonY2038, projectFile, dataDir, "y2038");
updateAddonCheckBox(mUI->mAddonCert, projectFile, dataDir, "cert");
updateAddonCheckBox(mUI->mAddonMisra, projectFile, dataDir, "misra");
const QString &misraFile = settings.value(SETTINGS_MISRA_FILE, QString()).toString();
@ -410,8 +409,6 @@ void ProjectFileDialog::saveToProjectFile(ProjectFile *projectFile) const
list << "threadsafety";
if (mUI->mAddonY2038->isChecked())
list << "y2038";
if (mUI->mAddonCert->isChecked())
list << "cert";
if (mUI->mAddonMisra->isChecked())
list << "misra";
projectFile->setAddons(list);