Skip to content

Commit f3ab307

Browse files
authored
Merge pull request #247 from phpDocumentor/safe_preg_split
Introduce safe preg_split
2 parents d870572 + 690d9cd commit f3ab307

File tree

14 files changed

+229
-34
lines changed

14 files changed

+229
-34
lines changed

src/DocBlock/DescriptionFactory.php

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,12 @@
1414
namespace phpDocumentor\Reflection\DocBlock;
1515

1616
use phpDocumentor\Reflection\Types\Context as TypeContext;
17-
use Webmozart\Assert\Assert;
17+
use phpDocumentor\Reflection\Utils;
1818
use function count;
1919
use function explode;
2020
use function implode;
2121
use function ltrim;
2222
use function min;
23-
use function preg_split;
2423
use function str_replace;
2524
use function strlen;
2625
use function strpos;
@@ -98,7 +97,7 @@ private function lex(string $contents) : array
9897
return [$contents];
9998
}
10099

101-
$parts = preg_split(
100+
return Utils::pregSplit(
102101
'/\{
103102
# "{@}" is not a valid inline tag. This ensures that we do not treat it as one, but treat it literally.
104103
(?!@\})
@@ -127,9 +126,6 @@ private function lex(string $contents) : array
127126
0,
128127
PREG_SPLIT_DELIM_CAPTURE
129128
);
130-
Assert::isArray($parts);
131-
132-
return $parts;
133129
}
134130

