mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
Fix per-instance Regexp timeout (#6621)
Fix per-instance Regexp timeout This makes it follow what was decided in [Bug #19055]: * `Regexp.new(str, timeout: nil)` should respect the global timeout * `Regexp.new(str, timeout: huge_val)` should use the maximum value that can be represented in the internal representation * `Regexp.new(str, timeout: 0 or negative value)` should raise an error
This commit is contained in:
parent
6700fa7f62
commit
b51b22513f
Notes:
git
2022-10-24 09:03:50 +00:00
Merged-By: mame <mame@ruby-lang.org>
3 changed files with 74 additions and 10 deletions
1
hrtime.h
1
hrtime.h
|
@ -206,6 +206,7 @@ double2hrtime(rb_hrtime_t *hrt, double d)
|
||||||
const double TIMESPEC_SEC_MAX_PLUS_ONE = 2.0 * (TIMESPEC_SEC_MAX_as_double / 2.0 + 1.0);
|
const double TIMESPEC_SEC_MAX_PLUS_ONE = 2.0 * (TIMESPEC_SEC_MAX_as_double / 2.0 + 1.0);
|
||||||
|
|
||||||
if (TIMESPEC_SEC_MAX_PLUS_ONE <= d) {
|
if (TIMESPEC_SEC_MAX_PLUS_ONE <= d) {
|
||||||
|
*hrt = RB_HRTIME_MAX;
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
else if (d <= 0) {
|
else if (d <= 0) {
|
||||||
|
|
10
re.c
10
re.c
|
@ -3772,6 +3772,8 @@ str_to_option(VALUE str)
|
||||||
* If optional keyword argument +timeout+ is given,
|
* If optional keyword argument +timeout+ is given,
|
||||||
* its float value overrides the timeout interval for the class,
|
* its float value overrides the timeout interval for the class,
|
||||||
* Regexp.timeout.
|
* Regexp.timeout.
|
||||||
|
* If +nil+ is passed as +timeout, it uses the timeout interval
|
||||||
|
* for the class, Regexp.timeout.
|
||||||
*
|
*
|
||||||
* With argument +regexp+ given, returns a new regexp. The source,
|
* With argument +regexp+ given, returns a new regexp. The source,
|
||||||
* options, timeout are the same as +regexp+. +options+ and +n_flag+
|
* options, timeout are the same as +regexp+. +options+ and +n_flag+
|
||||||
|
@ -3847,7 +3849,9 @@ rb_reg_initialize_m(int argc, VALUE *argv, VALUE self)
|
||||||
|
|
||||||
{
|
{
|
||||||
double limit = NIL_P(timeout) ? 0.0 : NUM2DBL(timeout);
|
double limit = NIL_P(timeout) ? 0.0 : NUM2DBL(timeout);
|
||||||
if (limit < 0) limit = 0;
|
if (!NIL_P(timeout) && limit <= 0) {
|
||||||
|
rb_raise(rb_eArgError, "invalid timeout: %"PRIsVALUE, timeout);
|
||||||
|
}
|
||||||
double2hrtime(®->timelimit, limit);
|
double2hrtime(®->timelimit, limit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4478,7 +4482,9 @@ rb_reg_s_timeout_set(VALUE dummy, VALUE limit)
|
||||||
|
|
||||||
rb_ractor_ensure_main_ractor("can not access Regexp.timeout from non-main Ractors");
|
rb_ractor_ensure_main_ractor("can not access Regexp.timeout from non-main Ractors");
|
||||||
|
|
||||||
if (timeout < 0) timeout = 0;
|
if (!NIL_P(limit) && timeout <= 0) {
|
||||||
|
rb_raise(rb_eArgError, "invalid timeout: %"PRIsVALUE, limit);
|
||||||
|
}
|
||||||
double2hrtime(&rb_reg_match_time_limit, timeout);
|
double2hrtime(&rb_reg_match_time_limit, timeout);
|
||||||
|
|
||||||
return limit;
|
return limit;
|
||||||
|
|
|
@ -1579,7 +1579,7 @@ class TestRegexp < Test::Unit::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_s_timeout
|
def test_s_timeout
|
||||||
assert_separately([], "#{<<-"begin;"}\n#{<<-"end;"}")
|
assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}")
|
||||||
begin;
|
begin;
|
||||||
timeout = EnvUtil.apply_timeout_scale(0.2)
|
timeout = EnvUtil.apply_timeout_scale(0.2)
|
||||||
|
|
||||||
|
@ -1597,15 +1597,42 @@ class TestRegexp < Test::Unit::TestCase
|
||||||
end;
|
end;
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_timeout
|
def test_s_timeout_corner_cases
|
||||||
assert_separately([], "#{<<-"begin;"}\n#{<<-"end;"}")
|
assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}")
|
||||||
begin;
|
begin;
|
||||||
dummy_timeout = EnvUtil.apply_timeout_scale(10)
|
assert_nil(Regexp.timeout)
|
||||||
timeout = EnvUtil.apply_timeout_scale(0.2)
|
|
||||||
|
|
||||||
Regexp.timeout = dummy_timeout # This should be ignored
|
# This is just an implementation detail that users should not depend on:
|
||||||
|
# If Regexp.timeout is set to a value greater than the value that can be
|
||||||
|
# represented in the internal representation of timeout, it uses the
|
||||||
|
# maximum value that can be represented.
|
||||||
|
Regexp.timeout = Float::INFINITY
|
||||||
|
assert_equal(((1<<64)-1) / 1000000000.0, Regexp.timeout)
|
||||||
|
|
||||||
re = Regexp.new("^a*b?a*$", timeout: timeout)
|
Regexp.timeout = 1e300
|
||||||
|
assert_equal(((1<<64)-1) / 1000000000.0, Regexp.timeout)
|
||||||
|
|
||||||
|
assert_raise(ArgumentError) { Regexp.timeout = 0 }
|
||||||
|
assert_raise(ArgumentError) { Regexp.timeout = -1 }
|
||||||
|
|
||||||
|
Regexp.timeout = nil
|
||||||
|
assert_nil(Regexp.timeout)
|
||||||
|
end;
|
||||||
|
end
|
||||||
|
|
||||||
|
def per_instance_redos_test(global_timeout, per_instance_timeout, expected_timeout)
|
||||||
|
assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}")
|
||||||
|
global_timeout = #{ global_timeout.inspect }
|
||||||
|
per_instance_timeout = #{ per_instance_timeout.inspect }
|
||||||
|
expected_timeout = #{ expected_timeout.inspect }
|
||||||
|
begin;
|
||||||
|
global_timeout = EnvUtil.apply_timeout_scale(global_timeout)
|
||||||
|
per_instance_timeout = EnvUtil.apply_timeout_scale(per_instance_timeout)
|
||||||
|
|
||||||
|
Regexp.timeout = global_timeout
|
||||||
|
|
||||||
|
re = Regexp.new("^a*b?a*$", timeout: per_instance_timeout)
|
||||||
|
assert_equal(per_instance_timeout, re.timeout)
|
||||||
|
|
||||||
t = Time.now
|
t = Time.now
|
||||||
assert_raise_with_message(Regexp::TimeoutError, "regexp match timeout") do
|
assert_raise_with_message(Regexp::TimeoutError, "regexp match timeout") do
|
||||||
|
@ -1613,7 +1640,37 @@ class TestRegexp < Test::Unit::TestCase
|
||||||
end
|
end
|
||||||
t = Time.now - t
|
t = Time.now - t
|
||||||
|
|
||||||
assert_in_delta(timeout, t, timeout / 2)
|
assert_in_delta(expected_timeout, t, expected_timeout / 2)
|
||||||
|
end;
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_timeout_shorter_than_global
|
||||||
|
per_instance_redos_test(10, 0.2, 0.2)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_timeout_longer_than_global
|
||||||
|
per_instance_redos_test(0.01, 0.5, 0.5)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_timeout_nil
|
||||||
|
per_instance_redos_test(0.5, nil, 0.5)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_timeout_corner_cases
|
||||||
|
assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}")
|
||||||
|
begin;
|
||||||
|
assert_nil(//.timeout)
|
||||||
|
|
||||||
|
# This is just an implementation detail that users should not depend on:
|
||||||
|
# If Regexp.timeout is set to a value greater than the value that can be
|
||||||
|
# represented in the internal representation of timeout, it uses the
|
||||||
|
# maximum value that can be represented.
|
||||||
|
assert_equal(((1<<64)-1) / 1000000000.0, Regexp.new("foo", timeout: Float::INFINITY).timeout)
|
||||||
|
|
||||||
|
assert_equal(((1<<64)-1) / 1000000000.0, Regexp.new("foo", timeout: 1e300).timeout)
|
||||||
|
|
||||||
|
assert_raise(ArgumentError) { Regexp.new("foo", timeout: 0) }
|
||||||
|
assert_raise(ArgumentError) { Regexp.new("foo", timeout: -1) }
|
||||||
end;
|
end;
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Add table
Reference in a new issue