Author: F.Tibor Date: 2026-08-24T13:30:41+03:00 New Revision: 43329df7800505a2ffd26b216de520f5fd3f93a3
URL: https://github.com/llvm/llvm-project/commit/43329df7800505a2ffd26b216de520f5fd3f93a3 DIFF: https://github.com/llvm/llvm-project/commit/43329df7800505a2ffd26b216de520f5fd3f93a3.diff LOG: [clang-tidy] Add llvm-regex check (#207407) This patch adds a new clang-tidy check, `llvm-regex`, which detects malformed regular expression patterns passed to `llvm::Regex` when the pattern can be resolved from a string literal at compile time. The check validates regex patterns using `llvm::Regex::isValid()` and emits a diagnostic when an invalid pattern is detected. It currently handles patterns provided directly as string literals, as well as string literals stored in immutable, or expected to be used as such, string-like values such as: - `const std::string` - `const char *` - `const llvm::StringRef` - `std::string_view` It also supports detecting invalid patterns stored in class members of the same types with in-class initializers. The check also takes into consideration `llvm::Regex::RegexFlags` modifier flag literals. --------- Co-authored-by: EugeneZelenko <[email protected]> Co-authored-by: Zeyi Xu <[email protected]> Co-authored-by: DonĂ¡t Nagy <[email protected]> Co-authored-by: Baranov Victor <[email protected]> Added: clang-tools-extra/clang-tidy/llvm/InvalidRegexPatternCheck.cpp clang-tools-extra/clang-tidy/llvm/InvalidRegexPatternCheck.h clang-tools-extra/docs/clang-tidy/checks/llvm/invalid-regex-pattern.md clang-tools-extra/test/clang-tidy/checkers/llvm/invalid-regex-pattern.cpp Modified: clang-tools-extra/clang-tidy/llvm/CMakeLists.txt clang-tools-extra/clang-tidy/llvm/LLVMTidyModule.cpp clang-tools-extra/docs/ReleaseNotes.md clang-tools-extra/docs/clang-tidy/checks/list.md Removed: ################################################################################ diff --git a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt index bec3ba50c81c5..563c05d14232a 100644 --- a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt +++ b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt @@ -12,6 +12,7 @@ add_clang_library(clangTidyLLVMModule STATIC PreferRegisterOverUnsignedCheck.cpp PreferStaticOverAnonymousNamespaceCheck.cpp RedundantCastingCheck.cpp + InvalidRegexPatternCheck.cpp TwineLocalCheck.cpp TypeSwitchCaseTypesCheck.cpp UseNewMLIROpBuilderCheck.cpp diff --git a/clang-tools-extra/clang-tidy/llvm/InvalidRegexPatternCheck.cpp b/clang-tools-extra/clang-tidy/llvm/InvalidRegexPatternCheck.cpp new file mode 100644 index 0000000000000..7ffc03f9795b8 --- /dev/null +++ b/clang-tools-extra/clang-tidy/llvm/InvalidRegexPatternCheck.cpp @@ -0,0 +1,77 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "InvalidRegexPatternCheck.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "llvm/Support/Regex.h" + +using namespace clang::ast_matchers; + +namespace clang::tidy::llvm_check { + +void InvalidRegexPatternCheck::registerMatchers(MatchFinder *Finder) { + auto IsConstllvmStringRef = qualType( + isConstQualified(), hasUnqualifiedDesugaredType(recordType(hasDeclaration( + cxxRecordDecl(hasName("::llvm::StringRef")))))); + auto IsConstStdString = qualType( + isConstQualified(), hasUnqualifiedDesugaredType(recordType(hasDeclaration( + cxxRecordDecl(hasName("::std::basic_string")))))); + auto GetStringLiteral = + ignoringImplicit(stringLiteral().bind("stringLiteral")); + auto GetStringLiteralFromObject = + ignoringImplicit(cxxConstructExpr(hasAnyArgument(GetStringLiteral))); + auto IsConstCharPtr = pointerType(pointee(builtinType(), isConstQualified())); + auto IsCharArray = qualType( + hasUnqualifiedDesugaredType(arrayType(hasElementType(builtinType())))); + auto IsStdStringView = qualType(hasUnqualifiedDesugaredType(recordType( + hasDeclaration(cxxRecordDecl(hasName("::std::basic_string_view")))))); + auto HasStringContainerType = + hasType(qualType(anyOf(IsConstStdString, IsConstllvmStringRef, + IsStdStringView, IsConstCharPtr, IsCharArray))); + auto GetString = anyOf(GetStringLiteralFromObject, GetStringLiteral); + auto AnyCastedToStringRef = ignoringImplicit( + anyOf(stringLiteral().bind("stringLiteral"), + declRefExpr( + to(varDecl(HasStringContainerType, hasInitializer(GetString)))), + memberExpr(member(fieldDecl(HasStringContainerType, + hasInClassInitializer(GetString)))))); + + auto IsRegexFlagsType = ignoringParenImpCasts( + anyOf(integerLiteral().bind("regexFlagsInt"), + declRefExpr(to(enumConstantDecl().bind("regexFlagEnum"))))); + Finder->addMatcher( + cxxConstructExpr( + hasDeclaration(cxxConstructorDecl(ofClass(hasName("llvm::Regex")))), + hasArgument(0, ignoringImplicit(cxxConstructExpr( + hasDeclaration(cxxConstructorDecl( + ofClass(hasName("::llvm::StringRef")))), + hasArgument(0, AnyCastedToStringRef)))), + optionally(hasArgument(1, IsRegexFlagsType))), + this); +} + +void InvalidRegexPatternCheck::check(const MatchFinder::MatchResult &Result) { + const auto *DetectedPattern = + Result.Nodes.getNodeAs<StringLiteral>("stringLiteral"); + assert(DetectedPattern && "stringLiteral must be bound in matcher"); + + unsigned int Flag = llvm::Regex::RegexFlags::NoFlags; + if (const auto *FlagInt = + Result.Nodes.getNodeAs<IntegerLiteral>("regexFlagsInt")) + Flag = FlagInt->getValue().getZExtValue(); + if (const auto *FlagEnum = + Result.Nodes.getNodeAs<EnumConstantDecl>("regexFlagEnum")) + Flag = FlagEnum->getInitVal().getZExtValue(); + const llvm::Regex TestRegex(DetectedPattern->getString(), Flag); + std::string RegexError; + if (!TestRegex.isValid(RegexError)) + diag(DetectedPattern->getBeginLoc(), "invalid regex pattern: %0") + << RegexError << DetectedPattern->getSourceRange(); +} + +} // namespace clang::tidy::llvm_check diff --git a/clang-tools-extra/clang-tidy/llvm/InvalidRegexPatternCheck.h b/clang-tools-extra/clang-tidy/llvm/InvalidRegexPatternCheck.h new file mode 100644 index 0000000000000..d1b42e3421067 --- /dev/null +++ b/clang-tools-extra/clang-tidy/llvm/InvalidRegexPatternCheck.h @@ -0,0 +1,33 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_INVALIDREGEXPATTERNCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_INVALIDREGEXPATTERNCHECK_H + +#include "../ClangTidyCheck.h" + +namespace clang::tidy::llvm_check { + +/// Detects malformed regex patterns used in ``llvm::Regex``. +/// +/// For the user-facing documentation see: +/// https://clang.llvm.org/extra/clang-tidy/checks/llvm/regex.html +class InvalidRegexPatternCheck : public ClangTidyCheck { +public: + InvalidRegexPatternCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.CPlusPlus17; + } +}; + +} // namespace clang::tidy::llvm_check + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_INVALIDREGEXPATTERNCHECK_H diff --git a/clang-tools-extra/clang-tidy/llvm/LLVMTidyModule.cpp b/clang-tools-extra/clang-tidy/llvm/LLVMTidyModule.cpp index 918af88c979e0..ed8d04f22a9e2 100644 --- a/clang-tools-extra/clang-tidy/llvm/LLVMTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/llvm/LLVMTidyModule.cpp @@ -14,6 +14,7 @@ #include "FormatvStringCheck.h" #include "HeaderGuardCheck.h" #include "IncludeOrderCheck.h" +#include "InvalidRegexPatternCheck.h" #include "PreferIsaOrDynCastInConditionalsCheck.h" #include "PreferRegisterOverUnsignedCheck.h" #include "PreferStaticOverAnonymousNamespaceCheck.h" @@ -48,6 +49,8 @@ class LLVMModule : public ClangTidyModule { "llvm-qualified-auto"); CheckFactories.registerCheck<RedundantCastingCheck>( "llvm-redundant-casting"); + CheckFactories.registerCheck<InvalidRegexPatternCheck>( + "llvm-invalid-regex-pattern"); CheckFactories.registerCheck<TwineLocalCheck>("llvm-twine-local"); CheckFactories.registerCheck<TypeSwitchCaseTypesCheck>( "llvm-type-switch-case-types"); diff --git a/clang-tools-extra/docs/ReleaseNotes.md b/clang-tools-extra/docs/ReleaseNotes.md index 51dd99256ca69..c2e90bc23eb25 100644 --- a/clang-tools-extra/docs/ReleaseNotes.md +++ b/clang-tools-extra/docs/ReleaseNotes.md @@ -102,6 +102,11 @@ infrastructure are described first, followed by tool-specific sections. #### New checks +- New {doc}`llvm-invalid-regex-pattern + <clang-tidy/checks/llvm/invalid-regex-pattern>` check. + + Detects malformed regex patterns defined in a single string literal. + - New {doc}`performance-expensive-value-or <clang-tidy/checks/performance/expensive-value-or>` check. diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.md b/clang-tools-extra/docs/clang-tidy/checks/list.md index a4696e192ef47..5a220b13eb599 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/list.md +++ b/clang-tools-extra/docs/clang-tidy/checks/list.md @@ -245,6 +245,7 @@ readability/* | {doc}`llvm-formatv-string <llvm/formatv-string>` | | | {doc}`llvm-header-guard <llvm/header-guard>` | | | {doc}`llvm-include-order <llvm/include-order>` | Yes | +| {doc}`llvm-invalid-regex-pattern <llvm/invalid-regex-pattern>` | | | {doc}`llvm-namespace-comment <llvm/namespace-comment>` | | | {doc}`llvm-prefer-isa-or-dyn-cast-in-conditionals <llvm/prefer-isa-or-dyn-cast-in-conditionals>` | Yes | | {doc}`llvm-prefer-register-over-unsigned <llvm/prefer-register-over-unsigned>` | Yes | diff --git a/clang-tools-extra/docs/clang-tidy/checks/llvm/invalid-regex-pattern.md b/clang-tools-extra/docs/clang-tidy/checks/llvm/invalid-regex-pattern.md new file mode 100644 index 0000000000000..403b19f693bbc --- /dev/null +++ b/clang-tools-extra/docs/clang-tidy/checks/llvm/invalid-regex-pattern.md @@ -0,0 +1,36 @@ +# clang-tidy - llvm-invalid-regex-pattern + +## llvm-invalid-regex-pattern + +Detects malformed regex patterns defined in a single string literal. +It detects these string literals if they are defined in the regex constructor +with a string literal, or stored in one of these non mutable containers: + +- `const std::string` +- `const char*` +- `const char[]` +- `static const char[]` +- `const llvm::StringRef` +- `std::string_view` + +In the event that the patterns are stored as a class member, the check verifies +the initialization value, if defined, even if its overwritten by the constructor. + +Example of detection: + +```cpp +const std::string my_regex_pattern("[0-8"); // invalid regex pattern +llvm::Regex my_regex(my_regex_pattern); +``` + +Example of member data being overwritten: + +```cpp +class foo{ +public: + foo(llvm::StringRef regex_pat) : regex_p(regex_pat){}; + const llvm::StringRef regex_pat = "("; // invalid regex pattern +}; +foo bar("[0-9]"); // pattern not checked +llvm::Regex my_regex(bar.regex_pat); +``` diff --git a/clang-tools-extra/test/clang-tidy/checkers/llvm/invalid-regex-pattern.cpp b/clang-tools-extra/test/clang-tidy/checkers/llvm/invalid-regex-pattern.cpp new file mode 100644 index 0000000000000..2d36fe5d1c5c3 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/llvm/invalid-regex-pattern.cpp @@ -0,0 +1,145 @@ +// RUN: %check_clang_tidy -std=c++17-or-later %s llvm-invalid-regex-pattern %t + +#include <string> + +namespace llvm { + class StringRef { + public: + StringRef(const char*); + StringRef(const std::string&); + StringRef(const std::string_view&); + }; + + class Regex { + public: + Regex(StringRef, unsigned int i = 0); + enum RegexFlags : unsigned { + NoFlags = 0, + IgnoreCase = 1, + Newline = 2, + BasicRegex = 4, + }; + }; +} // namespace llvm + +void test_detected_faulty_patterns(){ + llvm::Regex re1("("); + // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: invalid regex pattern: parentheses not balanced + + const std::string badStdString("("); + // CHECK-MESSAGES: :[[@LINE-1]]:34: warning: invalid regex pattern: parentheses not balanced + llvm::Regex re2(badStdString); + + const char* badCharPtr = "[]"; + // CHECK-MESSAGES: :[[@LINE-1]]:28: warning: invalid regex pattern: brackets ([ ]) not balanced + llvm::Regex re3(badCharPtr); + + std::string_view badStrView("+"); + // CHECK-MESSAGES: :[[@LINE-1]]:31: warning: invalid regex pattern: repetition-operator operand invalid + llvm::Regex re4(badStrView); + + const llvm::StringRef badStrRef = "a*?"; + // CHECK-MESSAGES: :[[@LINE-1]]:37: warning: invalid regex pattern: repetition-operator operand invalid + llvm::Regex re5(badStrRef); + + static const char badConstStaticChar[] = ""; + // CHECK-MESSAGES: :[[@LINE-1]]:44: warning: invalid regex pattern: empty (sub)expression + llvm::Regex regex_badConstStaticChar(badConstStaticChar); + + static char badStaticChar[] = ""; + // CHECK-MESSAGES: :[[@LINE-1]]:33: warning: invalid regex pattern: empty (sub)expression + llvm::Regex regex_badStaticChar(badStaticChar); + + char badCharArray[] = ""; + // CHECK-MESSAGES: :[[@LINE-1]]:25: warning: invalid regex pattern: empty (sub)expression + llvm::Regex regex_badCharArray(badCharArray); + + struct RegexPatterns { + const char* badMemberChar = ""; + // CHECK-MESSAGES: :[[@LINE-1]]:33: warning: invalid regex pattern: empty (sub)expression + + const std::string badMemberStr = "("; + // CHECK-MESSAGES: :[[@LINE-1]]:38: warning: invalid regex pattern: parentheses not balanced + + std::string_view badMemberStrView = "+"; + // CHECK-MESSAGES: :[[@LINE-1]]:41: warning: invalid regex pattern: repetition-operator operand invalid + + const llvm::StringRef badMemberStrRef = "a*?"; + // CHECK-MESSAGES: :[[@LINE-1]]:45: warning: invalid regex pattern: repetition-operator operand invalid + + const char badMemberConstChar[1] = ""; + // CHECK-MESSAGES: :[[@LINE-1]]:40: warning: invalid regex pattern: empty (sub)expression + + char badMemberCharArray[1] = ""; + // CHECK-MESSAGES: :[[@LINE-1]]:34: warning: invalid regex pattern: empty (sub)expression + }; + + RegexPatterns Pats; + + llvm::Regex re6(Pats.badMemberChar); + llvm::Regex re7(Pats.badMemberStr); + llvm::Regex re8(Pats.badMemberStrView); + llvm::Regex re9(Pats.badMemberStrRef); + llvm::Regex regex_badMemberConstStaticChar(Pats.badMemberConstChar); + llvm::Regex regex_badMemberCharArray(Pats.badMemberCharArray); +} + +void test_no_detection_on_mutable(){ + std::string badMutStdString("("); + llvm::Regex re10(badMutStdString); + + char* badMutCharPtr = "["; + llvm::Regex re11(badMutCharPtr); + + llvm::StringRef badMutStrRef = "a*?"; + llvm::Regex re12(badMutStrRef); + + struct RegexMutPatterns { + char* badMutMemberChar = ""; + std::string badMutMemberStr = "("; + llvm::StringRef badMutMemberStrRef = "a*?"; + }; + + RegexMutPatterns mutPats; + + llvm::Regex re13(mutPats.badMutMemberChar); + llvm::Regex re14(mutPats.badMutMemberStr); + llvm::Regex re15(mutPats.badMutMemberStrRef); +} + +void test_no_report_on_correct_patterns(){ + llvm::Regex re16("[0-9]"); + + const std::string goodStdString("test"); + llvm::Regex re17(goodStdString); + + const char* goodCharPtr = "\\[test\\]"; + llvm::Regex re18(goodCharPtr); + + std::string_view goodStrView("testi+ng"); + llvm::Regex re19(goodStrView); + + const llvm::StringRef goodStrRef = "a*b?"; + llvm::Regex re20(goodStrRef); + + struct GoodRegexPatterns { + const char* goodMemberChar = "[0-9]"; + const std::string goodMemberStr = "test"; + std::string_view goodMemberStrView = "\[test\]"; + const llvm::StringRef goodMemberStrRef = "a*b"; + }; + + GoodRegexPatterns goodPats; + + llvm::Regex re21(goodPats.goodMemberChar); + llvm::Regex re22(goodPats.goodMemberStr); + llvm::Regex re23(goodPats.goodMemberStrView); + llvm::Regex re24(goodPats.goodMemberStrRef); +} + +void test_grammar_flags(){ + llvm::Regex re1_noflag("(", 0); + // CHECK-MESSAGES: :[[@LINE-1]]:26: warning: invalid regex pattern: parentheses not balanced + llvm::Regex re1_basic("(", 4U); + llvm::Regex re2_basic("(", llvm::Regex::RegexFlags::BasicRegex); +} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
