Skip to content

Commit 3da83d0

Browse files
[Console] Make sure signals registered by SignalableCommandInterface commands are defined in the PCNTL extension
1 parent 38b5992 commit 3da83d0

File tree

4 files changed

+80
-0
lines changed

4 files changed

+80
-0
lines changed

src/Symfony/Component/Console/Application.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
2929
use Symfony\Component\Console\Exception\CommandNotFoundException;
3030
use Symfony\Component\Console\Exception\ExceptionInterface;
31+
use Symfony\Component\Console\Exception\InvalidArgumentException;
3132
use Symfony\Component\Console\Exception\LogicException;
3233
use Symfony\Component\Console\Exception\NamespaceNotFoundException;
3334
use Symfony\Component\Console\Exception\RuntimeException;
@@ -1006,6 +1007,12 @@ protected function doRunCommand(Command $command, InputInterface $input, OutputI
10061007
throw new RuntimeException('Unable to subscribe to signal events. Make sure that the `pcntl` extension is installed and that "pcntl_*" functions are not disabled by your php.ini\'s "disable_functions" directive.');
10071008
}
10081009

1010+
foreach (array_merge($commandSignals, $this->signalsToDispatchEvent) as $signal) {
1011+
if (!SignalRegistry::isValidSignal($signal)) {
1012+
throw new InvalidArgumentException(sprintf('Signal "%d" is not valid, make sure to use one of the "SIG*" constants as defined by the "pcntl" extension.', $signal));
1013+
}
1014+
}
1015+
10091016
if (Terminal::hasSttyAvailable()) {
10101017
$sttyMode = shell_exec('stty -g');
10111018

src/Symfony/Component/Console/SignalRegistry/SignalRegistry.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ final class SignalRegistry
1515
{
1616
private array $signalHandlers = [];
1717

18+
private static array $availableSignals;
19+
1820
public function __construct()
1921
{
2022
if (\function_exists('pcntl_async_signals')) {
@@ -42,6 +44,13 @@ public static function isSupported(): bool
4244
return \function_exists('pcntl_signal');
4345
}
4446

47+
public static function isValidSignal(int $signal): bool
48+
{
49+
static::$availableSignals ??= get_defined_constants(true)['pcntl'] ?? [];
50+
51+
return $signal < 32 && \in_array($signal, static::$availableSignals, true);
52+
}
53+
4554
/**
4655
* @internal
4756
*/

src/Symfony/Component/Console/Tests/ApplicationTest.php

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
use Symfony\Component\Console\Event\ConsoleSignalEvent;
2828
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
2929
use Symfony\Component\Console\Exception\CommandNotFoundException;
30+
use Symfony\Component\Console\Exception\InvalidArgumentException;
3031
use Symfony\Component\Console\Exception\NamespaceNotFoundException;
3132
use Symfony\Component\Console\Helper\FormatterHelper;
3233
use Symfony\Component\Console\Helper\HelperSet;
@@ -2137,6 +2138,37 @@ public function testSignalableWithEventCommandDoesNotInterruptedOnTermSignals()
21372138
$this->assertSame($expected, $tester->getDisplay(true));
21382139
}
21392140

2141+
/**
2142+
* @requires extension pcntl
2143+
*/
2144+
public function testSignalIsNotAvailableInPcntlExtension()
2145+
{
2146+
$this->expectException(InvalidArgumentException::class);
2147+
$this->expectExceptionMessage('Signal "40" is not valid, make sure to use one of the "SIG*" constants as defined by the "pcntl" extension.');
2148+
2149+
$command = new InvalidSignalableCommand();
2150+
$application = $this->createSignalableApplication($command, null);
2151+
$application->setCatchExceptions(false);
2152+
2153+
$application->run(new ArrayInput(['signal']));
2154+
}
2155+
2156+
/**
2157+
* @requires extension pcntl
2158+
*/
2159+
public function testSignalToDispatchIsNotAvailableInPcntlExtension()
2160+
{
2161+
$this->expectException(InvalidArgumentException::class);
2162+
$this->expectExceptionMessage('Signal "40" is not valid, make sure to use one of the "SIG*" constants as defined by the "pcntl" extension.');
2163+
2164+
$command = new SignableCommand();
2165+
$application = $this->createSignalableApplication($command, null);
2166+
$application->setSignalsToDispatchEvent(40);
2167+
$application->setCatchExceptions(false);
2168+
2169+
$application->run(new ArrayInput(['signal']));
2170+
}
2171+
21402172
/**
21412173
* @group tty
21422174
*/
@@ -2283,6 +2315,23 @@ public function handleSignal(int $signal, int|false $previousExitCode = 0): int|
22832315
}
22842316
}
22852317

2318+
#[AsCommand(name: 'signal')]
2319+
class InvalidSignalableCommand extends BaseSignableCommand implements SignalableCommandInterface
2320+
{
2321+
public function getSubscribedSignals(): array
2322+
{
2323+
return [40];
2324+
}
2325+
2326+
public function handleSignal(int $signal, int|false $previousExitCode = 0): int|false
2327+
{
2328+
$this->signaled = true;
2329+
$this->signalHandlers[] = __CLASS__;
2330+
2331+
return false;
2332+
}
2333+
}
2334+
22862335
#[AsCommand(name: 'signal')]
22872336
class TerminatableCommand extends BaseSignableCommand implements SignalableCommandInterface
22882337
{

src/Symfony/Component/Console/Tests/SignalRegistry/SignalRegistryTest.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,4 +131,19 @@ public function testTwoCallbacksForASignalPreviousCallbackFromAnotherRegistry()
131131
$this->assertTrue($isHandled1);
132132
$this->assertTrue($isHandled2);
133133
}
134+
135+
public function testIsValidSignalWithPcntlConstantButValueIsTooHigh()
136+
{
137+
$this->assertFalse(SignalRegistry::isValidSignal(\PCNTL_ENAMETOOLONG));
138+
}
139+
140+
public function testIsValidSignalWithInvalidSignalCode()
141+
{
142+
$this->assertFalse(SignalRegistry::isValidSignal(-12));
143+
}
144+
145+
public function testIsValidSignal()
146+
{
147+
$this->assertTrue(SignalRegistry::isValidSignal(\SIGINT));
148+
}
134149
}

0 commit comments

Comments
 (0)