Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions cli/cmdlineparser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1356,6 +1356,9 @@ CmdLineParser::Result CmdLineParser::parseFromArgs(int argc, const char* const a
if (std::strcmp(engine, "pcre") == 0) {
rule.engine = Regex::Engine::Pcre;
}
else if (std::strcmp(engine, "pcre2") == 0) {
rule.engine = Regex::Engine::Pcre2;
}
else {
mLogger.printError(std::string("unknown regex engine '") + engine + "'.");
return Result::Fail;
Expand Down
8 changes: 7 additions & 1 deletion cmake/findDependencies.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,13 @@ if(HAVE_RULES)
find_path(PCRE_INCLUDE pcre.h)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you still need pcre.h if you are using pcre2.h ? I'm trying to build with CMake on Debian Trixie, for which libpcre3-dev is not available, and the build still fails because CMake cannot detect pcre.h

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you still need pcre.h if you are using pcre2.h ?

No.

This is still WIP. I have not added build options separating PCRE from PCRE2 yet.

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")
message(FATAL_ERROR "PCRE dependency for RULES has not been found")
endif()

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()
else()
set(PCRE_LIBRARY "")
Expand Down
3 changes: 3 additions & 0 deletions cmake/printInfo.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,10 @@ endif()
message(STATUS)
message(STATUS "HAVE_RULES = ${HAVE_RULES}")
if(HAVE_RULES)
message(STATUS "PCRE_INCLUDE = ${PCRE_INCLUDE}")
message(STATUS "PCRE_LIBRARY = ${PCRE_LIBRARY}")
message(STATUS "PCRE2_INCLUDE = ${PCRE2_INCLUDE}")
message(STATUS "PCRE2_LIBRARY = ${PCRE2_LIBRARY}")
endif()
message(STATUS)
message(STATUS "DISALLOW_THREAD_EXECUTOR = ${DISALLOW_THREAD_EXECUTOR}")
Expand Down
4 changes: 2 additions & 2 deletions gui/test/resultstree/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ 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})
target_link_libraries(test-resultstree ${PCRE_LIBRARY} ${PCRE2_LIBRARY})
target_include_directories(test-resultstree SYSTEM PRIVATE ${PCRE_INCLUDE} ${PCRE2_INCLUDE})
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})
Expand Down
4 changes: 2 additions & 2 deletions lib/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,10 @@ 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 ${PCRE_LIBRARY} ${PCRE2_LIBRARY})

if (HAVE_RULES)
target_include_directories(cppcheck-core SYSTEM PRIVATE ${PCRE_INCLUDE})
target_include_directories(cppcheck-core SYSTEM PRIVATE ${PCRE_INCLUDE} ${PCRE2_INCLUDE})
endif()
if (Boost_FOUND)
target_include_directories(cppcheck-core SYSTEM PUBLIC ${Boost_INCLUDE_DIRS})
Expand Down
144 changes: 115 additions & 29 deletions lib/regex.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,41 @@
#endif
#include <pcre.h>

#define PCRE2_CODE_UNIT_WIDTH 8
#include <pcre2.h>

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:
Expand Down Expand Up @@ -157,34 +190,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)
Expand Down Expand Up @@ -246,6 +251,85 @@ namespace {
}
}

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<PCRE2_SPTR8>(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<char*>(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<int>(str.size())) {
const int pcreExecRet = pcre2_match(mRe, reinterpret_cast<PCRE2_SPTR8>(str.c_str()), static_cast<int>(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<char*>(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<unsigned int>(ovector[0]);
const auto pos2 = static_cast<unsigned int>(ovector[1]);

match(pos1, pos2);

// jump to the end of the match for the next pcre_exec
pos = static_cast<int>(pos2);
}

return "";
}
}

template<typename T>
static T* createAndCompileRegex(std::string pattern, std::string& err)
{
Expand All @@ -259,6 +343,8 @@ std::shared_ptr<Regex> Regex::create(std::string pattern, Engine engine, std::st
Regex* regex = nullptr;
if (engine == Engine::Pcre)
regex = createAndCompileRegex<PcreRegex>(std::move(pattern), err);
else if (engine == Engine::Pcre2)
regex = createAndCompileRegex<Pcre2Regex>(std::move(pattern), err);
else {
err = "unknown regular expression engine";
}
Expand Down
3 changes: 2 additions & 1 deletion lib/regex.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ class CPPCHECKLIB Regex
enum class Engine : std::uint8_t
{
Unknown = 0,
Pcre = 1
Pcre = 1,
Pcre2 = 2
};

static std::shared_ptr<Regex> create(std::string pattern, Engine engine, std::string& err);
Expand Down
1 change: 1 addition & 0 deletions releasenotes.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ Infrastructure & dependencies:

Other:
- Make it possible to specify the regular expression engine using the `engine` element in a rule XML.
- Added support for PCRE2 (`pcre`) as the regular expression engine for rules.
19 changes: 18 additions & 1 deletion test/testcmdlineparser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2682,10 +2682,20 @@ class TestCmdlineParser : public TestFixture {
"<summary>ruleSummary2</summary>\n"
"</message>\n"
"</rule>\n"
"<rule>\n"
"<engine>pcre2</engine>\n"
"<tokenlist>raw</tokenlist>\n"
"<pattern>[A-Z]</pattern>\n"
"<message>\n"
"<severity>style</severity>\n"
"<id>ruleId3</id>\n"
"<summary>ruleSummary3</summary>\n"
"</message>\n"
"</rule>\n"
"</rules>");
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);
Expand All @@ -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() {
Expand Down
9 changes: 9 additions & 0 deletions test/testregex.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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