Blame addons/cert.py

Packit 2035a7
#!/usr/bin/env python
Packit 2035a7
#
Packit 2035a7
# Cert: Some extra CERT checkers
Packit 2035a7
#
Packit 2035a7
# Cppcheck itself handles many CERT rules. Cppcheck warns when there is undefined behaviour.
Packit 2035a7
# CERT Homepage: https://www.cert.org/secure-coding/
Packit 2035a7
#
Packit 2035a7
# Example usage of this addon (scan a sourcefile main.cpp)
Packit 2035a7
# cppcheck --dump main.cpp
Packit 2035a7
# python cert.py main.cpp.dump
Packit 2035a7
Packit 2035a7
import cppcheckdata
Packit 2035a7
import sys
Packit 2035a7
import re
Packit 2035a7
Packit 2035a7
VERIFY = ('-verify' in sys.argv)
Packit 2035a7
VERIFY_EXPECTED = []
Packit 2035a7
VERIFY_ACTUAL = []
Packit 2035a7
Packit 2035a7
def reportError(token, severity, msg, id):
Packit 2035a7
    if VERIFY:
Packit 2035a7
        VERIFY_ACTUAL.append(str(token.linenr) + ':' + id)
Packit 2035a7
    else:
Packit 2035a7
        sys.stderr.write(
Packit 2035a7
            '[' + token.file + ':' + str(token.linenr) + '] (' + severity + '): ' + msg + ' [' + id + ']\n')
Packit 2035a7
Packit 2035a7
def simpleMatch(token, pattern):
Packit 2035a7
    for p in pattern.split(' '):
Packit 2035a7
        if not token or token.str != p:
Packit 2035a7
            return False
Packit 2035a7
        token = token.next
Packit 2035a7
    return True
Packit 2035a7
Packit 2035a7
def isUnpackedStruct(token):
Packit 2035a7
    if token.valueType is None:
Packit 2035a7
        return False
Packit 2035a7
    if token.valueType.typeScope is None:
Packit 2035a7
        return False;
Packit 2035a7
    if token.valueType.typeScope.type != "Struct":
Packit 2035a7
        return False
Packit 2035a7
    startToken = token.valueType.typeScope.classStart
Packit 2035a7
Packit 2035a7
    linenr = int(startToken.linenr)
Packit 2035a7
    for line in open(startToken.file):
Packit 2035a7
        linenr -= 1
Packit 2035a7
        if linenr == 0:
Packit 2035a7
            return True
Packit 2035a7
        if linenr < 3 and re.match(r'#pragma\s+pack\s*\(', line):
Packit 2035a7
            return False
Packit 2035a7
    return True
Packit 2035a7
Packit 2035a7
Packit 2035a7
def isLocalUnpackedStruct(arg):
Packit 2035a7
    if arg and arg.str == '&' and not arg.astOperand2:
Packit 2035a7
        arg = arg.astOperand1
Packit 2035a7
    return arg and arg.variable and (arg.variable.isLocal or arg.variable.isArgument) and isUnpackedStruct(arg)
Packit 2035a7
Packit 2035a7
Packit 2035a7
def isBitwiseOp(token):
Packit 2035a7
    return token and (token.str in {'&', '|', '^'})
Packit 2035a7
Packit 2035a7
Packit 2035a7
def isComparisonOp(token):
Packit 2035a7
    return token and (token.str in {'==', '!=', '>', '>=', '<', '<='})
Packit 2035a7
Packit 2035a7
def isCast(expr):
Packit 2035a7
    if not expr or expr.str != '(' or not expr.astOperand1 or expr.astOperand2:
Packit 2035a7
        return False
Packit 2035a7
    if simpleMatch(expr, '( )'):
Packit 2035a7
        return False
Packit 2035a7
    return True
Packit 2035a7
Packit 2035a7
# EXP42-C
Packit 2035a7
# do not compare padding data
Packit 2035a7
def exp42(data):
Packit 2035a7
    for token in data.tokenlist:
Packit 2035a7
        if token.str != '(' or not token.astOperand1 or token.astOperand1.str != 'memcmp':
Packit 2035a7
            continue
Packit 2035a7
Packit 2035a7
        arg1 = None
Packit 2035a7
        arg2 = None
Packit 2035a7
        if token.astOperand2 and token.astOperand2.str == ',':
Packit 2035a7
            if token.astOperand2.astOperand1 and token.astOperand2.astOperand1.str == ',':
Packit 2035a7
                arg1 = token.astOperand2.astOperand1.astOperand1
Packit 2035a7
                arg2 = token.astOperand2.astOperand1.astOperand2
Packit 2035a7
Packit 2035a7
        if isLocalUnpackedStruct(arg1) or isLocalUnpackedStruct(arg2):
Packit 2035a7
            reportError(
Packit 2035a7
                token, 'style', "Comparison of struct padding data " +
Packit 2035a7
                "(fix either by packing the struct using '#pragma pack' or by rewriting the comparison)", 'cert-EXP42-C')
Packit 2035a7
Packit 2035a7
Packit 2035a7
# EXP46-C
Packit 2035a7
# Do not use a bitwise operator with a Boolean-like operand
Packit 2035a7
#   int x = (a == b) & c;
Packit 2035a7
def exp46(data):
Packit 2035a7
    for token in data.tokenlist:
Packit 2035a7
        if isBitwiseOp(token) and (isComparisonOp(token.astOperand1) or isComparisonOp(token.astOperand2)):
Packit 2035a7
            reportError(
Packit 2035a7
                token, 'style', 'Bitwise operator is used with a Boolean-like operand', 'cert-EXP46-c')
Packit 2035a7
Packit 2035a7
# INT31-C
Packit 2035a7
# Ensure that integer conversions do not result in lost or misinterpreted data
Packit 2035a7
def int31(data, platform):
Packit 2035a7
    if not platform:
Packit 2035a7
        return
Packit 2035a7
    for token in data.tokenlist:
Packit 2035a7
        if not isCast(token):
Packit 2035a7
            continue
Packit 2035a7
        if not token.valueType or not token.astOperand1.values:
Packit 2035a7
            continue
Packit 2035a7
        bits = None
Packit 2035a7
        if token.valueType.type == 'char':
Packit 2035a7
            bits = platform.char_bit
Packit 2035a7
        elif token.valueType.type == 'short':
Packit 2035a7
            bits = platform.short_bit
Packit 2035a7
        elif token.valueType.type == 'int':
Packit 2035a7
            bits = platform.int_bit
Packit 2035a7
        elif token.valueType.type == 'long':
Packit 2035a7
            bits = platform.long_bit
Packit 2035a7
        elif token.valueType.type == 'long long':
Packit 2035a7
            bits = platform.long_long_bit
Packit 2035a7
        else:
Packit 2035a7
            continue
Packit 2035a7
        if token.valueType.sign == 'unsigned':
Packit 2035a7
            found = False
Packit 2035a7
            for value in token.astOperand1.values:
Packit 2035a7
                if value.intvalue < 0:
Packit 2035a7
                    found = True
Packit 2035a7
                    reportError(
Packit 2035a7
                        token,
Packit 2035a7
                        'style',
Packit 2035a7
                        'Ensure that integer conversions do not result in lost or misinterpreted data (casting ' + str(value.intvalue) + ' to unsigned ' + token.valueType.type + ')',
Packit 2035a7
                        'cert-INT31-c')
Packit 2035a7
                break
Packit 2035a7
            if found:
Packit 2035a7
                continue
Packit 2035a7
        if bits >= 64:
Packit 2035a7
            continue
Packit 2035a7
        minval = 0
Packit 2035a7
        maxval = 1
Packit 2035a7
        if token.valueType.sign == 'signed':
Packit 2035a7
            minval = -(1 << (bits - 1))
Packit 2035a7
            maxval = ((1 << (bits - 1)) - 1)
Packit 2035a7
        else:
Packit 2035a7
            minval = 0
Packit 2035a7
            maxval = ((1 << bits) - 1)
Packit 2035a7
        for value in token.astOperand1.values:
Packit 2035a7
            if value.intvalue < minval or value.intvalue > maxval:
Packit 2035a7
                destType = ''
Packit 2035a7
                if token.valueType.sign:
Packit 2035a7
                    destType = token.valueType.sign + ' ' + token.valueType.type
Packit 2035a7
                else:
Packit 2035a7
                    destType = token.valueType.type
Packit 2035a7
                reportError(
Packit 2035a7
                    token,
Packit 2035a7
                    'style',
Packit 2035a7
                    'Ensure that integer conversions do not result in lost or misinterpreted data (casting ' + str(value.intvalue) + ' to ' + destType + ')',
Packit 2035a7
                    'cert-INT31-c')
Packit 2035a7
                break
Packit 2035a7
Packit 2035a7
for arg in sys.argv[1:]:
Packit 2035a7
    if arg == '-verify':
Packit 2035a7
        VERIFY = True
Packit 2035a7
        continue
Packit 2035a7
    print('Checking ' + arg + '...')
Packit 2035a7
    data = cppcheckdata.parsedump(arg)
Packit 2035a7
Packit 2035a7
    if VERIFY:
Packit 2035a7
        VERIFY_ACTUAL = []
Packit 2035a7
        VERIFY_EXPECTED = []
Packit 2035a7
        for tok in data.rawTokens:
Packit 2035a7
            if tok.str.startswith('//') and 'TODO' not in tok.str:
Packit 2035a7
                for word in tok.str[2:].split(' '):
Packit 2035a7
                    if re.match(r'cert-[A-Z][A-Z][A-Z][0-9][0-9].*',word):
Packit 2035a7
                        VERIFY_EXPECTED.append(str(tok.linenr) + ':' + word)
Packit 2035a7
Packit 2035a7
    for cfg in data.configurations:
Packit 2035a7
        if len(data.configurations) > 1:
Packit 2035a7
            print('Checking ' + arg + ', config "' + cfg.name + '"...')
Packit 2035a7
        exp42(cfg)
Packit 2035a7
        exp46(cfg)
Packit 2035a7
        int31(cfg, data.platform)
Packit 2035a7
Packit 2035a7
    if VERIFY:
Packit 2035a7
        for expected in VERIFY_EXPECTED:
Packit 2035a7
            if expected not in VERIFY_ACTUAL:
Packit 2035a7
                print('Expected but not seen: ' + expected)
Packit 2035a7
                sys.exit(1)
Packit 2035a7
        for actual in VERIFY_ACTUAL:
Packit 2035a7
            if actual not in VERIFY_EXPECTED:
Packit 2035a7
                print('Not expected: ' + actual)
Packit 2035a7
                sys.exit(1)