135131
/**

src/DocBlock/Tags/Covers.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@
1818
use phpDocumentor\Reflection\Fqsen;
1919
use phpDocumentor\Reflection\FqsenResolver;
2020
use phpDocumentor\Reflection\Types\Context as TypeContext;
21+
use phpDocumentor\Reflection\Utils;
2122
use Webmozart\Assert\Assert;
22-
use function preg_split;
2323

2424
/**
2525
* Reflection class for a @covers tag in a Docblock.
@@ -51,8 +51,7 @@ public static function create(
5151
Assert::notNull($descriptionFactory);
5252
Assert::notNull($resolver);
5353

54-
$parts = preg_split('/\s+/Su', $body, 2);
55-
Assert::isArray($parts);
54+
$parts = Utils::pregSplit('/\s+/Su', $body, 2);
5655

5756
return new static(
5857
$resolver->resolve($parts[0], $context),

src/DocBlock/Tags/Link.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616
use phpDocumentor\Reflection\DocBlock\Description;
1717
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
1818
use phpDocumentor\Reflection\Types\Context as TypeContext;
19+
use phpDocumentor\Reflection\Utils;
1920
use Webmozart\Assert\Assert;
20-
use function preg_split;
2121

2222
/**
2323
* Reflection class for a @link tag in a Docblock.
@@ -46,8 +46,7 @@ public static function create(
4646
) : self {
4747
Assert::notNull($descriptionFactory);
4848

49-
$parts = preg_split('/\s+/Su', $body, 2);
50-
Assert::isArray($parts);
49+
$parts = Utils::pregSplit('/\s+/Su', $body, 2);
5150
$description = isset($parts[1]) ? $descriptionFactory->create($parts[1], $context) : null;
5251

5352
return new static($parts[0], $description);

src/DocBlock/Tags/Param.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@
1818
use phpDocumentor\Reflection\Type;
1919
use phpDocumentor\Reflection\TypeResolver;
2020
use phpDocumentor\Reflection\Types\Context as TypeContext;
21+
use phpDocumentor\Reflection\Utils;
2122
use Webmozart\Assert\Assert;
2223
use function array_shift;
2324
use function array_unshift;
2425
use function implode;
25-
use function preg_split;
2626
use function strpos;
2727
use function substr;
2828
use const PREG_SPLIT_DELIM_CAPTURE;
@@ -64,8 +64,7 @@ public static function create(
6464
[$firstPart, $body] = self::extractTypeFromBody($body);
6565

6666
$type = null;
67-
$parts = preg_split('/(\s+)/Su', $body, 2, PREG_SPLIT_DELIM_CAPTURE);
68-
Assert::isArray($parts);
67+
$parts = Utils::pregSplit('/(\s+)/Su', $body, 2, PREG_SPLIT_DELIM_CAPTURE);
6968
$variableName = '';
7069
$isVariadic = false;
7170

src/DocBlock/Tags/Property.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@
1818
use phpDocumentor\Reflection\Type;
1919
use phpDocumentor\Reflection\TypeResolver;
2020
use phpDocumentor\Reflection\Types\Context as TypeContext;
21+
use phpDocumentor\Reflection\Utils;
2122
use Webmozart\Assert\Assert;
2223
use function array_shift;
2324
use function array_unshift;
2425
use function implode;
25-
use function preg_split;
2626
use function strpos;
2727
use function substr;
2828
use const PREG_SPLIT_DELIM_CAPTURE;
@@ -57,8 +57,7 @@ public static function create(
5757

5858
[$firstPart, $body] = self::extractTypeFromBody($body);
5959
$type = null;
60-
$parts = preg_split('/(\s+)/Su', $body, 2, PREG_SPLIT_DELIM_CAPTURE);
61-
Assert::isArray($parts);
60+
$parts = Utils::pregSplit('/(\s+)/Su', $body, 2, PREG_SPLIT_DELIM_CAPTURE);
6261
$variableName = '';
6362

6463
// if the first item that is encountered is not a variable; it is a type

src/DocBlock/Tags/PropertyRead.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@
1818
use phpDocumentor\Reflection\Type;
1919
use phpDocumentor\Reflection\TypeResolver;
2020
use phpDocumentor\Reflection\Types\Context as TypeContext;
21+
use phpDocumentor\Reflection\Utils;
2122
use Webmozart\Assert\Assert;
2223
use function array_shift;
2324
use function array_unshift;
2425
use function implode;
25-
use function preg_split;
2626
use function strpos;
2727
use function substr;
2828
use const PREG_SPLIT_DELIM_CAPTURE;
@@ -57,8 +57,7 @@ public static function create(
5757

5858
[$firstPart, $body] = self::extractTypeFromBody($body);
5959
$type = null;
60-
$parts = preg_split('/(\s+)/Su', $body, 2, PREG_SPLIT_DELIM_CAPTURE);
61-
Assert::isArray($parts);
60+
$parts = Utils::pregSplit('/(\s+)/Su', $body, 2, PREG_SPLIT_DELIM_CAPTURE);
6261
$variableName = '';
6362

6463
// if the first item that is encountered is not a variable; it is a type

src/DocBlock/Tags/PropertyWrite.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@
1818
use phpDocumentor\Reflection\Type;
1919
use phpDocumentor\Reflection\TypeResolver;
2020
use phpDocumentor\Reflection\Types\Context as TypeContext;
21+
use phpDocumentor\Reflection\Utils;
2122
use Webmozart\Assert\Assert;
2223
use function array_shift;
2324
use function array_unshift;
2425
use function implode;
25-
use function preg_split;
2626
use function strpos;
2727
use function substr;
2828
use const PREG_SPLIT_DELIM_CAPTURE;
@@ -57,8 +57,7 @@ public static function create(
5757

5858
[$firstPart, $body] = self::extractTypeFromBody($body);
5959
$type = null;
60-
$parts = preg_split('/(\s+)/Su', $body, 2, PREG_SPLIT_DELIM_CAPTURE);
61-
Assert::isArray($parts);
60+
$parts = Utils::pregSplit('/(\s+)/Su', $body, 2, PREG_SPLIT_DELIM_CAPTURE);
6261
$variableName = '';
6362

6463
// if the first item that is encountered is not a variable; it is a type

src/DocBlock/Tags/See.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@
2020
use phpDocumentor\Reflection\DocBlock\Tags\Reference\Url;
2121
use phpDocumentor\Reflection\FqsenResolver;
2222
use phpDocumentor\Reflection\Types\Context as TypeContext;
23+
use phpDocumentor\Reflection\Utils;
2324
use Webmozart\Assert\Assert;
2425
use function preg_match;
25-
use function preg_split;
2626

2727
/**
2828
* Reflection class for an {@}see tag in a Docblock.
@@ -53,8 +53,7 @@ public static function create(
5353
Assert::notNull($typeResolver);
5454
Assert::notNull($descriptionFactory);
5555

56-
$parts = preg_split('/\s+/Su', $body, 2);
57-
Assert::isArray($parts);
56+
$parts = Utils::pregSplit('/\s+/Su', $body, 2);
5857
$description = isset($parts[1]) ? $descriptionFactory->create($parts[1], $context) : null;
5958

6059
// https://tools.ietf.org/html/rfc2396#section-3

src/DocBlock/Tags/Uses.php

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@
1818
use phpDocumentor\Reflection\Fqsen;
1919
use phpDocumentor\Reflection\FqsenResolver;
2020
use phpDocumentor\Reflection\Types\Context as TypeContext;
21+
use phpDocumentor\Reflection\Utils;
2122
use Webmozart\Assert\Assert;
22-
use function preg_split;
2323

2424
/**
2525
* Reflection class for a {@}uses tag in a Docblock.
@@ -50,9 +50,7 @@ public static function create(
5050
Assert::notNull($resolver);
5151
Assert::notNull($descriptionFactory);
5252

53-
$parts = preg_split('/\s+/Su', $body, 2);
54-
Assert::isArray($parts);
55-
Assert::allString($parts);
53+
$parts = Utils::pregSplit('/\s+/Su', $body, 2);
5654

5755
return new static(
5856
$resolver->resolve($parts[0], $context),

src/DocBlock/Tags/Var_.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@
1818
use phpDocumentor\Reflection\Type;
1919
use phpDocumentor\Reflection\TypeResolver;
2020
use phpDocumentor\Reflection\Types\Context as TypeContext;
21+
use phpDocumentor\Reflection\Utils;
2122
use Webmozart\Assert\Assert;
2223
use function array_shift;
2324
use function array_unshift;
2425
use function implode;
25-
use function preg_split;
2626
use function strpos;
2727
use function substr;
2828
use const PREG_SPLIT_DELIM_CAPTURE;
@@ -57,8 +57,7 @@ public static function create(
5757

5858
[$firstPart, $body] = self::extractTypeFromBody($body);
5959

60-
$parts = preg_split('/(\s+)/Su', $body, 2, PREG_SPLIT_DELIM_CAPTURE);
61-
Assert::isArray($parts);
60+
$parts = Utils::pregSplit('/(\s+)/Su', $body, 2, PREG_SPLIT_DELIM_CAPTURE);
6261
$type = null;
6362
$variableName = '';
6463

src/Exception/PcreException.php

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace phpDocumentor\Reflection\Exception;
6+
7+
use InvalidArgumentException;
8+
use const PREG_BACKTRACK_LIMIT_ERROR;
9+
use const PREG_BAD_UTF8_ERROR;
10+
use const PREG_BAD_UTF8_OFFSET_ERROR;
11+
use const PREG_INTERNAL_ERROR;
12+
use const PREG_JIT_STACKLIMIT_ERROR;
13+
use const PREG_NO_ERROR;
14+
use const PREG_RECURSION_LIMIT_ERROR;
15+
16+
final class PcreException extends InvalidArgumentException
17+
{
18+
public static function createFromPhpError(int $errorCode) : self
19+
{
20+
switch ($errorCode) {
21+
case PREG_BACKTRACK_LIMIT_ERROR:
22+
return new self('Backtrack limit error');
23+
case PREG_RECURSION_LIMIT_ERROR:
24+
return new self('Recursion limit error');
25+
case PREG_BAD_UTF8_ERROR:
26+
return new self('Bad UTF8 error');
27+
case PREG_BAD_UTF8_OFFSET_ERROR:
28+
return new self('Bad UTF8 offset error');
29+
case PREG_JIT_STACKLIMIT_ERROR:
30+
return new self('Jit stacklimit error');
31+
case PREG_NO_ERROR:
32+
case PREG_INTERNAL_ERROR:
33+
default:
34+
}
35+
36+
return new self('Unknown Pcre error');
37+
}
38+
}

src/Utils.php

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* This file is part of phpDocumentor.
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*
11+
* @link http://phpdoc.org
12+
*/
13+
14+
namespace phpDocumentor\Reflection;
15+
16+
use phpDocumentor\Reflection\Exception\PcreException;
17+
use function preg_last_error;
18+
use function preg_split as php_preg_split;
19+
20+
abstract class Utils
21+
{
22+
/**
23+
* Wrapper function for phps preg_split
24+
*
25+
* This function is inspired by {@link https://github.com/thecodingmachine/safe/blob/master/generated/pcre.php}. But
26+
* since this library is all about performance we decided to strip everything we don't need. Reducing the amount
27+
* of files that have to be loaded, ect.
28+
*
29+
* @param string $pattern The pattern to search for, as a string.
30+
* @param string $subject The input string.
31+
* @param int|null $limit If specified, then only substrings up to limit are returned with the
32+
* rest of the string being placed in the last substring. A limit of -1 or 0 means "no limit".
33+
* @param int $flags flags can be any combination of the following flags (combined with the | bitwise operator):
34+
* *PREG_SPLIT_NO_EMPTY*
35+
* If this flag is set, only non-empty pieces will be returned by preg_split().
36+
* *PREG_SPLIT_DELIM_CAPTURE*
37+
* If this flag is set, parenthesized expression in the delimiter pattern will be captured
38+
* and returned as well.
39+
* *PREG_SPLIT_OFFSET_CAPTURE*
40+
* If this flag is set, for every occurring match the appendant string offset will also be returned.
41+
* Note that this changes the return value in an array where every element is an array consisting of the
42+
* matched string at offset 0 and its string offset into subject at offset 1.
43+
*
44+
* @return string[] Returns an array containing substrings of subject split along boundaries matched by pattern
45+
*
46+
* @throws PcreException
47+
*/
48+
public static function pregSplit(string $pattern, string $subject, ?int $limit = -1, int $flags = 0) : array
49+
{
50+
$parts = php_preg_split($pattern, $subject, $limit, $flags);
51+
if ($parts === false) {
52+
throw PcreException::createFromPhpError(preg_last_error());
53+
}
54+
55+
return $parts;
56+
}
57+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace phpDocumentor\Reflection\Exception;
6+
7+
use PHPUnit\Framework\TestCase;
8+
use const PREG_BACKTRACK_LIMIT_ERROR;
9+
use const PREG_BAD_UTF8_ERROR;
10+
use const PREG_BAD_UTF8_OFFSET_ERROR;
11+
use const PREG_INTERNAL_ERROR;
12+
use const PREG_JIT_STACKLIMIT_ERROR;
13+
use const PREG_NO_ERROR;
14+
use const PREG_RECURSION_LIMIT_ERROR;
15+
16+
/**
17+
* @coversDefaultClass \phpDocumentor\Reflection\Exception\PcreException
18+
*/
19+
final class PcreExceptionTest extends TestCase
20+
{
21+
/**
22+
* @covers ::createFromPhpError
23+
* @dataProvider errorCodeProvider
24+
*/
25+
public function testErrorConversion(int $errorCode, string $message) : void
26+
{
27+
$this->assertSame($message, PcreException::createFromPhpError($errorCode)->getMessage());
28+
}
29+
30+
/**
31+
* @return array<int, (string|int)[]>
32+
*/
33+
public function errorCodeProvider() : array
34+
{
35+
return [
36+
[
37+
PREG_BACKTRACK_LIMIT_ERROR,
38+
'Backtrack limit error',
39+
],
40+
[
41+
PREG_RECURSION_LIMIT_ERROR,
42+
'Recursion limit error',
43+
],
44+
[
45+
PREG_BAD_UTF8_ERROR,
46+
'Bad UTF8 error',
47+
],
48+
[
49+
PREG_BAD_UTF8_OFFSET_ERROR,
50+
'Bad UTF8 offset error',
51+
],
52+
[
53+
PREG_JIT_STACKLIMIT_ERROR,
54+
'Jit stacklimit error',
55+
],
56+
[
57+
PREG_NO_ERROR,
58+
'Unknown Pcre error',
59+
],
60+
[
61+
PREG_INTERNAL_ERROR,
62+
'Unknown Pcre error',
63+
],
64+
];
65+
}
66+
}

0 commit comments

Comments
 (0)