diff --git a/cli/cmdlineparser.cpp b/cli/cmdlineparser.cpp index 00eee88c66e..2bc22043e87 100644 --- a/cli/cmdlineparser.cpp +++ b/cli/cmdlineparser.cpp @@ -1291,6 +1291,9 @@ CmdLineParser::Result CmdLineParser::parseFromArgs(int argc, const char* const a #ifdef HAVE_RULES Settings::Rule rule; rule.pattern = 7 + argv[i]; +#if defined(HAVE_PCRE2) && !defined(HAVE_PCRE) + rule.engine = Regex::Engine::Pcre2; +#endif if (rule.pattern.empty()) { mLogger.printError("no rule pattern provided."); @@ -1357,9 +1360,16 @@ CmdLineParser::Result CmdLineParser::parseFromArgs(int argc, const char* const a } else if (std::strcmp(subname, "engine") == 0) { const char * const engine = empty_if_null(subtext); +#ifdef HAVE_PCRE if (std::strcmp(engine, "pcre") == 0) { rule.engine = Regex::Engine::Pcre; } +#endif +#ifdef HAVE_PCRE2 + else if (std::strcmp(engine, "pcre2") == 0) { + rule.engine = Regex::Engine::Pcre2; + } +#endif else { mLogger.printError(std::string("unknown regex engine '") + engine + "'."); return Result::Fail; diff --git a/cmake/compilerDefinitions.cmake b/cmake/compilerDefinitions.cmake index 5f03b83dcee..21368773330 100644 --- a/cmake/compilerDefinitions.cmake +++ b/cmake/compilerDefinitions.cmake @@ -37,6 +37,12 @@ endif() if(HAVE_RULES) add_definitions(-DHAVE_RULES) + if(USE_PCRE2) + add_definitions(-DHAVE_PCRE2) + endif() + if(NOT DISABLE_PCRE1) + add_definitions(-DHAVE_PCRE) + endif() endif() if(Boost_FOUND) diff --git a/cmake/findDependencies.cmake b/cmake/findDependencies.cmake index b1f6571504d..31c0c10b29f 100644 --- a/cmake/findDependencies.cmake +++ b/cmake/findDependencies.cmake @@ -29,10 +29,19 @@ if(BUILD_GUI) endif() if(HAVE_RULES) - find_path(PCRE_INCLUDE pcre.h) - find_library(PCRE_LIBRARY NAMES pcre pcred) - if(NOT PCRE_LIBRARY OR NOT PCRE_INCLUDE) - message(FATAL_ERROR "pcre dependency for RULES has not been found") + if(USE_PCRE2) + find_path(PCRE2_INCLUDE pcre2.h) + find_library(PCRE2_LIBRARY NAMES pcre2-8) # TODO: is this the proper library? + if(NOT PCRE2_LIBRARY OR NOT PCRE2_INCLUDE) + message(FATAL_ERROR "PCRE2 dependency for rules has not been found") + endif() + endif() + if(NOT DISABLE_PCRE1) + find_path(PCRE_INCLUDE pcre.h) + find_library(PCRE_LIBRARY NAMES pcre pcred) + if(NOT PCRE_LIBRARY OR NOT PCRE_INCLUDE) + message(FATAL_ERROR "PCRE dependency for rules has not been found") + endif() endif() else() set(PCRE_LIBRARY "") diff --git a/cmake/options.cmake b/cmake/options.cmake index d640710e428..3bfee5fe196 100644 --- a/cmake/options.cmake +++ b/cmake/options.cmake @@ -95,6 +95,17 @@ if(NOT BUILD_GUI) endif() option(HAVE_RULES "Usage of rules (needs PCRE library and headers)" OFF) +option(USE_PCRE2 "Usage of PCRE2 for rules" OFF) +if(NOT HAVE_RULES AND USE_PCRE2) + message(AUTHOR_WARNING "USE_PCRE2 has no effect without HAVE_RULES") +endif() +option(DISABLE_PCRE1 "Disable usage of PCRE1 for rules" OFF) +if(NOT HAVE_RULES AND DISABLE_PCRE1) + message(AUTHOR_WARNING "DISABLE_PCRE1 has no effect without HAVE_RULES") +endif() +if(HAVE_RULES AND DISABLE_PCRE1 AND NOT USE_PCRE2) + message(FATAL_ERROR "No regular expression engine enabled") +endif() option(USE_BUNDLED_TINYXML2 "Usage of bundled TinyXML2 library" ON) if(BUILD_CORE_DLL AND NOT USE_BUNDLED_TINYXML2) message(FATAL_ERROR "Cannot use external TinyXML2 library when building lib as DLL") diff --git a/cmake/printInfo.cmake b/cmake/printInfo.cmake index 87c7e41f284..e9516f9209e 100644 --- a/cmake/printInfo.cmake +++ b/cmake/printInfo.cmake @@ -79,7 +79,16 @@ endif() message(STATUS) message(STATUS "HAVE_RULES = ${HAVE_RULES}") if(HAVE_RULES) - message(STATUS "PCRE_LIBRARY = ${PCRE_LIBRARY}") + message(STATUS "USE_PCRE2 = ${USE_PCRE2}") + message(STATUS "DISABLE_PCRE1 = ${DISABLE_PCRE1}") + if(USE_PCRE2) + message(STATUS "PCRE2_INCLUDE = ${PCRE2_INCLUDE}") + message(STATUS "PCRE2_LIBRARY = ${PCRE2_LIBRARY}") + endif() + if(NOT DISABLE_PCRE1) + message(STATUS "PCRE_INCLUDE = ${PCRE_INCLUDE}") + message(STATUS "PCRE_LIBRARY = ${PCRE_LIBRARY}") + endif() endif() message(STATUS) message(STATUS "DISALLOW_THREAD_EXECUTOR = ${DISALLOW_THREAD_EXECUTOR}") diff --git a/gui/CMakeLists.txt b/gui/CMakeLists.txt index d640c341458..58f8e2382b7 100644 --- a/gui/CMakeLists.txt +++ b/gui/CMakeLists.txt @@ -34,7 +34,12 @@ CheckOptions: target_precompile_headers(cppcheck-gui PRIVATE precompiled.h) endif() if (HAVE_RULES) - target_link_libraries(cppcheck-gui ${PCRE_LIBRARY}) + if(USE_PCRE2) + target_link_libraries(cppcheck-gui ${PCRE2_LIBRARY}) + endif() + if(NOT DISABLE_PCRE1) + target_link_libraries(cppcheck-gui ${PCRE_LIBRARY}) + endif() endif() target_link_libraries(cppcheck-gui ${QT_CORE_LIB} ${QT_GUI_LIB} ${QT_WIDGETS_LIB} ${QT_PRINTSUPPORT_LIB} ${QT_HELP_LIB} ${QT_NETWORK_LIB}) if(WITH_QCHART) diff --git a/gui/test/resultstree/CMakeLists.txt b/gui/test/resultstree/CMakeLists.txt index a48b137e349..b7fdd17ab50 100644 --- a/gui/test/resultstree/CMakeLists.txt +++ b/gui/test/resultstree/CMakeLists.txt @@ -24,8 +24,14 @@ add_executable(test-resultstree target_include_directories(test-resultstree PRIVATE ${CMAKE_SOURCE_DIR}/gui) target_link_libraries(test-resultstree cppcheck-core simplecpp tinyxml2) if (HAVE_RULES) - target_link_libraries(test-resultstree ${PCRE_LIBRARY}) - target_include_directories(test-resultstree SYSTEM PRIVATE ${PCRE_INCLUDE}) + if(USE_PCRE2) + target_link_libraries(test-resultstree ${PCRE2_LIBRARY}) + target_include_directories(test-resultstree SYSTEM PRIVATE ${PCRE2_INCLUDE}) + endif() + if(NOT DISABLE_CPRE1) + target_link_libraries(test-resultstree ${PCRE_LIBRARY}) + target_include_directories(test-resultstree SYSTEM PRIVATE ${PCRE_INCLUDE}) + endif() endif() target_compile_definitions(test-resultstree PRIVATE SRCDIR="${CMAKE_CURRENT_SOURCE_DIR}") target_link_libraries(test-resultstree ${QT_CORE_LIB} ${QT_GUI_LIB} ${QT_WIDGETS_LIB} ${QT_TEST_LIB}) diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index b826bc0b265..dcd0e112162 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -53,10 +53,23 @@ endif() target_dll_compile_definitions(cppcheck-core EXPORT CPPCHECKLIB_EXPORT IMPORT CPPCHECKLIB_IMPORT) target_include_directories(cppcheck-core PUBLIC .) -target_link_libraries(cppcheck-core PRIVATE tinyxml2 simplecpp picojson ${PCRE_LIBRARY}) +target_link_libraries(cppcheck-core PRIVATE tinyxml2 simplecpp picojson) +if (HAVE_RULES) + if(USE_PCRE2) + target_link_libraries(cppcheck-core PRIVATE ${PCRE2_LIBRARY}) + endif() + if(NOT DISABLE_PCRE1) + target_link_libraries(cppcheck-core PRIVATE ${PCRE_LIBRARY}) + endif() +endif() if (HAVE_RULES) - target_include_directories(cppcheck-core SYSTEM PRIVATE ${PCRE_INCLUDE}) + if(USE_PCRE2) + target_include_directories(cppcheck-core SYSTEM PRIVATE ${PCRE2_INCLUDE}) + endif() + if(NOT DISABLE_PCRE1) + target_include_directories(cppcheck-core SYSTEM PRIVATE ${PCRE_INCLUDE}) + endif() endif() if (Boost_FOUND) target_include_directories(cppcheck-core SYSTEM PUBLIC ${Boost_INCLUDE_DIRS}) diff --git a/lib/regex.cpp b/lib/regex.cpp index bee8acb2a2a..3d5edc64f3d 100644 --- a/lib/regex.cpp +++ b/lib/regex.cpp @@ -22,13 +22,51 @@ #include +#ifdef HAVE_PCRE #ifdef _WIN32 #define PCRE_STATIC #endif #include +#endif + +#ifdef HAVE_PCRE2 +#define PCRE2_CODE_UNIT_WIDTH 8 +#include +#endif +#ifdef HAVE_PCRE namespace { - std::string pcreErrorCodeToString(const int pcreExecRet) + class PcreRegex : public Regex + { + public: + explicit PcreRegex(std::string pattern) + : mPattern(std::move(pattern)) + {} + + ~PcreRegex() override + { + if (mExtra) { + pcre_free_study(mExtra); + mExtra = nullptr; + } + if (mRe) { + pcre_free(mRe); + mRe = nullptr; + } + } + + std::string compile(); + std::string match(const std::string& str, const MatchFn& match) const override; + + private: + static std::string pcreErrorCodeToString(int pcreExecRet); + + std::string mPattern; + pcre* mRe{}; + pcre_extra* mExtra{}; + }; + + std::string PcreRegex::pcreErrorCodeToString(const int pcreExecRet) { switch (pcreExecRet) { case PCRE_ERROR_NULL: @@ -157,34 +195,6 @@ namespace { return "unknown PCRE error " + std::to_string(pcreExecRet); } - class PcreRegex : public Regex - { - public: - explicit PcreRegex(std::string pattern) - : mPattern(std::move(pattern)) - {} - - ~PcreRegex() override - { - if (mExtra) { - pcre_free_study(mExtra); - mExtra = nullptr; - } - if (mRe) { - pcre_free(mRe); - mRe = nullptr; - } - } - - std::string compile(); - std::string match(const std::string& str, const MatchFn& match) const override; - - private: - std::string mPattern; - pcre* mRe{}; - pcre_extra* mExtra{}; - }; - std::string PcreRegex::compile() { if (mRe) @@ -245,6 +255,88 @@ namespace { return ""; } } +#endif // HAVE_PCRE + +#ifdef HAVE_PCRE2 +namespace { + class Pcre2Regex : public Regex + { + public: + explicit Pcre2Regex(std::string pattern) + : mPattern(std::move(pattern)) + {} + + ~Pcre2Regex() override + { + if (mRe) { + pcre_free(mRe); + mRe = nullptr; + } + } + + std::string compile(); + std::string match(const std::string& str, const MatchFn& match) const override; + + private: + std::string mPattern; + pcre2_code* mRe{}; + }; + + std::string Pcre2Regex::compile() + { + if (mRe) + return "regular expression has already been compiled"; + + int errnumber = 0; + size_t erroffset = 0; + pcre2_code * const re = pcre2_compile(reinterpret_cast(mPattern.c_str()), PCRE2_ZERO_TERMINATED, 0, &errnumber, &erroffset, nullptr); + if (!re) { + PCRE2_UCHAR buffer[256]; + pcre2_get_error_message(errnumber, buffer, sizeof(buffer)); + return reinterpret_cast(buffer); + } + + mRe = re; + + return ""; + } + + std::string Pcre2Regex::match(const std::string& str, const MatchFn& match) const + { + if (!mRe) + return "regular expression has not been compiled yet"; + + pcre2_match_data* match_data = pcre2_match_data_create_from_pattern(mRe, NULL); + + int pos = 0; + while (pos < static_cast(str.size())) { + const int pcreExecRet = pcre2_match(mRe, reinterpret_cast(str.c_str()), static_cast(str.size()), pos, 0, match_data, nullptr); + if (pcreExecRet == PCRE2_ERROR_NOMATCH) + return ""; + if (pcreExecRet < 0) { + PCRE2_UCHAR errorMessageBuf[120]; + const int res = pcre2_get_error_message(pcreExecRet, errorMessageBuf, sizeof(errorMessageBuf)); + if (res < 0) + return std::string("failed to get error message ") + std::to_string(res) + " (pos: " + std::to_string(pos) + ")"; + return std::string(reinterpret_cast(errorMessageBuf)) + " (pos: " + std::to_string(pos) + ")"; + } + PCRE2_SIZE* ovector = pcre2_get_ovector_pointer(match_data); + const uint32_t ovcount = pcre2_get_ovector_count(match_data); + if (ovcount != 1) + return "invalid ovector count"; + const auto pos1 = static_cast(ovector[0]); + const auto pos2 = static_cast(ovector[1]); + + match(pos1, pos2); + + // jump to the end of the match for the next pcre_exec + pos = static_cast(pos2); + } + + return ""; + } +} +#endif // HAVE_PCRE2 template static T* createAndCompileRegex(std::string pattern, std::string& err) @@ -257,9 +349,15 @@ static T* createAndCompileRegex(std::string pattern, std::string& err) std::shared_ptr Regex::create(std::string pattern, Engine engine, std::string& err) { Regex* regex = nullptr; +#ifdef HAVE_PCRE if (engine == Engine::Pcre) regex = createAndCompileRegex(std::move(pattern), err); - else { +#endif +#ifdef HAVE_PCRE2 + if (engine == Engine::Pcre2) + regex = createAndCompileRegex(std::move(pattern), err); +#endif + if (!regex) { err = "unknown regular expression engine"; } if (!err.empty()) { diff --git a/lib/regex.h b/lib/regex.h index afe92d92d40..178291490da 100644 --- a/lib/regex.h +++ b/lib/regex.h @@ -41,7 +41,12 @@ class CPPCHECKLIB Regex enum class Engine : std::uint8_t { Unknown = 0, - Pcre = 1 +#ifdef HAVE_PCRE + Pcre = 1, +#endif +#ifdef HAVE_PCRE2 + Pcre2 = 2 +#endif }; static std::shared_ptr create(std::string pattern, Engine engine, std::string& err); diff --git a/releasenotes.txt b/releasenotes.txt index 94e79a792e3..9150baf7d69 100644 --- a/releasenotes.txt +++ b/releasenotes.txt @@ -21,3 +21,8 @@ Infrastructure & dependencies: Other: - Make it possible to specify the regular expression engine using the `engine` element in a rule XML. +- Added support for PCRE2 (`pcre2`) as the regular expression engine for rules. +- Added CMake option `USE_PCRE2` to enable usage of the PCRE2 library for rules (will not change the default). +- Added CMake option `DISABLE_PCRE1` to disable usage of the PCRE library for rules (changes default to `pcre2`). +- +- \ No newline at end of file diff --git a/test/testcmdlineparser.cpp b/test/testcmdlineparser.cpp index 870e0ac341c..4604f87b92a 100644 --- a/test/testcmdlineparser.cpp +++ b/test/testcmdlineparser.cpp @@ -2682,10 +2682,20 @@ class TestCmdlineParser : public TestFixture { "ruleSummary2\n" "\n" "\n" + "\n" + "pcre2\n" + "raw\n" + "[A-Z]\n" + "\n" + "style\n" + "ruleId3\n" + "ruleSummary3\n" + "\n" + "\n" ""); const char * const argv[] = {"cppcheck", "--rule-file=rule.xml", "file.cpp"}; ASSERT_EQUALS_ENUM(CmdLineParser::Result::Success, parseFromArgs(argv)); - ASSERT_EQUALS(2, settings->rules.size()); + ASSERT_EQUALS(3, settings->rules.size()); auto it = settings->rules.cbegin(); ASSERT_EQUALS_ENUM(Regex::Engine::Pcre, it->engine); ASSERT_EQUALS("raw", it->tokenlist); @@ -2700,6 +2710,13 @@ class TestCmdlineParser : public TestFixture { ASSERT_EQUALS_ENUM(Severity::warning, it->severity); ASSERT_EQUALS("ruleId2", it->id); ASSERT_EQUALS("ruleSummary2", it->summary); + ++it; + ASSERT_EQUALS_ENUM(Regex::Engine::Pcre2, it->engine); + ASSERT_EQUALS("raw", it->tokenlist); + ASSERT_EQUALS("[A-Z]", it->pattern); + ASSERT_EQUALS_ENUM(Severity::style, it->severity); + ASSERT_EQUALS("ruleId3", it->id); + ASSERT_EQUALS("ruleSummary3", it->summary); } void ruleFileSingle() { diff --git a/test/testregex.cpp b/test/testregex.cpp index 4b6b2781cc8..41010096b70 100644 --- a/test/testregex.cpp +++ b/test/testregex.cpp @@ -85,6 +85,8 @@ class TestRegExBase : public TestFixture { std::string exp; if (mEngine == Regex::Engine::Pcre) exp = "missing terminating ] for character class"; + else if (mEngine == Regex::Engine::Pcre2) + exp = "missing terminating ] for character class"; (void)assertRegex("[", exp); } @@ -201,6 +203,13 @@ class TestRegExPcre : public TestRegExBase { TestRegExPcre() : TestRegExBase("TestRegExPcre", Regex::Engine::Pcre) {} }; +class TestRegExPcre2 : public TestRegExBase { +public: + TestRegExPcre2() : TestRegExBase("TestRegExPcre2", Regex::Engine::Pcre2) {} +}; + + REGISTER_TEST(TestRegExPcre) +REGISTER_TEST(TestRegExPcre2) #endif // HAVE_RULES