Skip to content

Commit e3a42cf

Browse files
committed
added command-line option --cpp-probe to probe headers and extension-less files for Emacs marker [skip ci]
1 parent d20f324 commit e3a42cf

11 files changed

Lines changed: 221 additions & 50 deletions

cli/cmdlineparser.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -544,6 +544,10 @@ CmdLineParser::Result CmdLineParser::parseFromArgs(int argc, const char* const a
544544
}
545545
}
546546

547+
else if (std::strcmp(argv[i], "--cpp-probe") == 0) {
548+
mSettings.cppProbe = true;
549+
}
550+
547551
// Show --debug output after the first simplifications
548552
else if (std::strcmp(argv[i], "--debug") == 0 ||
549553
std::strcmp(argv[i], "--debug-normal") == 0)
@@ -887,6 +891,10 @@ CmdLineParser::Result CmdLineParser::parseFromArgs(int argc, const char* const a
887891
return Result::Fail;
888892
}
889893

894+
else if (std::strcmp(argv[i], "--no-cpp-probe") == 0) {
895+
mSettings.cppProbe = false;
896+
}
897+
890898
// Write results in file
891899
else if (std::strncmp(argv[i], "--output-file=", 14) == 0)
892900
mSettings.outputFile = Path::simplifyPath(Path::fromNativeSeparators(argv[i] + 14));

lib/cppcheck.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ static void createDumpFile(const Settings& settings,
182182
case Standards::Language::None:
183183
{
184184
// TODO: error out on unknown language?
185-
const Standards::Language lang = Path::identify(filename);
185+
const Standards::Language lang = Path::identify(filename, settings.cppProbe);
186186
if (lang == Standards::Language::CPP)
187187
language = " language=\"cpp\"";
188188
else if (lang == Standards::Language::C)
@@ -420,7 +420,7 @@ unsigned int CppCheck::checkClang(const std::string &path)
420420
mErrorLogger.reportOut(std::string("Checking ") + path + " ...", Color::FgGreen);
421421

422422
// TODO: this ignores the configured language
423-
const bool isCpp = Path::identify(path) == Standards::Language::CPP;
423+
const bool isCpp = Path::identify(path, mSettings.cppProbe) == Standards::Language::CPP;
424424
const std::string langOpt = isCpp ? "-x c++" : "-x c";
425425
const std::string analyzerInfo = mSettings.buildDir.empty() ? std::string() : AnalyzerInformation::getAnalyzerInfoFile(mSettings.buildDir, path, emptyString);
426426
const std::string clangcmd = analyzerInfo + ".clang-cmd";

lib/path.cpp

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@
2424
#include "utils.h"
2525

2626
#include <algorithm>
27+
#include <cstdio>
2728
#include <cstdlib>
29+
#include <memory>
2830
#include <sys/stat.h>
2931
#include <unordered_set>
3032
#include <utility>
@@ -235,7 +237,7 @@ bool Path::isCPP(const std::string &path)
235237
bool Path::acceptFile(const std::string &path, const std::set<std::string> &extra)
236238
{
237239
bool header = false;
238-
return (identify(path, &header) != Standards::Language::None && !header) || extra.find(getFilenameExtension(path)) != extra.end();
240+
return (identify(path, false, &header) != Standards::Language::None && !header) || extra.find(getFilenameExtension(path)) != extra.end();
239241
}
240242

241243
// cppcheck-suppress unusedFunction
@@ -245,13 +247,59 @@ bool Path::isHeader(const std::string &path)
245247
return startsWith(extension, ".h");
246248
}
247249

248-
Standards::Language Path::identify(const std::string &path, bool *header)
250+
#include <iostream>
251+
252+
static bool hasEmacsCppMarker(const char* path)
253+
{
254+
FILE *fp = fopen(path, "rt");
255+
if (!fp)
256+
return false;
257+
std::unique_ptr<FILE, decltype(&fclose)> fp_deleter(fp, fclose);
258+
std::string buf(1024, '\0');
259+
if (fgets(const_cast<char*>(buf.data()), buf.size(), fp) == nullptr)
260+
return false;
261+
// TODO: check if line is a comment
262+
const auto pos1 = buf.find("-*-");
263+
if (pos1 == std::string::npos)
264+
return false;
265+
const auto pos2 = buf.find("-*-", pos1 + 3);
266+
// TODO: make sure we read the whole line before bailing out
267+
if (pos2 == std::string::npos)
268+
return false;
269+
270+
std::cout /*<< path << " -*/ << "Emacs marker: '" << buf.substr(pos1, (pos2 + 3) - pos1) << "'" << std::endl;
271+
272+
// there are more variations with lowercase and no whitespaces
273+
// -*- C++ -*-
274+
// -*- Mode: C++; -*-
275+
// -*- Mode: C++; c-basic-offset: 8 -*-
276+
const auto marker = trim(buf.substr(pos1 + 3, pos2 - pos1 - 3), " ");
277+
// TODO: support extended format with "mode: C++;" and "Mode: C++;"
278+
if (marker == "C++" || marker == "c++")
279+
return true;
280+
281+
//if (marker == "C" || marker == "c")
282+
// return false;
283+
std::cout << path << " - unmatched Emacs marker: '" << marker << "'" << std::endl;
284+
285+
return false;
286+
}
287+
288+
Standards::Language Path::identify(const std::string &path, bool cppProbe, bool *header)
249289
{
250290
// cppcheck-suppress uninitvar - TODO: FP
251291
if (header)
252292
*header = false;
253293

254294
std::string ext = getFilenameExtension(path);
295+
// standard library headers have no extension
296+
if (cppProbe && ext.empty()) {
297+
if (hasEmacsCppMarker(path.c_str())) {
298+
if (header)
299+
*header = true;
300+
return Standards::Language::CPP;
301+
}
302+
}
255303
if (ext == ".C")
256304
return Standards::Language::CPP;
257305
if (c_src_exts.find(ext) != c_src_exts.end())
@@ -262,7 +310,9 @@ Standards::Language Path::identify(const std::string &path, bool *header)
262310
if (ext == ".h") {
263311
if (header)
264312
*header = true;
265-
return Standards::Language::C; // treat as C for now
313+
if (cppProbe && hasEmacsCppMarker(path.c_str()))
314+
return Standards::Language::CPP;
315+
return Standards::Language::C;
266316
}
267317
if (cpp_src_exts.find(ext) != cpp_src_exts.end())
268318
return Standards::Language::CPP;
@@ -277,7 +327,7 @@ Standards::Language Path::identify(const std::string &path, bool *header)
277327
bool Path::isHeader2(const std::string &path)
278328
{
279329
bool header;
280-
(void)Path::identify(path, &header);
330+
(void)identify(path, false, &header);
281331
return header;
282332
}
283333

lib/path.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,10 +187,11 @@ class CPPCHECKLIB Path {
187187
/**
188188
* @brief Identify the language based on the file extension
189189
* @param path filename to check. path info is optional
190+
* @param cppProbe check optional Emacs marker to idengtify headers as C++
190191
* @param header if provided indicates if the file is a header
191192
* @return the language type
192193
*/
193-
static Standards::Language identify(const std::string &path, bool *header = nullptr);
194+
static Standards::Language identify(const std::string &path, bool cppProbe, bool *header = nullptr);
194195

195196
/**
196197
* @brief Get filename without a directory path part.

lib/preprocessor.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -684,7 +684,7 @@ static simplecpp::DUI createDUI(const Settings &mSettings, const std::string &cf
684684
dui.includes = mSettings.userIncludes; // --include
685685
// TODO: use mSettings.standards.stdValue instead
686686
// TODO: error out on unknown language?
687-
const Standards::Language lang = Path::identify(filename);
687+
const Standards::Language lang = Path::identify(filename, mSettings.cppProbe);
688688
if (lang == Standards::Language::CPP) {
689689
dui.std = mSettings.standards.getCPP();
690690
splitcfg(mSettings.platform.getLimitsDefines(Standards::getCPP(dui.std)), dui.defines, "");

lib/settings.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,9 @@ class CPPCHECKLIB WARN_UNUSED Settings {
167167
/** cppcheck.cfg: About text */
168168
std::string cppcheckCfgAbout;
169169

170+
/** @brief check Emacs marker to detect header files as C++ */
171+
bool cppProbe{};
172+
170173
/** @brief Are we running from DACA script? */
171174
bool daca{};
172175

lib/tokenlist.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ void TokenList::determineCppC()
9595
{
9696
// only try to determine if it wasn't enforced
9797
if (mLang == Standards::Language::None) {
98-
mLang = Path::identify(getSourceFilePath());
98+
mLang = Path::identify(getSourceFilePath(), mSettings ? mSettings->cppProbe : false);
9999
// TODO: cannot enable assert as this might occur for unknown extensions
100100
//ASSERT_LANG(mLang != Standards::Language::None);
101101
if (mLang == Standards::Language::None) {

test/cli/other_test.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1357,4 +1357,33 @@ def test_rule(tmpdir):
13571357
lines = stderr.splitlines()
13581358
assert lines == [
13591359
"{}:4:0: style: found 'f' [rule]".format(test_file)
1360-
]
1360+
]
1361+
1362+
1363+
def test_cpp_probe(tmpdir):
1364+
test_file = os.path.join(tmpdir, 'test.h')
1365+
with open(test_file, 'wt') as f:
1366+
f.writelines([
1367+
'class A {};'
1368+
])
1369+
1370+
args = ['-q', '--template=simple', '--cpp-probe', test_file]
1371+
err_lines = [
1372+
"{}:1:1: error: Code 'classA{{' is invalid C code. Use --std or --language to configure the language. [syntaxError]".format(test_file)
1373+
]
1374+
1375+
assert_cppcheck(args, ec_exp=0, err_exp=err_lines, out_exp=[])
1376+
1377+
1378+
def test_cpp_probe_2(tmpdir):
1379+
test_file = os.path.join(tmpdir, 'test.h')
1380+
with open(test_file, 'wt') as f:
1381+
f.writelines([
1382+
'// -*- C++ -*-',
1383+
'class A {};'
1384+
])
1385+
1386+
# TODO: the probing is performed twice
1387+
args = ['-q', '--template=simple', '--cpp-probe', test_file]
1388+
1389+
assert_cppcheck(args, ec_exp=0, err_exp=[], out_exp=[])

test/cli/testutils.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ def assert_cppcheck(args, ec_exp=None, out_exp=None, err_exp=None, env=None):
163163
assert exitcode == ec_exp, stdout
164164
if out_exp is not None:
165165
out_lines = stdout.splitlines()
166-
assert out_lines == out_exp, stdout
166+
assert out_lines == out_exp, out_lines
167167
if err_exp is not None:
168168
err_lines = stderr.splitlines()
169-
assert err_lines == err_exp, stderr
169+
assert err_lines == err_exp, err_lines

test/testcmdlineparser.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,10 @@ class TestCmdlineParser : public TestFixture {
387387
TEST_CASE(checkLevelNormal);
388388
TEST_CASE(checkLevelExhaustive);
389389
TEST_CASE(checkLevelUnknown);
390+
TEST_CASE(cppProbe);
391+
TEST_CASE(cppProbe2);
392+
TEST_CASE(noCppProbe);
393+
TEST_CASE(noCppProbe2);
390394

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

2610+
void cppProbe() {
2611+
REDIRECT;
2612+
const char * const argv[] = {"cppcheck", "--cpp-probe", "file.cpp"};
2613+
ASSERT_EQUALS(CmdLineParser::Result::Success, parser->parseFromArgs(3, argv));
2614+
ASSERT_EQUALS(true, settings->cppProbe);
2615+
}
2616+
2617+
void cppProbe2() {
2618+
REDIRECT;
2619+
const char * const argv[] = {"cppcheck", "--no-cpp-probe", "--cpp-probe", "file.cpp"};
2620+
ASSERT_EQUALS(CmdLineParser::Result::Success, parser->parseFromArgs(4, argv));
2621+
ASSERT_EQUALS(true, settings->cppProbe);
2622+
}
2623+
2624+
void noCppProbe() {
2625+
REDIRECT;
2626+
const char * const argv[] = {"cppcheck", "--no-cpp-probe", "file.cpp"};
2627+
ASSERT_EQUALS(CmdLineParser::Result::Success, parser->parseFromArgs(3, argv));
2628+
ASSERT_EQUALS(false, settings->cppProbe);
2629+
}
2630+
2631+
void noCppProbe2() {
2632+
REDIRECT;
2633+
const char * const argv[] = {"cppcheck", "--cpp-probe", "--no-cpp-probe", "file.cpp"};
2634+
ASSERT_EQUALS(CmdLineParser::Result::Success, parser->parseFromArgs(4, argv));
2635+
ASSERT_EQUALS(false, settings->cppProbe);
2636+
}
2637+
26062638
void ignorepaths1() {
26072639
REDIRECT;
26082640
const char * const argv[] = {"cppcheck", "-isrc", "file.cpp"};

0 commit comments

Comments
 (0)