addons; add CTU infrastructure

This commit is contained in:
Daniel Marjamäki 2021-07-07 10:58:13 +02:00
parent 5825a35566
commit 9172f2ab3b
11 changed files with 245 additions and 54 deletions

View File

@ -125,6 +125,7 @@ jobs:
run: | run: |
./cppcheck --addon=threadsafety addons/test/threadsafety ./cppcheck --addon=threadsafety addons/test/threadsafety
./cppcheck --addon=threadsafety --std=c++03 addons/test/threadsafety ./cppcheck --addon=threadsafety --std=c++03 addons/test/threadsafety
./cppcheck --addon=misra --inline-suppr --error-exitcode=1 addons/test/misra/misra-ctu-*-test.c
- name: Build GUI on ubuntu - name: Build GUI on ubuntu
if: contains(matrix.os, 'ubuntu') if: contains(matrix.os, 'ubuntu')

View File

@ -127,3 +127,8 @@ jobs:
python -m pytest test-more-projects.py || exit /b !errorlevel! python -m pytest test-more-projects.py || exit /b !errorlevel!
python -m pytest test-proj2.py || exit /b !errorlevel! python -m pytest test-proj2.py || exit /b !errorlevel!
python -m pytest test-suppress-syntaxError.py || exit /b !errorlevel! python -m pytest test-suppress-syntaxError.py || exit /b !errorlevel!
- name: Test addons
run: |
.\cppcheck.exe --addon=misra --inline-suppr --error-exitcode=1 addons/test/misra/misra-ctu-*-test.c

View File

@ -559,6 +559,21 @@ class Variable:
self.scope = IdMap[self.scopeId] self.scope = IdMap[self.scopeId]
class TypedefInfo:
"""
TypedefInfo class -- information about typedefs
"""
name = None
filename = None
lineNumber = None
used = None
def __init__(self, element):
self.name = element.get('name')
self.filename = element.get('file')
self.lineNumber = int(element.get('line'))
self.used = (element.get('used') == '1')
class Value: class Value:
""" """
Value class Value class
@ -707,6 +722,7 @@ class Configuration:
scopes = [] scopes = []
functions = [] functions = []
variables = [] variables = []
typedefInfo = []
valueflow = [] valueflow = []
standards = None standards = None
@ -717,6 +733,7 @@ class Configuration:
self.scopes = [] self.scopes = []
self.functions = [] self.functions = []
self.variables = [] self.variables = []
self.typedefInfo = []
self.valueflow = [] self.valueflow = []
self.standards = Standards() self.standards = Standards()
@ -939,6 +956,9 @@ class CppcheckData:
# Iterating <varlist> in a <scope>. # Iterating <varlist> in a <scope>.
iter_scope_varlist = False iter_scope_varlist = False
# Iterating <typedef-info>
iter_typedef_info = False
# Use iterable objects to traverse XML tree for dump files incrementally. # Use iterable objects to traverse XML tree for dump files incrementally.
# Iterative approach is required to avoid large memory consumption. # Iterative approach is required to avoid large memory consumption.
# Calling .clear() is necessary to let the element be garbage collected. # Calling .clear() is necessary to let the element be garbage collected.
@ -1013,6 +1033,12 @@ class CppcheckData:
else: else:
cfg_arguments.append(var) cfg_arguments.append(var)
# Parse typedef info
elif node.tag == 'typedef-info':
iter_typedef_info = (event == 'start')
elif iter_typedef_info and node.tag == 'info' and event == 'start':
cfg.typedefInfo.append(TypedefInfo(node))
# Parse valueflows (list of values) # Parse valueflows (list of values)
elif node.tag == 'valueflow' and event == 'start': elif node.tag == 'valueflow' and event == 'start':
continue continue
@ -1163,3 +1189,10 @@ def reportError(location, severity, message, addon, errorId, extra=''):
sys.stderr.write('%s (%s) %s [%s-%s]\n' % (loc, severity, message, addon, errorId)) sys.stderr.write('%s (%s) %s [%s-%s]\n' % (loc, severity, message, addon, errorId))
global EXIT_CODE global EXIT_CODE
EXIT_CODE = 1 EXIT_CODE = 1
def reportSummary(dumpfile, summary_type, summary_data):
# dumpfile ends with ".dump"
ctu_info_file = dumpfile[:-4] + "ctu-info"
with open(ctu_info_file, 'at') as f:
msg = {'summary': summary_type, 'data': summary_data}
f.write(json.dumps(msg) + '\n')

View File

@ -17,6 +17,7 @@ from __future__ import print_function
import cppcheckdata import cppcheckdata
import itertools import itertools
import json
import sys import sys
import re import re
import os import os
@ -24,6 +25,8 @@ import argparse
import codecs import codecs
import string import string
from collections import namedtuple
try: try:
from itertools import izip as zip from itertools import izip as zip
except ImportError: except ImportError:
@ -1336,6 +1339,15 @@ class MisraChecker:
self.reportError(scope.bodyStart, 5, 5) self.reportError(scope.bodyStart, 5, 5)
def misra_5_6(self, data):
dumpfile = data[0]
typedefInfo = data[1]
summary = []
for ti in typedefInfo:
summary.append({ 'name': ti.name, 'file': ti.filename, 'line': ti.lineNumber })
if len(summary) > 0:
cppcheckdata.reportSummary(dumpfile, 'misra_5_6', summary)
def misra_6_1(self, data): def misra_6_1(self, data):
# Bitfield type must be bool or explicitly signed/unsigned int # Bitfield type must be bool or explicitly signed/unsigned int
for token in data.tokenlist: for token in data.tokenlist:
@ -3250,6 +3262,7 @@ class MisraChecker:
self.executeCheck(502, self.misra_5_2, cfg) self.executeCheck(502, self.misra_5_2, cfg)
self.executeCheck(504, self.misra_5_4, cfg) self.executeCheck(504, self.misra_5_4, cfg)
self.executeCheck(505, self.misra_5_5, cfg) self.executeCheck(505, self.misra_5_5, cfg)
self.executeCheck(506, self.misra_5_6, (dumpfile, cfg.typedefInfo))
self.executeCheck(601, self.misra_6_1, cfg) self.executeCheck(601, self.misra_6_1, cfg)
self.executeCheck(602, self.misra_6_2, cfg) self.executeCheck(602, self.misra_6_2, cfg)
if cfgNumber == 0: if cfgNumber == 0:
@ -3342,6 +3355,32 @@ class MisraChecker:
self.executeCheck(2112, self.misra_21_12, cfg) self.executeCheck(2112, self.misra_21_12, cfg)
# 22.4 is already covered by Cppcheck writeReadOnlyFile # 22.4 is already covered by Cppcheck writeReadOnlyFile
def analyse_ctu_info(self, files):
data_misra_5_6 = []
Location = namedtuple('Location', 'file linenr column')
for filename in files:
if not filename.endswith('.ctu-info'):
continue
for line in open(filename, 'rt'):
if line.startswith('{'):
s = json.loads(line)
summary_type = s['summary']
summary_data = s['data']
# TODO break out info some function
if summary_type == 'misra_5_6':
for info1 in summary_data:
found = False
for info2 in data_misra_5_6:
if info1['name'] == info2['name']:
found = True
if info1['file'] != info2['file'] or info1['line'] != info2['line']:
self.reportError(Location(info2['file'], info2['line'], 0), 5, 6)
self.reportError(Location(info1['file'], info1['line'], 0), 5, 6)
if not found:
data_misra_5_6.append(info1)
RULE_TEXTS_HELP = '''Path to text file of MISRA rules RULE_TEXTS_HELP = '''Path to text file of MISRA rules
@ -3434,6 +3473,9 @@ def main():
checker.setSeverity(args.severity) checker.setSeverity(args.severity)
for item in args.dumpfile: for item in args.dumpfile:
if item.endswith('.ctu-info'):
continue
checker.parseDump(item) checker.parseDump(item)
if settings.verify: if settings.verify:
@ -3457,6 +3499,8 @@ def main():
if exitCode != 0: if exitCode != 0:
sys.exit(exitCode) sys.exit(exitCode)
checker.analyse_ctu_info(args.dumpfile)
if settings.verify: if settings.verify:
sys.exit(exitCode) sys.exit(exitCode)

View File

@ -0,0 +1,8 @@
// Test with command:
// ./cppcheck --addon=misra --inline-suppr addons/test/misra/misra-ctu-*-test.c
#include "misra-ctu-test.h"
// cppcheck-suppress misra-c2012-5.6
typedef int MISRA_5_6_VIOLATION;

View File

@ -0,0 +1,8 @@
// Test with command:
// ./cppcheck --addon=misra --inline-suppr addons/test/misra/misra-ctu-*-test.c
#include "misra-ctu-test.h"
// cppcheck-suppress misra-c2012-5.6
typedef int MISRA_5_6_VIOLATION;

View File

@ -0,0 +1,5 @@
typedef int t1;
typedef int t2;

View File

@ -67,6 +67,7 @@ namespace {
std::string scriptFile; std::string scriptFile;
std::string args; std::string args;
std::string python; std::string python;
bool ctu = false;
static std::string getFullPath(const std::string &fileName, const std::string &exename) { static std::string getFullPath(const std::string &fileName, const std::string &exename) {
if (Path::fileExists(fileName)) if (Path::fileExists(fileName))
@ -102,6 +103,15 @@ namespace {
args += " " + v.get<std::string>(); args += " " + v.get<std::string>();
} }
if (obj.count("ctu")) {
// ctu is specified in the config file
if (!obj["ctu"].is<bool>())
return "Loading " + fileName + " failed. ctu must be array.";
ctu = obj["ctu"].get<bool>();
} else {
ctu = false;
}
if (obj.count("python")) { if (obj.count("python")) {
// Python was defined in the config file // Python was defined in the config file
if (obj["python"].is<picojson::array>()) { if (obj["python"].is<picojson::array>()) {
@ -187,6 +197,20 @@ static std::vector<std::string> split(const std::string &str, const std::string
return ret; return ret;
} }
static std::string getDumpFileName(const Settings& settings, const std::string& filename)
{
if (!settings.dumpFile.empty())
return settings.dumpFile;
if (!settings.dump && !settings.buildDir.empty())
return AnalyzerInformation::getAnalyzerInfoFile(settings.buildDir, filename, "") + ".dump";
return filename + ".dump";
}
static std::string getCtuInfoFileName(const std::string &dumpFile)
{
return dumpFile.substr(0, dumpFile.size()-4) + "ctu-info";
}
static void createDumpFile(const Settings& settings, static void createDumpFile(const Settings& settings,
const std::string& filename, const std::string& filename,
const std::vector<std::string>& files, const std::vector<std::string>& files,
@ -196,16 +220,16 @@ static void createDumpFile(const Settings& settings,
{ {
if (!settings.dump && settings.addons.empty()) if (!settings.dump && settings.addons.empty())
return; return;
if (!settings.dumpFile.empty()) dumpFile = getDumpFileName(settings, filename);
dumpFile = settings.dumpFile;
else if (!settings.dump && !settings.buildDir.empty())
dumpFile = AnalyzerInformation::getAnalyzerInfoFile(settings.buildDir, filename, "") + ".dump";
else
dumpFile = filename + ".dump";
fdump.open(dumpFile); fdump.open(dumpFile);
if (!fdump.is_open()) if (!fdump.is_open())
return; return;
{
std::ofstream fout(getCtuInfoFileName(dumpFile));
}
fdump << "<?xml version=\"1.0\"?>" << std::endl; fdump << "<?xml version=\"1.0\"?>" << std::endl;
fdump << "<dumps>" << std::endl; fdump << "<dumps>" << std::endl;
fdump << " <platform" fdump << " <platform"
@ -235,7 +259,7 @@ static void createDumpFile(const Settings& settings,
static std::string executeAddon(const AddonInfo &addonInfo, static std::string executeAddon(const AddonInfo &addonInfo,
const std::string &defaultPythonExe, const std::string &defaultPythonExe,
const std::string &dumpFile, const std::vector<std::string> &files,
std::function<bool(std::string,std::vector<std::string>,std::string,std::string*)> executeCommand) std::function<bool(std::string,std::vector<std::string>,std::string,std::string*)> executeCommand)
{ {
const std::string redirect = "2>&1"; const std::string redirect = "2>&1";
@ -263,7 +287,9 @@ static std::string executeAddon(const AddonInfo &addonInfo,
throw InternalError(nullptr, "Failed to auto detect python"); throw InternalError(nullptr, "Failed to auto detect python");
} }
const std::string args = cmdFileName(addonInfo.scriptFile) + " --cli" + addonInfo.args + " " + cmdFileName(dumpFile); std::string args = cmdFileName(addonInfo.scriptFile) + " --cli" + addonInfo.args;
for (const std::string& filename: files)
args += " " + cmdFileName(filename);
std::string result; std::string result;
if (!executeCommand(pythonExe, split(args), redirect, &result)) if (!executeCommand(pythonExe, split(args), redirect, &result))
throw InternalError(nullptr, "Failed to execute addon (command: '" + pythonExe + " " + args + "')"); throw InternalError(nullptr, "Failed to execute addon (command: '" + pythonExe + " " + args + "')");
@ -1264,8 +1290,19 @@ void CppCheck::executeRules(const std::string &tokenlist, const Tokenizer &token
void CppCheck::executeAddons(const std::string& dumpFile) void CppCheck::executeAddons(const std::string& dumpFile)
{ {
if (!dumpFile.empty()) {
std::vector<std::string> f{dumpFile};
executeAddons(f);
if (!mSettings.dump && mSettings.buildDir.empty())
std::remove(dumpFile.c_str());
}
}
void CppCheck::executeAddons(const std::vector<std::string>& files)
{
if (mSettings.addons.empty() || files.empty())
return;
if (!mSettings.addons.empty() && !dumpFile.empty()) {
for (const std::string &addon : mSettings.addons) { for (const std::string &addon : mSettings.addons) {
struct AddonInfo addonInfo; struct AddonInfo addonInfo;
const std::string &failedToGetAddonInfo = addonInfo.getAddonInfo(addon, mSettings.exename); const std::string &failedToGetAddonInfo = addonInfo.getAddonInfo(addon, mSettings.exename);
@ -1274,8 +1311,11 @@ void CppCheck::executeAddons(const std::string& dumpFile)
mExitCode = 1; mExitCode = 1;
continue; continue;
} }
if (addon != "misra" && !addonInfo.ctu && endsWith(files.back(), ".ctu-info", 9))
continue;
const std::string results = const std::string results =
executeAddon(addonInfo, mSettings.addonPython, dumpFile, mExecuteCommand); executeAddon(addonInfo, mSettings.addonPython, files, mExecuteCommand);
std::istringstream istr(results); std::istringstream istr(results);
std::string line; std::string line;
@ -1311,9 +1351,20 @@ void CppCheck::executeAddons(const std::string& dumpFile)
reportErr(errmsg); reportErr(errmsg);
} }
} }
std::remove(dumpFile.c_str());
} }
void CppCheck::executeAddonsWholeProgram(const std::map<std::string, std::size_t> &files)
{
if (mSettings.addons.empty())
return;
std::vector<std::string> ctuInfoFiles;
for (const auto f: files) {
const std::string &dumpFileName = getDumpFileName(mSettings, f.first);
ctuInfoFiles.push_back(getCtuInfoFileName(dumpFileName));
}
executeAddons(ctuInfoFiles);
} }
Settings &CppCheck::settings() Settings &CppCheck::settings()
@ -1578,7 +1629,7 @@ bool CppCheck::analyseWholeProgram()
void CppCheck::analyseWholeProgram(const std::string &buildDir, const std::map<std::string, std::size_t> &files) void CppCheck::analyseWholeProgram(const std::string &buildDir, const std::map<std::string, std::size_t> &files)
{ {
(void)files; executeAddonsWholeProgram(files);
if (buildDir.empty()) if (buildDir.empty())
return; return;
if (mSettings.checks.isEnabled(Checks::unusedFunction)) if (mSettings.checks.isEnabled(Checks::unusedFunction))

View File

@ -172,8 +172,14 @@ private:
/** /**
* Execute addons * Execute addons
*/ */
void executeAddons(const std::vector<std::string>& files);
void executeAddons(const std::string &dumpFile); void executeAddons(const std::string &dumpFile);
/**
* Execute addons
*/
void executeAddonsWholeProgram(const std::map<std::string, std::size_t> &files);
/** /**
* @brief Execute rules, if any * @brief Execute rules, if any
* @param tokenlist token list to use (normal / simple) * @param tokenlist token list to use (normal / simple)

View File

@ -1044,6 +1044,13 @@ void Tokenizer::simplifyTypedef()
bool done = false; bool done = false;
bool ok = true; bool ok = true;
TypedefInfo typedefInfo;
typedefInfo.name = typeName->str();
typedefInfo.filename = list.file(typeName);
typedefInfo.lineNumber = typeName->linenr();
typedefInfo.used = false;
mTypedefInfo.push_back(typedefInfo);
while (!done) { while (!done) {
std::string pattern = typeName->str(); std::string pattern = typeName->str();
int scope = 0; int scope = 0;
@ -1254,6 +1261,8 @@ void Tokenizer::simplifyTypedef()
} }
if (simplifyType) { if (simplifyType) {
mTypedefInfo.back().used = true;
// can't simplify 'operator functionPtr ()' and 'functionPtr operator ... ()' // can't simplify 'operator functionPtr ()' and 'functionPtr operator ... ()'
if (functionPtr && (tok2->previous()->str() == "operator" || if (functionPtr && (tok2->previous()->str() == "operator" ||
(tok2->next() && tok2->next()->str() == "operator"))) { (tok2->next() && tok2->next()->str() == "operator"))) {
@ -5480,6 +5489,19 @@ void Tokenizer::dump(std::ostream &out) const
mSymbolDatabase->printXml(out); mSymbolDatabase->printXml(out);
if (list.front()) if (list.front())
list.front()->printValueFlow(true, out); list.front()->printValueFlow(true, out);
if (!mTypedefInfo.empty()) {
out << " <typedef-info>" << std::endl;
for (const TypedefInfo &typedefInfo: mTypedefInfo) {
out << " <info"
<< " name=\"" << typedefInfo.name << "\""
<< " file=\"" << typedefInfo.filename << "\""
<< " line=\"" << typedefInfo.lineNumber << "\""
<< " used=\"" << (typedefInfo.used?1:0) << "\""
<< "/>" << std::endl;
}
out << " </typedef-info>" << std::endl;
}
} }
void Tokenizer::simplifyHeadersAndUnusedTemplates() void Tokenizer::simplifyHeadersAndUnusedTemplates()

View File

@ -987,6 +987,14 @@ private:
/** sizeof information for known types */ /** sizeof information for known types */
std::map<std::string, int> mTypeSize; std::map<std::string, int> mTypeSize;
struct TypedefInfo {
std::string name;
std::string filename;
int lineNumber;
bool used;
};
std::vector<TypedefInfo> mTypedefInfo;
/** variable count */ /** variable count */
nonneg int mVarId; nonneg int mVarId;