Задать вопрос
jfaFan
@jfaFan
Student, interested in programming

SEGV при использовании ICU?

Здравствуйте.

Я пытаюсь использовать библиотеку 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-образ).

Подскажите пожалуйста, в чем может быть проблема.
  • Вопрос задан
  • 117 просмотров
Подписаться 2 Простой Комментировать
Решения вопроса 1
jfaFan
@jfaFan Автор вопроса
Student, interested in programming
Кажется, я нашел проблему и ее решение.

Описание функции matcher:

/**
    * Creates a RegexMatcher that will match the given input against this pattern.  The
    * RegexMatcher can then be used to perform match, find or replace operations
    * on the input.  Note that a RegexPattern object must not be deleted while
    * RegexMatchers created from it still exist and might possibly be used again.
    *
    * The matcher will retain a reference to the supplied input string, and all regexp
    * pattern matching operations happen directly on this original string.  It is
    * critical that the string not be altered or deleted before use by the regular
    * expression operations is complete.
    *
    * @param input    The input string to which the regular expression will be applied.
    * @param status   A reference to a UErrorCode to receive any errors.
    * @return         A RegexMatcher object for this pattern and input.
    *
    * @stable ICU 2.4
    */
    virtual RegexMatcher *matcher(const UnicodeString &input,
        UErrorCode          &status) const;


Здесь говорится о том, что input не копируется в объект matcher. Т.е. вызывающий код несет ответственность за валидность оригинального объекта input. RegexMatcher в конструкторе вызывает метод reset, описание которого говорит то же самое об оригинальном объекте:

/**
    *   Resets this matcher with a new input string.  This allows instances of RegexMatcher
    *     to be reused, which is more efficient than creating a new RegexMatcher for
    *     each input string to be processed.
    *   @param input The new string on which subsequent pattern matches will operate.
    *                The matcher retains a reference to the callers string, and operates
    *                directly on that.  Ownership of the string remains with the caller.
    *                Because no copy of the string is made, it is essential that the
    *                caller not delete the string until after regexp operations on it
    *                are done.
    *                Note that while a reset on the matcher with an input string that is then
    *                modified across/during matcher operations may be supported currently for UnicodeString,
    *                this was not originally intended behavior, and support for this is not guaranteed
    *                in upcoming versions of ICU.
    *   @return this RegexMatcher.
    *   @stable ICU 2.4
    */
    virtual RegexMatcher &reset(const UnicodeString &input);


В моем коде создается временный объект icu::UnicodeString, который уничтожается, как только заканчивается его scope (функция CreateRegexMatcher). Соответственно после уничтожения временного объекта matcher указывает на невалидную область памяти. Таким образом, нужно гарантировать, что время жизни исходной строки будет не меньше времени жизни matcher'а, который будет ее использовать.

Это можно сделать, например, создавая и используя RegexMatcher в одной области:

std::shared_ptr< icu::RegexPattern > pattern = GetRegexPatternFromCache(rules.pattern);

      icu::ErrorCode status;

      // temporary object lives as long as matcher is used
      std::unique_ptr< icu::RegexMatcher > matcher { pattern->matcher(icu::UnicodeString::fromUTF8(line), status) };
      // auto matcher = GetMatcherFromPattern(pattern, line);

      if (status.isFailure() || !matcher) {
        std::cout << fmt::format("Failed to create regex matcher, status - {}\n", status.errorName());
        return false;
      }

      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;
      }
Ответ написан
Комментировать
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы