Zenn
🎃

[Feature #19694] Regexp のオブジェクトごとにタイムアウトを設定できるようにする提案

2024/05/22に公開

[Feature #19694] Add Regexp#timeout= setter

  • Ruby 3.2 で次のように正規表現マッチのタイムアウトを設定することができるようになった
# 正規表現マッチのタイムアウトを設定する
# 0.2秒以上かかったら例外が発生する
Regexp.timeout = 0.2

begin
  # めっちゃ時間がかかる正規表現マッチ
  "a" * 1000000 + "x" =~ /^(a*)\1b?a*$/
rescue => e
  pp e
  # => #<Regexp::TimeoutError: regexp match timeout>
end
  • このタイムアウトをインスタンスオブジェクトごとに設定する Regexp#timeout= を追加したいという提案
  • モチベーションとしてはアプリケーション上で存在しているグローバルな正規表現に対して個々でタイムアウトを設定したいとのこと
    • 以下のようなイメージ
# 正規表現リテラルは暗黙的に freeze されているので dup する必要がある
emoji_filter_pattern = %r{
  (?<!#{Regexp.quote(ZERO_WIDTH_JOINER)})
  #{EmojiFilter.unicodes_pattern}
  (?!#{Regexp.union(EmojiFilter::MODIFIER_CHAR_MAP.keys.map { |k| Regexp.quote k })})
}x.dup
emoji_filter_pattern.timeout = 1.0
# timeout を設定した後に不変にする
emoji_filter_pattern.freeze
  • 以下のように Regexp.newtimeout: キーワード引数を渡して定義すればいいんじゃないか、とコメントされてている
emoji_filter_pattern = %r{
  (?<!#{Regexp.quote(ZERO_WIDTH_JOINER)})
  #{EmojiFilter.unicodes_pattern}
  (?!#{Regexp.union(EmojiFilter::MODIFIER_CHAR_MAP.keys.map { |k| Regexp.quote k })})
}x
emoji_filter_pattern = Regexp.new(emoji_filter_pattern, timeout: 1.0).freeze
  • 別の話としてそもそも正規表現自体を線形にしたほうがよいのではないか、みたいなコメントもある
GitHubで編集を提案

Discussion

ログインするとコメントできます