From 5d2e21969a8772b20fd7b5891bf7946999611a17 Mon Sep 17 00:00:00 2001 From: PabloKowalczyk <11366345+PabloKowalczyk@users.noreply.github.com> Date: Sat, 27 Jul 2019 08:20:31 +0200 Subject: [PATCH 1/2] Prevent "interval" overflow in ExtUvLoop --- src/ExtUvLoop.php | 29 ++++++++++++-- tests/AbstractLoopTest.php | 11 +++++- tests/ExtUvLoopTest.php | 78 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 114 insertions(+), 4 deletions(-) diff --git a/src/ExtUvLoop.php b/src/ExtUvLoop.php index aade9943..70471b59 100644 --- a/src/ExtUvLoop.php +++ b/src/ExtUvLoop.php @@ -127,7 +127,7 @@ public function addTimer($interval, $callback) $this->timers->attach($timer, $event); \uv_timer_start( $event, - (int) ($interval * 1000) + 1, + $this->convertFloatSecondsToMilliseconds($interval), 0, $callback ); @@ -146,12 +146,13 @@ public function addPeriodicTimer($interval, $callback) \call_user_func($timer->getCallback(), $timer); }; + $interval = $this->convertFloatSecondsToMilliseconds($interval); $event = \uv_timer_init($this->uv); $this->timers->attach($timer, $event); \uv_timer_start( $event, - (int) ($interval * 1000) + 1, - (int) ($interval * 1000) + 1, + $interval, + (int) $interval === 0 ? 1 : $interval, $callback ); @@ -313,4 +314,26 @@ private function createStreamListener() return $callback; } + + /** + * @param float $interval + * @return int + */ + private function convertFloatSecondsToMilliseconds($interval) + { + if ($interval < 0) { + return 0; + } + + $maxValue = (int) (\PHP_INT_MAX / 1000); + $intInterval = (int) $interval; + + if (($intInterval <= 0 && $interval > 1) || $intInterval >= $maxValue) { + throw new \InvalidArgumentException( + "Interval overflow, value must be lower than '{$maxValue}', but '{$interval}' passed." + ); + } + + return (int) \floor($interval * 1000); + } } diff --git a/tests/AbstractLoopTest.php b/tests/AbstractLoopTest.php index 83f5756b..a2d24513 100644 --- a/tests/AbstractLoopTest.php +++ b/tests/AbstractLoopTest.php @@ -589,11 +589,20 @@ public function testSignalsKeepTheLoopRunningAndRemovingItStopsTheLoop() $this->assertRunFasterThan(1.6); } + public function testTimerIntervalBelowZeroRunsImmediately() + { + $this->loop->addTimer(-1, function () {}); + + $this->assertRunFasterThan(0.002); + } + public function testTimerIntervalCanBeFarInFuture() { + // Maximum interval for ExtUvLoop implementation + $interval = ((int) (PHP_INT_MAX / 1000)) - 1; $loop = $this->loop; // start a timer very far in the future - $timer = $this->loop->addTimer(PHP_INT_MAX, function () { }); + $timer = $this->loop->addTimer($interval, function () { }); $this->loop->futureTick(function () use ($timer, $loop) { $loop->cancelTimer($timer); diff --git a/tests/ExtUvLoopTest.php b/tests/ExtUvLoopTest.php index 61a94a9f..267eddf1 100644 --- a/tests/ExtUvLoopTest.php +++ b/tests/ExtUvLoopTest.php @@ -14,4 +14,82 @@ public function createLoop() return new ExtUvLoop(); } + + /** @dataProvider intervalProvider */ + public function testTimerInterval($interval, $expectedExceptionMessage) + { + $this->expectException('InvalidArgumentException'); + $this->expectExceptionMessage($expectedExceptionMessage); + + $this->loop + ->addTimer( + $interval, + function () { + return 0; + } + ); + } + + public function intervalProvider() + { + $oversizeInterval = PHP_INT_MAX / 1000; + $maxValue = (int) (PHP_INT_MAX / 1000); + $oneMaxValue = $maxValue + 1; + $tenMaxValue = $maxValue + 10; + $tenMillionsMaxValue = $maxValue + 10000000; + $intMax = PHP_INT_MAX; + $oneIntMax = PHP_INT_MAX + 1; + $tenIntMax = PHP_INT_MAX + 10; + $oneHundredIntMax = PHP_INT_MAX + 100; + $oneThousandIntMax = PHP_INT_MAX + 1000; + $tenMillionsIntMax = PHP_INT_MAX + 10000000; + $tenThousandsTimesIntMax = PHP_INT_MAX * 1000; + + return array( + array( + $oversizeInterval, + "Interval overflow, value must be lower than '{$maxValue}', but '{$oversizeInterval}' passed." + ), + array( + $oneMaxValue, + "Interval overflow, value must be lower than '{$maxValue}', but '{$oneMaxValue}' passed.", + ), + array( + $tenMaxValue, + "Interval overflow, value must be lower than '{$maxValue}', but '{$tenMaxValue}' passed.", + ), + array( + $tenMillionsMaxValue, + "Interval overflow, value must be lower than '{$maxValue}', but '{$tenMillionsMaxValue}' passed.", + ), + array( + $intMax, + "Interval overflow, value must be lower than '{$maxValue}', but '{$intMax}' passed.", + ), + array( + $oneIntMax, + "Interval overflow, value must be lower than '{$maxValue}', but '{$oneIntMax}' passed.", + ), + array( + $tenIntMax, + "Interval overflow, value must be lower than '{$maxValue}', but '{$tenIntMax}' passed.", + ), + array( + $oneHundredIntMax, + "Interval overflow, value must be lower than '{$maxValue}', but '{$oneHundredIntMax}' passed.", + ), + array( + $oneThousandIntMax, + "Interval overflow, value must be lower than '{$maxValue}', but '{$oneThousandIntMax}' passed.", + ), + array( + $tenMillionsIntMax, + "Interval overflow, value must be lower than '{$maxValue}', but '{$tenMillionsIntMax}' passed.", + ), + array( + $tenThousandsTimesIntMax, + "Interval overflow, value must be lower than '{$maxValue}', but '{$tenThousandsTimesIntMax}' passed.", + ), + ); + } } From a3165da14fcf467518445be4a0bd794d96cf5ce9 Mon Sep 17 00:00:00 2001 From: PabloKowalczyk <11366345+PabloKowalczyk@users.noreply.github.com> Date: Sat, 3 Aug 2019 09:55:15 +0200 Subject: [PATCH 2/2] Move "testTimerIntervalBelowZeroRunsImmediately" from AbstractLoopTest to AbstractTimerTest --- tests/AbstractLoopTest.php | 7 ------- tests/Timer/AbstractTimerTest.php | 18 ++++++++++++++++++ 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/tests/AbstractLoopTest.php b/tests/AbstractLoopTest.php index a2d24513..2e46b66e 100644 --- a/tests/AbstractLoopTest.php +++ b/tests/AbstractLoopTest.php @@ -589,13 +589,6 @@ public function testSignalsKeepTheLoopRunningAndRemovingItStopsTheLoop() $this->assertRunFasterThan(1.6); } - public function testTimerIntervalBelowZeroRunsImmediately() - { - $this->loop->addTimer(-1, function () {}); - - $this->assertRunFasterThan(0.002); - } - public function testTimerIntervalCanBeFarInFuture() { // Maximum interval for ExtUvLoop implementation diff --git a/tests/Timer/AbstractTimerTest.php b/tests/Timer/AbstractTimerTest.php index 11187b31..0f96c9fe 100644 --- a/tests/Timer/AbstractTimerTest.php +++ b/tests/Timer/AbstractTimerTest.php @@ -137,4 +137,22 @@ public function testMinimumIntervalOneMicrosecond() $this->assertEquals(0.000001, $timer->getInterval()); } + + public function testTimerIntervalBelowZeroRunsImmediately() + { + $loop = $this->createLoop(); + $start = 0; + $loop->addTimer( + -1, + function () use (&$start) { + $start = \microtime(true); + } + ); + + $loop->run(); + $end = \microtime(true); + + // 1ms should be enough even on slow machines + $this->assertLessThan(0.001, $end - $start); + } }
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: