Здравствуйте.
Я пытаюсь использовать библиотеку ICU для валидации входных данных с помощью регулярных выражений.
В частности я пытаюсь провалидировать имена пользователей.
Код (main.cpp):
#include <fmt/format.h>
#include "validators/validation_rules.hpp"
#include <iostream>
#include <boost/regex.hpp>
#include <boost/regex/icu.hpp>
#include <unicode/errorcode.h>
#include <unicode/regex.h>
std::shared_ptr<icu::RegexPattern> CreateRegexPattern(std::string_view pattern) {
UParseError parse_error;
icu::ErrorCode status;
std::shared_ptr<icu::RegexPattern> regex_ptr{
icu::RegexPattern::compile(
icu::UnicodeString::fromUTF8(pattern),
parse_error, // UParseError
status // UErrorCode
)
};
if (status.isFailure()) {
throw std::runtime_error(fmt::format(
"Regex compilation failed: {} (parse error at line {}, offset {})",
status.errorName(),
parse_error.line,
parse_error.offset
));
}
assert(regex_ptr);
return regex_ptr;
}
std::unique_ptr<icu::RegexMatcher> CreateRegexMatcher(std::string_view pattern, std::string_view line) {
static std::unordered_map<std::string, std::shared_ptr<icu::RegexPattern>> cache;
const std::string pattern_str(pattern);
if (cache.find(pattern_str) == cache.end()) {
cache[pattern_str] = CreateRegexPattern(pattern);
}
icu::ErrorCode status;
std::unique_ptr<icu::RegexMatcher> matcher{
cache[pattern_str]->matcher(icu::UnicodeString::fromUTF8(line), status)
};
if (status.isFailure()) {
throw std::runtime_error(fmt::format(
"Failed to create regex matcher: {}", status.errorName()
));
}
assert(matcher);
return matcher;
}
bool _ValidateRegex(std::string_view line, const tms::validators::rules::ValidationRulesRegex& rules) {
try {
std::unique_ptr<icu::RegexMatcher> matcher = CreateRegexMatcher(rules.pattern, line);
icu::ErrorCode status;
// std::string input;
// std::cout << fmt::format("matcher input - {}\n", matcher.get()->input().toUTF8String(input));
if (!matcher->matches(status)) {
std::cout << fmt::format("{} does not match\n", line);
return false;
} else {
std::cout << fmt::format("{} matches\n", line);
return true;
}
} catch (const std::exception& ex) {
std::cout << fmt::format("Error: {}\n", ex.what());
return false;
}
}
int main(int argc, char* argv[]) {
constexpr std::array< std::string_view, 7 > UserNames =
{ u8"JohnDoe", u8"O'Connor", u8"Jean-Luc", u8"Åsa", u8"Jo", u8"JohnDoe123", u8"John Doe" };
for (auto name : UserNames)
{
_ValidateRegex(name, tms::validators::rules::kUserNameRules);
}
return 0;
}
Также validation_rules.hpp:
#pragma once
#include <string_view>
namespace tms::validators::rules
{
struct ValidationRulesLength
{
std::size_t min_length;
std::size_t max_length;
};
struct ValidationRulesRegex
{
std::string_view pattern;
};
struct ValidationRulesField : ValidationRulesLength,
ValidationRulesRegex
{
};
inline constexpr ValidationRulesField kUserNameRules {{3, 20},{R"(^[\p{L}'’-]{3,20}$)"}};
inline constexpr ValidationRulesField kUserLastNameRules {{3, 20}, {R"(^[\p{L}'’-]{3,20}$)"}};
inline constexpr ValidationRulesField kUserEmailRules {{5, 254}, {R"(^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$)"}};
inline constexpr ValidationRulesField kUserLoginRules {{4, 20}, {R"(^[A-Za-z0-9_-]{4,20}$)"}};
inline constexpr ValidationRulesField kUserPasswordRules {{8, 64},{R"(^(?=.*[A-Z])(?=.*[a-z])(?=.*\d)(?=.*[!@#$%^&*])[A-Za-z\d!@#$%^&*]{8,64}$)"}};
inline constexpr ValidationRulesField kTaskTitleRules {{1, 100},{R"(^[\p{L}\p{N}\s\-,.!?]{1,100}$)"}};
inline constexpr ValidationRulesField kTaskDescriptionRules {{0, 1000 },{R"(^[\p{L}\p{N}\s\-,.!?\n\t]{0,1000}$)"}};
inline constexpr ValidationRulesField kTaskCategoryRules {{1, 100 },{R"(^[\p{L}\p{N}\-_]{1,100}$)"}};
}
В результате выполнения я получаю либо это:
JohnDoe does not match
O'Connor does not match
Jean-Luc does not match
Åsa does not match
Jo does not match
JohnDoe123 does not match
John Doe does not match
Либо это:
AddressSanitizer:DEADLYSIGNAL
=================================================================
==75747==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000 (pc 0x7fcc403b8cfb bp 0x613000002a40 sp 0x7fff787e4cd0 T0)
==75747==The signal is caused by a READ memory access.
==75747==Hint: address points to the zero page.
#0 0x7fcc403b8cfb in icu_70::RegexMatcher::MatchChunkAt(int, signed char, UErrorCode&) (/lib/x86_64-linux-gnu/libicui18n.so.70+0x210cfb)
#1 0x7fcc403af1f8 in icu_70::RegexMatcher::matches(UErrorCode&) (/lib/x86_64-linux-gnu/libicui18n.so.70+0x2071f8)
#2 0x560ce783829e in _ValidateRegex(std::basic_string_view >, tms::validators::rules::ValidationRulesRegex const&) (/workspaces/TMS/build_debug/tms_service+0x4ff029e)
#3 0x560ce7839b9a in main (/workspaces/TMS/build_debug/tms_service+0x4ff1b9a)
#4 0x7fcc3f663d8f (/lib/x86_64-linux-gnu/libc.so.6+0x29d8f)
#5 0x7fcc3f663e3f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x29e3f)
#6 0x560ce7761f84 in _start (/workspaces/TMS/build_debug/tms_service+0x4f19f84)
AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV (/lib/x86_64-linux-gnu/libicui18n.so.70+0x210cfb) in icu_70::RegexMatcher::MatchChunkAt(int, signed char, UErrorCode&)
==75747==ABORTING
Я измучился с попытками узнать, почему MatchChunkAt обращается к некорректной памяти.
Документация icu::RegexPattern::matcher говорит о том, что RegexPattern должнен жить не меньше, чем
все matcher'ы, образованные от него. Вроде как кэш все сохраняет, так что наврядли проблема в этом.
Я ожидал примерно следующий результат:
JohnDoe matches
O'Connor matches
Jean-Luc matches
Åsa matches
Jo does not match
JohnDoe123 does not match
Система - Ubuntu (docker-образ).
Подскажите пожалуйста, в чем может быть проблема.