Skip to content

Commit

Permalink
added command-line option --cpp-probe to probe headers and extensio…
Browse files Browse the repository at this point in the history
…n-less files for Emacs marker [skip ci]
  • Loading branch information
firewave committed Apr 22, 2024
1 parent d20f324 commit 516107a
Show file tree
Hide file tree
Showing 11 changed files with 245 additions and 51 deletions.
8 changes: 8 additions & 0 deletions cli/cmdlineparser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,10 @@ CmdLineParser::Result CmdLineParser::parseFromArgs(int argc, const char* const a
}
}

else if (std::strcmp(argv[i], "--cpp-probe") == 0) {
mSettings.cppProbe = true;
}

// Show --debug output after the first simplifications
else if (std::strcmp(argv[i], "--debug") == 0 ||
std::strcmp(argv[i], "--debug-normal") == 0)
Expand Down Expand Up @@ -887,6 +891,10 @@ CmdLineParser::Result CmdLineParser::parseFromArgs(int argc, const char* const a
return Result::Fail;
}

else if (std::strcmp(argv[i], "--no-cpp-probe") == 0) {
mSettings.cppProbe = false;
}

// Write results in file
else if (std::strncmp(argv[i], "--output-file=", 14) == 0)
mSettings.outputFile = Path::simplifyPath(Path::fromNativeSeparators(argv[i] + 14));
Expand Down
6 changes: 3 additions & 3 deletions lib/cppcheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ static void createDumpFile(const Settings& settings,
case Standards::Language::None:
{
// TODO: error out on unknown language?
const Standards::Language lang = Path::identify(filename);
const Standards::Language lang = Path::identify(filename, settings.cppProbe);
if (lang == Standards::Language::CPP)
language = " language=\"cpp\"";
else if (lang == Standards::Language::C)
Expand Down Expand Up @@ -420,7 +420,7 @@ unsigned int CppCheck::checkClang(const std::string &path)
mErrorLogger.reportOut(std::string("Checking ") + path + " ...", Color::FgGreen);

// TODO: this ignores the configured language
const bool isCpp = Path::identify(path) == Standards::Language::CPP;
const bool isCpp = Path::identify(path, mSettings.cppProbe) == Standards::Language::CPP;
const std::string langOpt = isCpp ? "-x c++" : "-x c";
const std::string analyzerInfo = mSettings.buildDir.empty() ? std::string() : AnalyzerInformation::getAnalyzerInfoFile(mSettings.buildDir, path, emptyString);
const std::string clangcmd = analyzerInfo + ".clang-cmd";
Expand Down Expand Up @@ -783,7 +783,7 @@ unsigned int CppCheck::checkFile(const std::string& filename, const std::string
TokenList tokenlist(&mSettings);
std::istringstream istr2(code);
// TODO: asserts when file has unknown extension
tokenlist.createTokens(istr2, Path::identify(*files.begin())); // TODO: check result?
tokenlist.createTokens(istr2, Path::identify(*files.begin(), false)); // TODO: check result?
executeRules("define", tokenlist);
}
#endif
Expand Down
71 changes: 67 additions & 4 deletions lib/path.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@
#include "utils.h"

#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <memory>
#include <sys/stat.h>
#include <unordered_set>
#include <utility>
Expand Down Expand Up @@ -235,7 +237,7 @@ bool Path::isCPP(const std::string &path)
bool Path::acceptFile(const std::string &path, const std::set<std::string> &extra)
{
bool header = false;
return (identify(path, &header) != Standards::Language::None && !header) || extra.find(getFilenameExtension(path)) != extra.end();
return (identify(path, false, &header) != Standards::Language::None && !header) || extra.find(getFilenameExtension(path)) != extra.end();
}

// cppcheck-suppress unusedFunction
Expand All @@ -245,13 +247,72 @@ bool Path::isHeader(const std::string &path)
return startsWith(extension, ".h");
}

Standards::Language Path::identify(const std::string &path, bool *header)
#include <iostream>

static bool hasEmacsCppMarker(const char* path)
{
FILE *fp = fopen(path, "rt");
if (!fp)
return false;
std::unique_ptr<FILE, decltype(&fclose)> fp_deleter(fp, fclose);
std::string buf(1024, '\0');
// TODO: read first line only
if (fgets(const_cast<char*>(buf.data()), buf.size(), fp) == nullptr)
return false; // failed to read file
// TODO: replace with regular expression
const auto pos1 = buf.find("-*-");
if (pos1 == std::string::npos)
return false; // no start marker
const auto pos_nl = buf.find_first_of("\r\n");
if (pos_nl != std::string::npos && (pos_nl < pos1))
return false; // not on first line
const auto pos2 = buf.find("-*-", pos1 + 3);
// TODO: make sure we have read the whole line before bailing out
if (pos2 == std::string::npos)
return false; // no end marker
const std::string buf_trim = trim(buf); // trim whitespaces
if (buf_trim[0] != '/' || buf_trim[1] != '/')
return false; // not a comment

std::cout /*<< path << " -*/ << "Emacs marker: '" << buf.substr(pos1, (pos2 + 3) - pos1) << "'" << std::endl;

// there are more variations with lowercase and no whitespaces
// -*- C++ -*-
// -*- Mode: C++; -*-
// -*- Mode: C++; c-basic-offset: 8 -*-
std::string marker = trim(buf.substr(pos1 + 3, pos2 - pos1 - 3), " ;");
// cut off additional attributes
const auto pos_semi = marker.find(';');
if (pos_semi != std::string::npos)
marker.resize(pos_semi);
findAndReplace(marker, "mode:", "");
findAndReplace(marker, "Mode:", "");
marker = trim(marker);
if (marker == "C++" || marker == "c++")
return true; // C++ marker found

//if (marker == "C" || marker == "c")
// return false;
std::cout << path << " - unmatched Emacs marker: '" << marker << "'" << std::endl;

return false; // marker is not a C++ one
}

Standards::Language Path::identify(const std::string &path, bool cppProbe, bool *header)
{
// cppcheck-suppress uninitvar - TODO: FP
if (header)
*header = false;

std::string ext = getFilenameExtension(path);
// standard library headers have no extension
if (cppProbe && ext.empty()) {
if (hasEmacsCppMarker(path.c_str())) {
if (header)
*header = true;
return Standards::Language::CPP;
}
}
if (ext == ".C")
return Standards::Language::CPP;
if (c_src_exts.find(ext) != c_src_exts.end())
Expand All @@ -262,7 +323,9 @@ Standards::Language Path::identify(const std::string &path, bool *header)
if (ext == ".h") {
if (header)
*header = true;
return Standards::Language::C; // treat as C for now
if (cppProbe && hasEmacsCppMarker(path.c_str()))
return Standards::Language::CPP;
return Standards::Language::C;
}
if (cpp_src_exts.find(ext) != cpp_src_exts.end())
return Standards::Language::CPP;
Expand All @@ -277,7 +340,7 @@ Standards::Language Path::identify(const std::string &path, bool *header)
bool Path::isHeader2(const std::string &path)
{
bool header;
(void)Path::identify(path, &header);
(void)identify(path, false, &header);
return header;
}

Expand Down
3 changes: 2 additions & 1 deletion lib/path.h
Original file line number Diff line number Diff line change
Expand Up @@ -187,10 +187,11 @@ class CPPCHECKLIB Path {
/**
* @brief Identify the language based on the file extension
* @param path filename to check. path info is optional
* @param cppProbe check optional Emacs marker to idengtify headers as C++
* @param header if provided indicates if the file is a header
* @return the language type
*/
static Standards::Language identify(const std::string &path, bool *header = nullptr);
static Standards::Language identify(const std::string &path, bool cppProbe, bool *header = nullptr);

/**
* @brief Get filename without a directory path part.
Expand Down
2 changes: 1 addition & 1 deletion lib/preprocessor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -684,7 +684,7 @@ static simplecpp::DUI createDUI(const Settings &mSettings, const std::string &cf
dui.includes = mSettings.userIncludes; // --include
// TODO: use mSettings.standards.stdValue instead
// TODO: error out on unknown language?
const Standards::Language lang = Path::identify(filename);
const Standards::Language lang = Path::identify(filename, mSettings.cppProbe);
if (lang == Standards::Language::CPP) {
dui.std = mSettings.standards.getCPP();
splitcfg(mSettings.platform.getLimitsDefines(Standards::getCPP(dui.std)), dui.defines, "");
Expand Down
3 changes: 3 additions & 0 deletions lib/settings.h
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,9 @@ class CPPCHECKLIB WARN_UNUSED Settings {
/** cppcheck.cfg: About text */
std::string cppcheckCfgAbout;

/** @brief check Emacs marker to detect header files as C++ */
bool cppProbe{};

/** @brief Are we running from DACA script? */
bool daca{};

Expand Down
2 changes: 1 addition & 1 deletion lib/tokenlist.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ void TokenList::determineCppC()
{
// only try to determine if it wasn't enforced
if (mLang == Standards::Language::None) {
mLang = Path::identify(getSourceFilePath());
mLang = Path::identify(getSourceFilePath(), mSettings ? mSettings->cppProbe : false);
// TODO: cannot enable assert as this might occur for unknown extensions
//ASSERT_LANG(mLang != Standards::Language::None);
if (mLang == Standards::Language::None) {
Expand Down
31 changes: 30 additions & 1 deletion test/cli/other_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -1357,4 +1357,33 @@ def test_rule(tmpdir):
lines = stderr.splitlines()
assert lines == [
"{}:4:0: style: found 'f' [rule]".format(test_file)
]
]


def test_cpp_probe(tmpdir):
test_file = os.path.join(tmpdir, 'test.h')
with open(test_file, 'wt') as f:
f.writelines([
'class A {};'
])

args = ['-q', '--template=simple', '--cpp-probe', test_file]
err_lines = [
"{}:1:1: error: Code 'classA{{' is invalid C code. Use --std or --language to configure the language. [syntaxError]".format(test_file)
]

assert_cppcheck(args, ec_exp=0, err_exp=err_lines, out_exp=[])


def test_cpp_probe_2(tmpdir):
test_file = os.path.join(tmpdir, 'test.h')
with open(test_file, 'wt') as f:
f.writelines([
'// -*- C++ -*-',
'class A {};'
])

# TODO: the probing is performed twice
args = ['-q', '--template=simple', '--cpp-probe', test_file]

assert_cppcheck(args, ec_exp=0, err_exp=[], out_exp=[])
4 changes: 2 additions & 2 deletions test/cli/testutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ def assert_cppcheck(args, ec_exp=None, out_exp=None, err_exp=None, env=None):
assert exitcode == ec_exp, stdout
if out_exp is not None:
out_lines = stdout.splitlines()
assert out_lines == out_exp, stdout
assert out_lines == out_exp, out_lines
if err_exp is not None:
err_lines = stderr.splitlines()
assert err_lines == err_exp, stderr
assert err_lines == err_exp, err_lines
32 changes: 32 additions & 0 deletions test/testcmdlineparser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,10 @@ class TestCmdlineParser : public TestFixture {
TEST_CASE(checkLevelNormal);
TEST_CASE(checkLevelExhaustive);
TEST_CASE(checkLevelUnknown);
TEST_CASE(cppProbe);
TEST_CASE(cppProbe2);
TEST_CASE(noCppProbe);
TEST_CASE(noCppProbe2);

TEST_CASE(ignorepaths1);
TEST_CASE(ignorepaths2);
Expand Down Expand Up @@ -2603,6 +2607,34 @@ class TestCmdlineParser : public TestFixture {
ASSERT_EQUALS("cppcheck: error: unknown '--check-level' value 'default'.\n", logger->str());
}

void cppProbe() {
REDIRECT;
const char * const argv[] = {"cppcheck", "--cpp-probe", "file.cpp"};
ASSERT_EQUALS_ENUM(CmdLineParser::Result::Success, parser->parseFromArgs(3, argv));
ASSERT_EQUALS(true, settings->cppProbe);
}

void cppProbe2() {
REDIRECT;
const char * const argv[] = {"cppcheck", "--no-cpp-probe", "--cpp-probe", "file.cpp"};
ASSERT_EQUALS_ENUM(CmdLineParser::Result::Success, parser->parseFromArgs(4, argv));
ASSERT_EQUALS(true, settings->cppProbe);
}

void noCppProbe() {
REDIRECT;
const char * const argv[] = {"cppcheck", "--no-cpp-probe", "file.cpp"};
ASSERT_EQUALS_ENUM(CmdLineParser::Result::Success, parser->parseFromArgs(3, argv));
ASSERT_EQUALS(false, settings->cppProbe);
}

void noCppProbe2() {
REDIRECT;
const char * const argv[] = {"cppcheck", "--cpp-probe", "--no-cpp-probe", "file.cpp"};
ASSERT_EQUALS_ENUM(CmdLineParser::Result::Success, parser->parseFromArgs(4, argv));
ASSERT_EQUALS(false, settings->cppProbe);
}

void ignorepaths1() {
REDIRECT;
const char * const argv[] = {"cppcheck", "-isrc", "file.cpp"};
Expand Down
Loading

0 comments on commit 516107a

Please sign in to comment.