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
2 changes: 1 addition & 1 deletion .github/workflows/valgrind.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ jobs:
- name: Run valgrind
run: |
ec=0
valgrind --error-limit=yes --leak-check=full --num-callers=50 --show-reachable=yes --track-origins=yes --suppressions=valgrind/testrunner.supp --gen-suppressions=all -s --log-fd=9 --error-exitcode=42 ./testrunner TestGarbage TestOther TestSimplifyTemplate TestRegEx 9>memcheck.log || ec=1
valgrind --error-limit=yes --leak-check=full --num-callers=50 --show-reachable=yes --track-origins=yes --suppressions=valgrind/testrunner.supp --gen-suppressions=all -s --log-fd=9 --error-exitcode=42 ./testrunner TestGarbage TestOther TestSimplifyTemplate TestRegExPcre 9>memcheck.log || ec=1
cat memcheck.log
exit $ec
# TODO: debuginfod.ubuntu.com is currently not responding to any requests causing it to run into a 40(!) minute timeout
Expand Down
258 changes: 129 additions & 129 deletions Makefile

Large diffs are not rendered by default.

17 changes: 15 additions & 2 deletions cli/cmdlineparser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1294,7 +1294,7 @@ CmdLineParser::Result CmdLineParser::parseFromArgs(int argc, const char* const a
}

std::string regex_err;
auto regex = Regex::create(rule.pattern, regex_err);
auto regex = Regex::create(rule.pattern, Regex::Engine::Pcre, regex_err);
if (!regex) {
mLogger.printError("failed to compile rule pattern '" + rule.pattern + "' (" + regex_err + ").");
return Result::Fail;
Expand Down Expand Up @@ -1351,6 +1351,19 @@ 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);
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;
}
}
else {
mLogger.printError("unable to load rule-file '" + ruleFile + "' - unknown element '" + subname + "' encountered in 'rule'.");
return Result::Fail;
Expand Down Expand Up @@ -1378,7 +1391,7 @@ CmdLineParser::Result CmdLineParser::parseFromArgs(int argc, const char* const a
}

std::string regex_err;
auto regex = Regex::create(rule.pattern, regex_err);
auto regex = Regex::create(rule.pattern, rule.engine, regex_err);
if (!regex) {
mLogger.printError("unable to load rule-file '" + ruleFile + "' - pattern '" + rule.pattern + "' failed to compile (" + regex_err + ").");
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)
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
172 changes: 135 additions & 37 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,46 +190,18 @@ 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)
return "pcre_compile failed: regular expression has already been compiled";
return "regular expression has already been compiled";

const char *pcreCompileErrorStr = nullptr;
int erroffset = 0;
pcre * const re = pcre_compile(mPattern.c_str(),0,&pcreCompileErrorStr,&erroffset,nullptr);
if (!re) {
if (pcreCompileErrorStr)
return "pcre_compile failed: " + std::string(pcreCompileErrorStr);
return "pcre_compile failed: unknown error";
return pcreCompileErrorStr;
return "unknown error";
}

// Optimize the regex, but only if PCRE_CONFIG_JIT is available
Expand All @@ -209,7 +214,7 @@ namespace {
if (pcreStudyErrorStr) {
// pcre_compile() worked, but pcre_study() returned an error. Free the resources allocated by pcre_compile().
pcre_free(re);
return "pcre_study failed: " + std::string(pcreStudyErrorStr);
return std::string(pcreStudyErrorStr) + " (pcre_study)";
}
mExtra = pcreExtra;
#endif
Expand All @@ -222,7 +227,7 @@ namespace {
std::string PcreRegex::match(const std::string& str, const MatchFn& match) const
{
if (!mRe)
return "pcre_exec failed: regular expression has not been compiled yet";
return "regular expression has not been compiled yet";

int pos = 0;
int ovector[30]= {0};
Expand All @@ -231,7 +236,7 @@ namespace {
if (pcreExecRet == PCRE_ERROR_NOMATCH)
return "";
if (pcreExecRet < 0) {
return "pcre_exec failed (pos: " + std::to_string(pos) + "): " + pcreErrorCodeToString(pcreExecRet);
return pcreErrorCodeToString(pcreExecRet) + " (pos: " + std::to_string(pos) + ")";
}
const auto pos1 = static_cast<unsigned int>(ovector[0]);
const auto pos2 = static_cast<unsigned int>(ovector[1]);
Expand All @@ -246,10 +251,103 @@ namespace {
}
}

std::shared_ptr<Regex> Regex::create(std::string pattern, std::string& err)
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)
{
auto* regex = new PcreRegex(std::move(pattern));
T* regex = new T(std::move(pattern));
err = regex->compile();
return regex;
}

std::shared_ptr<Regex> Regex::create(std::string pattern, Engine engine, std::string& err)
{
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";
}
if (!err.empty()) {
delete regex;
return nullptr;
Expand Down
10 changes: 9 additions & 1 deletion lib/regex.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

#include "config.h"

#include <cstdint>
#include <functional>
#include <memory>
#include <string>
Expand All @@ -37,7 +38,14 @@ class CPPCHECKLIB Regex
using MatchFn = std::function<void (int start, int end)>;
virtual std::string match(const std::string& str, const MatchFn& matchFn) const = 0;

static std::shared_ptr<Regex> create(std::string pattern, std::string& err);
enum class Engine : std::uint8_t
{
Unknown = 0,
Pcre = 1,
Pcre2 = 2
};

static std::shared_ptr<Regex> create(std::string pattern, Engine engine, std::string& err);
};

#endif // HAVE_RULES
Expand Down
3 changes: 3 additions & 0 deletions lib/settings.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@
#include <unordered_set>
#include <utility>

#include "regex.h"

#if defined(USE_WINDOWS_SEH) || defined(USE_UNIX_SIGNAL_HANDLING)
#include <cstdio>
#endif
Expand Down Expand Up @@ -371,6 +373,7 @@ class CPPCHECKLIB WARN_UNUSED Settings {
std::string id = "rule"; // default id
std::string summary;
Severity severity = Severity::style; // default severity
Regex::Engine engine = Regex::Engine::Pcre;
std::shared_ptr<Regex> regex;
};

Expand Down
Loading