Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion src/Assert.php
Original file line number Diff line number Diff line change
Expand Up @@ -1507,7 +1507,15 @@ public static function regex(mixed $value, mixed $pattern, string|callable $mess
static::string($value);
static::string($pattern);

if (!\preg_match($pattern, $value)) {
if (false === $result = @\preg_match($pattern, $value)) {
static::reportInvalidArgument(\sprintf(
'The pattern %s could not be evaluated: %s.',
static::valueToString($pattern),
\preg_last_error_msg()
));
}

if (0 === $result) {
$message = self::resolveMessage($message);
static::reportInvalidArgument(\sprintf(
$message ?: 'The value %s does not match the expected pattern.',
Expand Down
61 changes: 61 additions & 0 deletions tests/AssertTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
use DateTimeImmutable;
use Error;
use Exception;
use InvalidArgumentException;
use LogicException;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
Expand Down Expand Up @@ -991,6 +992,66 @@ public function testEnumAssertionErrorMessage(): void
Assert::null(DummyEnum::CaseName, 'Expected null. Got: %s');
}

public function testRegexRejectsAnUncompilablePattern(): void
{
$this->expectException('\InvalidArgumentException');
$this->expectExceptionMessage('The pattern "/(/" could not be evaluated: Internal error.');

Assert::regex('abc', '/(/');
}

public function testRegexSuppressesTheWarningForAnUncompilablePattern(): void
{
$reported = [];
// PHPUnit masks E_WARNING out of error_reporting(); restore it here.
$errorReporting = error_reporting(E_ALL);
set_error_handler(static function (int $errno, string $message) use (&$reported): bool {
// This is what a well behaved error handler does with a suppressed diagnostic.
if (0 !== (error_reporting() & $errno)) {
$reported[] = $message;
}

return true;
});

try {
Assert::regex('abc', '/(/');
} catch (InvalidArgumentException) {
// The assertion is expected to fail; this test only inspects the reported errors.
} finally {
restore_error_handler();
error_reporting($errorReporting);
}

$this->assertSame([], $reported);
}

public function testRegexDoesNotUseTheCustomMessageForAnUncompilablePattern(): void
{
$this->expectException('\InvalidArgumentException');
$this->expectExceptionMessage('The pattern "/(/" could not be evaluated: Internal error.');

Assert::regex('abc', '/(/', 'The value %s is not a valid slug.');
}

public function testRegexRejectsAPatternExceedingTheBacktrackLimit(): void
{
$backtrackLimit = ini_get('pcre.backtrack_limit');
ini_set('pcre.backtrack_limit', '100');

try {
Assert::regex(str_repeat('a', 30).'c', '/^(a+)+$/');
$this->fail('Expected an InvalidArgumentException to be thrown.');
} catch (InvalidArgumentException $e) {
$this->assertSame(
'The pattern "/^(a+)+$/" could not be evaluated: Backtrack limit exhausted.',
$e->getMessage()
);
} finally {
ini_set('pcre.backtrack_limit', $backtrackLimit);
}
}

#[DataProvider('getMethodsThatUseOtherMethods')]
public function testMessageIsPassedToInternalCalls(string $method, array $args, string $exceptionMessage): void
{
Expand Down