From 599c1bd217c2ced79047f0f733aeb3edba6ea5a9 Mon Sep 17 00:00:00 2001 From: firewave Date: Thu, 13 Nov 2025 01:03:48 +0100 Subject: [PATCH 1/3] test/cli/other_test.py: added some tests for preprocessor errors --- test/cli/other_test.py | 102 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) diff --git a/test/cli/other_test.py b/test/cli/other_test.py index d071a080548..2e1e2099b88 100644 --- a/test/cli/other_test.py +++ b/test/cli/other_test.py @@ -3847,3 +3847,105 @@ def test_unmatched_file(tmp_path): # #14248 / #14249 f'{lib_file}:-1:0: information: Unmatched suppression: error6 [unmatchedSuppression]' ] assert ret == 0, stdout + + +def test_simplecpp_warning(tmp_path): + test_file = tmp_path / 'test.c' + with open(test_file, "w") as f: + f.write( +""" +#define warning "warn msg" +""") + + args = [ + '-q', + '--template=simple', + str(test_file) + ] + + exitcode, stdout, stderr = cppcheck(args) + assert exitcode == 0, stdout + assert stdout.splitlines() == [] + assert stderr.splitlines() == [] + + +def test_simplecpp_unhandled_char(tmp_path): + test_file = tmp_path / 'test.c' + with open(test_file, "w", encoding='utf-8') as f: + f.write( +""" +int 你=0; +""") + + args = [ + '-q', + '--template=simple', + '--emit-duplicates', + str(test_file) + ] + + exitcode, stdout, stderr = cppcheck(args) + assert exitcode == 0, stdout + assert stdout.splitlines() == [] + assert stderr.splitlines() == [ + # TODO: should report another ID + '{}:2:5: error: The code contains unhandled character(s) (character code=228). Neither unicode nor extended ascii is supported. [syntaxError]'.format(test_file) + ] + + +def test_simplecpp_include_nested_too_deeply(tmp_path): + test_file = tmp_path / 'test.c' + with open(test_file, "w") as f: + f.write('#include "test.h"') + + test_h = tmp_path / 'test.h' + with open(test_h, "w") as f: + f.write('#include "test_0.h"') + + for i in range(400): + test_h = tmp_path / f'test_{i}.h' + with open(test_h, "w") as f: + f.write('#include "test_{}.h"'.format(i+1)) + + args = [ + '-q', + '--template=simple', + '--emit-duplicates', + str(test_file) + ] + + exitcode, stdout, stderr = cppcheck(args) + assert exitcode == 0, stdout + assert stdout.splitlines() == [] + test_h = tmp_path / 'test_398.h' + assert stderr.splitlines() == [ + # TODO: should only report the error once + # TODO: should report another ID + # TODO: lacks column information + '{}:1:0: error: #include nested too deeply [preprocessorErrorDirective]'.format(test_h), + '{}:1:2: error: #include nested too deeply [preprocessorErrorDirective]'.format(test_h) + ] + + +def test_simplecpp_syntax_error(tmp_path): + test_file = tmp_path / 'test.c' + with open(test_file, "w") as f: + f.write('#include ""') + + args = [ + '-q', + '--template=simple', + '--emit-duplicates', + str(test_file) + ] + + exitcode, stdout, stderr = cppcheck(args) + assert exitcode == 0, stdout + assert stdout.splitlines() == [] + assert stderr.splitlines() == [ + # TODO: should only report the error once + # TODO: should report another ID + # TODO: lacks column information + '{}:1:0: error: No header in #include [preprocessorErrorDirective]'.format(test_file), + '{}:1:2: error: No header in #include [preprocessorErrorDirective]'.format(test_file) + ] From 810fa3f36e988e83e49b96b7368a322b4b58d68b Mon Sep 17 00:00:00 2001 From: firewave Date: Tue, 21 Oct 2025 15:11:11 +0200 Subject: [PATCH 2/3] got rid of duplicated error reporting code in `CppCheck::checkInternal()` / cleanups --- lib/cppcheck.cpp | 25 +++---------------------- lib/preprocessor.h | 5 ++--- test/cli/other_test.py | 3 ++- test/cli/project_test.py | 7 +++++-- test/testpreprocessor.cpp | 13 ++----------- test/testsuppressions.cpp | 16 ++++++++-------- 6 files changed, 22 insertions(+), 47 deletions(-) diff --git a/lib/cppcheck.cpp b/lib/cppcheck.cpp index 0c0144c63ee..3c0b8e82808 100644 --- a/lib/cppcheck.cpp +++ b/lib/cppcheck.cpp @@ -977,30 +977,11 @@ unsigned int CppCheck::checkInternal(const FileWithDetails& file, const std::str std::vector files; simplecpp::TokenList tokens1 = createTokenList(files, &outputList); - // If there is a syntax error, report it and stop - const auto output_it = std::find_if(outputList.cbegin(), outputList.cend(), [](const simplecpp::Output &output){ - return Preprocessor::hasErrors(output); - }); - if (output_it != outputList.cend()) { - const simplecpp::Output &output = *output_it; - std::string locfile = Path::fromNativeSeparators(output.location.file()); - if (mSettings.relativePaths) - locfile = Path::getRelativePath(locfile, mSettings.basePaths); - - ErrorMessage::FileLocation loc1(locfile, output.location.line, output.location.col); - - ErrorMessage errmsg({std::move(loc1)}, - "", // TODO: is this correct? - Severity::error, - output.msg, - "syntaxError", - Certainty::normal); - mErrorLogger.reportErr(errmsg); - return mLogger->exitcode(); - } - Preprocessor preprocessor(tokens1, mSettings, mErrorLogger, file.lang()); + if (preprocessor.reportOutput(outputList, true)) + return mLogger->exitcode(); + if (!preprocessor.loadFiles(files)) return mLogger->exitcode(); diff --git a/lib/preprocessor.h b/lib/preprocessor.h index 2b5fef5158e..926b7ce1824 100644 --- a/lib/preprocessor.h +++ b/lib/preprocessor.h @@ -140,12 +140,11 @@ class CPPCHECKLIB WARN_UNUSED Preprocessor { */ void dump(std::ostream &out) const; - static bool hasErrors(const simplecpp::Output &output); - -protected: bool reportOutput(const simplecpp::OutputList &outputList, bool showerror); private: + static bool hasErrors(const simplecpp::Output &output); + bool handleErrors(const simplecpp::OutputList &outputList, bool throwError); static void simplifyPragmaAsmPrivate(simplecpp::TokenList &tokenList); diff --git a/test/cli/other_test.py b/test/cli/other_test.py index 2e1e2099b88..f1fc310cf33 100644 --- a/test/cli/other_test.py +++ b/test/cli/other_test.py @@ -3888,8 +3888,9 @@ def test_simplecpp_unhandled_char(tmp_path): assert exitcode == 0, stdout assert stdout.splitlines() == [] assert stderr.splitlines() == [ + # TODO: lacks column information # TODO: should report another ID - '{}:2:5: error: The code contains unhandled character(s) (character code=228). Neither unicode nor extended ascii is supported. [syntaxError]'.format(test_file) + '{}:2:0: error: The code contains unhandled character(s) (character code=228). Neither unicode nor extended ascii is supported. [preprocessorErrorDirective]'.format(test_file) ] diff --git a/test/cli/project_test.py b/test/cli/project_test.py index 47752e08bd4..25fcd587b73 100644 --- a/test/cli/project_test.py +++ b/test/cli/project_test.py @@ -59,9 +59,12 @@ def test_json_entry_file_not_found(tmpdir): with open(project_file, 'w') as f: f.write(json.dumps(compilation_db)) - ret, _, stderr = cppcheck(["--project=" + str(project_file)]) + ret, _, stderr = cppcheck([ + '--template=simple', + "--project=" + str(project_file) + ]) assert 0 == ret - assert f"{missing_file}:1:0: error: File is missing: {missing_file_posix} [syntaxError]\n\n^\n" == stderr + assert stderr == f"nofile:0:0: error: File is missing: {missing_file_posix} [preprocessorErrorDirective]\n" def test_json_no_arguments(tmpdir): diff --git a/test/testpreprocessor.cpp b/test/testpreprocessor.cpp index ea49ddbd7d1..1c8f570750e 100644 --- a/test/testpreprocessor.cpp +++ b/test/testpreprocessor.cpp @@ -49,21 +49,12 @@ class TestPreprocessor : public TestFixture { TestPreprocessor() : TestFixture("TestPreprocessor") {} private: - class PreprocessorTest : public Preprocessor - { - friend class TestPreprocessor; - public: - PreprocessorTest(simplecpp::TokenList& tokens, const Settings& settings, ErrorLogger &errorLogger, Standards::Language lang) - : Preprocessor(tokens, settings, errorLogger, lang) - {} - }; - template std::string expandMacros(const char (&code)[size], ErrorLogger &errorLogger) const { simplecpp::OutputList outputList; std::vector files; simplecpp::TokenList tokens1 = simplecpp::TokenList(code, files, "file.cpp", &outputList); - PreprocessorTest p(tokens1, settingsDefault, errorLogger, Path::identify(tokens1.getFiles()[0], false)); + Preprocessor p(tokens1, settingsDefault, errorLogger, Path::identify(tokens1.getFiles()[0], false)); simplecpp::TokenList tokens2 = p.preprocess("", files, true); (void)p.reportOutput(outputList, true); return tokens2.stringify(); @@ -128,7 +119,7 @@ class TestPreprocessor : public TestFixture { simplecpp::TokenList tokens(code, size, files, Path::simplifyPath(filename), &outputList); // TODO: we should be using the actual Preprocessor implementation - PreprocessorTest preprocessor(tokens, settings, errorlogger, Path::identify(tokens.getFiles()[0], false)); + Preprocessor preprocessor(tokens, settings, errorlogger, Path::identify(tokens.getFiles()[0], false)); // TODO: should be possible without a Preprocessor instance if (preprocessor.reportOutput(outputList, true)) diff --git a/test/testsuppressions.cpp b/test/testsuppressions.cpp index eabfbe7b9e2..a1abdf1fb58 100644 --- a/test/testsuppressions.cpp +++ b/test/testsuppressions.cpp @@ -88,8 +88,8 @@ class TestSuppressions : public TestFixture { TEST_CASE(suppressingSyntaxErrorsFS); // #7076 TEST_CASE(suppressingSyntaxErrorsInlineFiles); // #5917 TEST_CASE(suppressingSyntaxErrorsInlineFS); // #5917 - TEST_CASE(suppressingSyntaxErrorsWhileFileReadFiles); // PR #1333 - TEST_CASE(suppressingSyntaxErrorsWhileFileReadFS); // PR #1333 + TEST_CASE(suppressingSimplecppErrorsWhileFileReadFiles); // PR #1333 + TEST_CASE(suppressingSimplecppErrorsWhileFileReadFS); // PR #1333 TEST_CASE(symbol); TEST_CASE(unusedFunctionFiles); @@ -1341,7 +1341,7 @@ class TestSuppressions : public TestFixture { suppressingSyntaxErrorsInlineInternal(&TestSuppressions::checkSuppressionFS); } - void suppressingSyntaxErrorsWhileFileReadInternal(unsigned int (TestSuppressions::*check)(const char[], const std::string &)) { // syntaxError while file read should be suppressible (PR #1333) + void suppressingSimplecppErrorsWhileFileReadInternal(unsigned int (TestSuppressions::*check)(const char[], const std::string &)) { // syntaxError while file read should be suppressible (PR #1333) const char code[] = "CONST (genType, KS_CONST) genService[KS_CFG_NR_OF_NVM_BLOCKS] =\n" "{\n" "[!VAR \"BC\" = \"$BC + 1\"!][!//\n" @@ -1355,16 +1355,16 @@ class TestSuppressions : public TestFixture { "[!VAR \"BC\" = \"$BC + 1\"!][!//\n" "[!ENDIF!][!//\n" "};"; - ASSERT_EQUALS(0, (this->*check)(code, "syntaxError:test.cpp:4")); + ASSERT_EQUALS(0, (this->*check)(code, "preprocessorErrorDirective:test.cpp:4")); ASSERT_EQUALS("", errout_str()); } - void suppressingSyntaxErrorsWhileFileReadFiles() { - suppressingSyntaxErrorsWhileFileReadInternal(&TestSuppressions::checkSuppressionFiles); + void suppressingSimplecppErrorsWhileFileReadFiles() { + suppressingSimplecppErrorsWhileFileReadInternal(&TestSuppressions::checkSuppressionFiles); } - void suppressingSyntaxErrorsWhileFileReadFS() { - suppressingSyntaxErrorsWhileFileReadInternal(&TestSuppressions::checkSuppressionFiles); + void suppressingSimplecppErrorsWhileFileReadFS() { + suppressingSimplecppErrorsWhileFileReadInternal(&TestSuppressions::checkSuppressionFiles); } // TODO: this tests an internal function - should it be private? From e287f2453dfa2fbfddda2fc878a6aa15964edbe2 Mon Sep 17 00:00:00 2001 From: firewave Date: Mon, 17 Nov 2025 16:56:52 +0100 Subject: [PATCH 3/3] got rid of some redundant error reporting code in `CppCheck::checkInternal()` --- lib/cppcheck.cpp | 14 +------------- lib/preprocessor.cpp | 2 +- lib/preprocessor.h | 5 +++-- test/cli/other_test.py | 7 ++++--- 4 files changed, 9 insertions(+), 19 deletions(-) diff --git a/lib/cppcheck.cpp b/lib/cppcheck.cpp index 3c0b8e82808..ccc6c7a0995 100644 --- a/lib/cppcheck.cpp +++ b/lib/cppcheck.cpp @@ -1213,19 +1213,7 @@ unsigned int CppCheck::checkInternal(const FileWithDetails& file, const std::str if (!hasValidConfig && currCfg == *configurations.rbegin()) { // If there is no valid configuration then report error.. - std::string locfile = Path::fromNativeSeparators(o.location.file()); - if (mSettings.relativePaths) - locfile = Path::getRelativePath(locfile, mSettings.basePaths); - - ErrorMessage::FileLocation loc1(locfile, o.location.line, o.location.col); - - ErrorMessage errmsg({std::move(loc1)}, - file.spath(), - Severity::error, - o.msg, - "preprocessorErrorDirective", - Certainty::normal); - mErrorLogger.reportErr(errmsg); + preprocessor.error(o.location.file(), o.location.line, o.msg); } continue; diff --git a/lib/preprocessor.cpp b/lib/preprocessor.cpp index 75c20292365..cb1c93aaf7f 100644 --- a/lib/preprocessor.cpp +++ b/lib/preprocessor.cpp @@ -899,7 +899,7 @@ void Preprocessor::error(const std::string &filename, unsigned int linenr, const if (mSettings.relativePaths) file = Path::getRelativePath(file, mSettings.basePaths); - locationList.emplace_back(file, linenr, 0); + locationList.emplace_back(file, linenr, 0); // TODO: set column } mErrorLogger.reportErr(ErrorMessage(std::move(locationList), mFile0, diff --git a/lib/preprocessor.h b/lib/preprocessor.h index 926b7ce1824..0547213ddc5 100644 --- a/lib/preprocessor.h +++ b/lib/preprocessor.h @@ -142,6 +142,8 @@ class CPPCHECKLIB WARN_UNUSED Preprocessor { bool reportOutput(const simplecpp::OutputList &outputList, bool showerror); + void error(const std::string &filename, unsigned int linenr, const std::string &msg); + private: static bool hasErrors(const simplecpp::Output &output); @@ -158,7 +160,6 @@ class CPPCHECKLIB WARN_UNUSED Preprocessor { }; void missingInclude(const std::string &filename, unsigned int linenr, unsigned int col, const std::string &header, HeaderTypes headerType); - void error(const std::string &filename, unsigned int linenr, const std::string &msg); void addRemarkComments(const simplecpp::TokenList &tokens, std::vector &remarkComments) const; @@ -172,7 +173,7 @@ class CPPCHECKLIB WARN_UNUSED Preprocessor { simplecpp::FileDataCache mFileCache; /** filename for cpp/c file - useful when reporting errors */ - std::string mFile0; + std::string mFile0; // TODO: this is never set Standards::Language mLang{Standards::Language::None}; /** simplecpp tracking info */ diff --git a/test/cli/other_test.py b/test/cli/other_test.py index f1fc310cf33..b6c8f2392eb 100644 --- a/test/cli/other_test.py +++ b/test/cli/other_test.py @@ -3429,7 +3429,8 @@ def test_preprocess_enforced_cpp(tmp_path): # #10989 assert exitcode == 0, stdout if stdout else stderr assert stdout.splitlines() == [] assert stderr.splitlines() == [ - '{}:2:2: error: #error "err" [preprocessorErrorDirective]'.format(test_file) + # TODO: lacks column information + '{}:2:0: error: #error "err" [preprocessorErrorDirective]'.format(test_file) ] @@ -3924,7 +3925,7 @@ def test_simplecpp_include_nested_too_deeply(tmp_path): # TODO: should report another ID # TODO: lacks column information '{}:1:0: error: #include nested too deeply [preprocessorErrorDirective]'.format(test_h), - '{}:1:2: error: #include nested too deeply [preprocessorErrorDirective]'.format(test_h) + '{}:1:0: error: #include nested too deeply [preprocessorErrorDirective]'.format(test_h) ] @@ -3948,5 +3949,5 @@ def test_simplecpp_syntax_error(tmp_path): # TODO: should report another ID # TODO: lacks column information '{}:1:0: error: No header in #include [preprocessorErrorDirective]'.format(test_file), - '{}:1:2: error: No header in #include [preprocessorErrorDirective]'.format(test_file) + '{}:1:0: error: No header in #include [preprocessorErrorDirective]'.format(test_file) ]