Skip to content

Commit 7a42be9

Browse files
Watson1978claude
andcommitted
Regexp: タイムアウト機能一式を追加 (Ruby 3.2)
Ruby 3.2 で追加された正規表現のタイムアウト機能が、るりまに一切収録されて いなかった (Regexp.md に timeout / linear_time / TimeoutError の記述がゼロ)。 追加したもの: - Regexp.timeout クラス全体の上限の取得 - Regexp.timeout= クラス全体の上限の設定 - Regexp#timeout 正規表現ごとの上限の取得 - Regexp.linear_time? 線形時間でマッチできるかの判定 - Regexp::TimeoutError 上限を超えたときに発生する例外 いずれも 3.2 で追加されたことを実機で確認し (3.1 には存在しない)、 #@SInCE 3.2 で分岐した。 例に使う正規表現は実機で検証して選んだ。Ruby 3.2 以降はメモ化により 古典的な破滅的バックトラック (/^(a+)+$/ など) が線形時間で処理されるため タイムアウトしない。後方参照を含み linear_time? が false になる /^((a|a)+)\1$/ を用いており、3.2.11 / 4.0.6 の両方で TimeoutError が 発生することを確認済み。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 4703a68 commit 7a42be9

1 file changed

Lines changed: 102 additions & 0 deletions

File tree

manual/api/_builtin/Regexp.md

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,69 @@ p Regexp.try_convert(/re/) # => /re/
244244
p Regexp.try_convert("re") # => nil
245245
```
246246

247+
#@since 3.2
248+
### def timeout -> Float | nil
249+
250+
正規表現のマッチにかける時間の上限を秒数で返します。
251+
252+
上限が設定されていない場合は nil を返します。既定値は nil です。
253+
254+
この設定はプロセス全体で共有されます。個々の正規表現に設定された上限
255+
([m:Regexp#timeout])がある場合、そちらが優先されます。
256+
257+
```ruby
258+
p Regexp.timeout # => nil
259+
```
260+
261+
- **SEE** [m:Regexp.timeout=], [m:Regexp#timeout]
262+
263+
### def timeout=(seconds)
264+
265+
正規表現のマッチにかける時間の上限を秒数で設定します。
266+
267+
nil を指定すると上限を設定しない状態に戻します。
268+
269+
この設定はプロセス全体に影響します。個々の正規表現ごとに設定したい場合は
270+
[m:Regexp.new] の timeout キーワード引数を使用してください。
271+
272+
上限を超えた場合は [c:Regexp::TimeoutError] が発生します。
273+
274+
- **param** `seconds` -- 上限の秒数を数値で指定します。nil を指定すると上限なしになります。
275+
276+
```ruby
277+
Regexp.timeout = 0.5
278+
p Regexp.timeout # => 0.5
279+
280+
# バックトラックが多く発生し、線形時間で処理できない正規表現の例
281+
/^((a|a)+)\1$/ =~ ("a" * 30 + "x") # ~> Regexp::TimeoutError
282+
```
283+
284+
- **SEE** [m:Regexp.timeout], [m:Regexp#timeout], [c:Regexp::TimeoutError]
285+
286+
### def linear_time?(re) -> bool
287+
### def linear_time?(string, options = 0) -> bool
288+
289+
正規表現 re が入力文字列の長さに対して線形時間でマッチできる場合に true を、
290+
そうでない場合に false を返します。
291+
292+
これは Ruby の処理系の性質であって、正規表現そのものの性質ではありません。
293+
同じ正規表現でも Ruby のビルドや実装によって結果が変わることがあり、
294+
返り値について前方互換性も後方互換性も保証されません。
295+
296+
- **param** `re` -- 判定したい正規表現を指定します。
297+
298+
- **param** `string` -- 正規表現を文字列で指定します。
299+
300+
- **param** `options` -- 正規表現のオプションを指定します。
301+
302+
```ruby
303+
p Regexp.linear_time?(/re/) # => true
304+
p Regexp.linear_time?(/^((a|a)+)\1$/) # => false (後方参照があるため)
305+
```
306+
307+
- **SEE** [m:Regexp.timeout=]
308+
#@end
309+
247310
## Instance Methods
248311

249312
### def =~(string) -> Integer | nil
@@ -544,6 +607,27 @@ p Regexp.new("foo", Regexp::MULTILINE | Regexp::EXTENDED).options # =>6
544607
p Regexp.new("foo", Regexp::IGNORECASE | Regexp::MULTILINE | Regexp::EXTENDED).options # => 7
545608
```
546609

610+
#@since 3.2
611+
### def timeout -> Float | nil
612+
613+
その正規表現のマッチにかける時間の上限を秒数で返します。
614+
615+
上限が設定されていない場合は nil を返します。
616+
617+
この設定は正規表現ごとのもので、[m:Regexp.new] の timeout キーワード引数で
618+
指定します。設定されている場合、[m:Regexp.timeout] によるプロセス全体の設定
619+
よりも優先されます。
620+
621+
```ruby
622+
re = Regexp.new("^(a|a)*$", timeout: 0.5)
623+
p re.timeout # => 0.5
624+
625+
p /^(a|a)*$/.timeout # => nil
626+
```
627+
628+
- **SEE** [m:Regexp.timeout], [m:Regexp.timeout=]
629+
#@end
630+
547631
### def source -> String
548632

549633
その正規表現のもととなった文字列表現を生成して返します。
@@ -693,3 +777,21 @@ p /(.)(.)/.names
693777
バイト列としてマッチすることを意味します。
694778

695779
正規表現リテラルの n オプションに対応します。
780+
781+
#@since 3.2
782+
# class Regexp::TimeoutError < RegexpError
783+
784+
正規表現のマッチが、設定された時間の上限を超えた場合に発生します。
785+
786+
上限は [m:Regexp.timeout=] でプロセス全体に対して、
787+
あるいは [m:Regexp.new] の timeout キーワード引数で正規表現ごとに設定します。
788+
789+
```ruby
790+
Regexp.timeout = 0.5
791+
792+
# バックトラックが多く発生し、線形時間で処理できない正規表現の例
793+
/^((a|a)+)\1$/ =~ ("a" * 30 + "x") # ~> Regexp::TimeoutError
794+
```
795+
796+
- **SEE** [m:Regexp.timeout=], [m:Regexp#timeout]
797+
#@end

0 commit comments

Comments
 (0)