From 9588654ae3881f7f2270e7dd464568dc7d82b379 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 11 Aug 2021 21:08:38 +0200 Subject: [PATCH 1/3] ClassMapGenerator: add tests for "long heredoc" bug ... to proof the existence of the bug and demonstrate the effect. Note: in the test the backtrack limit is being lowered (and restored back to the default afterwards) to prevent the tests needing ridiculously huge test fixture files. --- .../Test/Autoload/ClassMapGeneratorTest.php | 19 + .../pcrebacktracelimit/StripNoise.php | 87 ++ .../pcrebacktracelimit/VeryLongHeredoc.php | 464 ++++++++ .../pcrebacktracelimit/VeryLongNowdoc.php | 327 ++++++ .../VeryLongPHP73Heredoc.php | 307 +++++ .../VeryLongPHP73Nowdoc.php | 1018 +++++++++++++++++ 6 files changed, 2222 insertions(+) create mode 100644 tests/Composer/Test/Autoload/Fixtures/pcrebacktracelimit/StripNoise.php create mode 100644 tests/Composer/Test/Autoload/Fixtures/pcrebacktracelimit/VeryLongHeredoc.php create mode 100644 tests/Composer/Test/Autoload/Fixtures/pcrebacktracelimit/VeryLongNowdoc.php create mode 100644 tests/Composer/Test/Autoload/Fixtures/pcrebacktracelimit/VeryLongPHP73Heredoc.php create mode 100644 tests/Composer/Test/Autoload/Fixtures/pcrebacktracelimit/VeryLongPHP73Nowdoc.php diff --git a/tests/Composer/Test/Autoload/ClassMapGeneratorTest.php b/tests/Composer/Test/Autoload/ClassMapGeneratorTest.php index 1fb73c25300e..261fb7cede6e 100644 --- a/tests/Composer/Test/Autoload/ClassMapGeneratorTest.php +++ b/tests/Composer/Test/Autoload/ClassMapGeneratorTest.php @@ -244,6 +244,25 @@ public function testDump() $fs->removeDirectory($tempDir); } + public function testCreateMapDoesNotHitRegexBacktraceLimit() + { + $expected = array( + 'Foo\\StripNoise' => realpath(__DIR__) . '/Fixtures/pcrebacktracelimit/StripNoise.php', + 'Foo\\VeryLongHeredoc' => realpath(__DIR__) . '/Fixtures/pcrebacktracelimit/VeryLongHeredoc.php', + 'Foo\\ClassAfterLongHereDoc' => realpath(__DIR__) . '/Fixtures/pcrebacktracelimit/VeryLongHeredoc.php', + 'Foo\\VeryLongPHP73Heredoc' => realpath(__DIR__) . '/Fixtures/pcrebacktracelimit/VeryLongPHP73Heredoc.php', + 'Foo\\VeryLongPHP73Nowdoc' => realpath(__DIR__) . '/Fixtures/pcrebacktracelimit/VeryLongPHP73Nowdoc.php', + 'Foo\\ClassAfterLongNowDoc' => realpath(__DIR__) . '/Fixtures/pcrebacktracelimit/VeryLongPHP73Nowdoc.php', + 'Foo\\VeryLongNowdoc' => realpath(__DIR__) . '/Fixtures/pcrebacktracelimit/VeryLongNowdoc.php', + ); + + ini_set('pcre.backtrack_limit', '30000'); + $result = ClassMapGenerator::createMap(__DIR__ . '/Fixtures/pcrebacktracelimit'); + ini_restore('pcre.backtrack_limit'); + + $this->assertEqualsNormalized($expected, $result); + } + protected function assertEqualsNormalized($expected, $actual, $message = '') { foreach ($expected as $ns => $path) { diff --git a/tests/Composer/Test/Autoload/Fixtures/pcrebacktracelimit/StripNoise.php b/tests/Composer/Test/Autoload/Fixtures/pcrebacktracelimit/StripNoise.php new file mode 100644 index 000000000000..8f93e842751c --- /dev/null +++ b/tests/Composer/Test/Autoload/Fixtures/pcrebacktracelimit/StripNoise.php @@ -0,0 +1,87 @@ +'; + } + + public function test_simple_string() + { + return 'class FailSimpleString {}'; + } +} diff --git a/tests/Composer/Test/Autoload/Fixtures/pcrebacktracelimit/VeryLongHeredoc.php b/tests/Composer/Test/Autoload/Fixtures/pcrebacktracelimit/VeryLongHeredoc.php new file mode 100644 index 000000000000..11f87546de7f --- /dev/null +++ b/tests/Composer/Test/Autoload/Fixtures/pcrebacktracelimit/VeryLongHeredoc.php @@ -0,0 +1,464 @@ + Date: Wed, 11 Aug 2021 22:08:56 +0200 Subject: [PATCH 2/3] ClassMapGenerator: add test for "marker in text" bug In PHP < 7.3, the heredoc/nowdoc marker was allowed to occur in the text, as long as it did not occur at the very start of the line. This was also not handled correctly. Ref: https://www.php.net/manual/en/migration73.incompatible.php#migration73.incompatible.core.heredoc-nowdoc --- .../Test/Autoload/Fixtures/classmap/StripNoise.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/Composer/Test/Autoload/Fixtures/classmap/StripNoise.php b/tests/Composer/Test/Autoload/Fixtures/classmap/StripNoise.php index 8944360ee576..02da0a6c435c 100644 --- a/tests/Composer/Test/Autoload/Fixtures/classmap/StripNoise.php +++ b/tests/Composer/Test/Autoload/Fixtures/classmap/StripNoise.php @@ -17,7 +17,12 @@ class FailHeredocBasic class FailHeredocWhitespace { } -WHITESPACE . <<<"DOUBLEQUOTES" +WHITESPACE . <<< MARKERINTEXT + In PHP < 7.3, the docblock marker could occur in the text as long as it did not occur at the very start of the line. +But, what are you blind McFly, it's there. How else do you explain that wreck out there? Doc, Doc. Oh, no. You're alive. Bullet proof vest, how did you know, I never got a chance to tell you. About all that talk about screwing up future events, the space time continuum. Okay, alright, I'll prove it to you. + MARKERINTEXT + Look at my driver's license, expires 1987. Look at my birthday, for crying out load I haven't even been born yet. And, look at this picture, my brother, my sister, and me. Look at the sweatshirt, Doc, class of 1984. Why do you keep following me around? Hey beat it, spook, this don't concern you. +MARKERINTEXT . <<<"DOUBLEQUOTES" class FailHeredocDoubleQuotes { } From 42c6a0d7c50c94d79298248f5f8ae51a522f9844 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 11 Aug 2021 21:18:08 +0200 Subject: [PATCH 3/3] ClassMapGenerator: fix the regex By using a look ahead assertion to match "new line - maybe whitespace - marker", the negative performance impact of the `.*` is significantly mitigated and backtracing will be severely limited. This fixes the bug as reported in 10037. The bug was discovered due to a PHP 8.1 "passing null to non-nullable" deprecation notice being thrown, but is not a PHP 8.1 bug. In actual fact, this issue affected all PHP versions and could lead to incomplete classmaps when the code base contained files with huge heredocs/nowdocs. The regex change (not completely) incidentally also fixes an issue with markers in a heredoc/nowdoc not being correctly handled. This bug could lead to "classes" being added to the class map which aren't actually classes. Fixes 10037 --- src/Composer/Autoload/ClassMapGenerator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Composer/Autoload/ClassMapGenerator.php b/src/Composer/Autoload/ClassMapGenerator.php index abb0d5e9ad6e..33c2d48c87a1 100644 --- a/src/Composer/Autoload/ClassMapGenerator.php +++ b/src/Composer/Autoload/ClassMapGenerator.php @@ -246,7 +246,7 @@ private static function findClasses($path) } // strip heredocs/nowdocs - $contents = preg_replace('{<<<[ \t]*([\'"]?)(\w+)\\1(?:\r\n|\n|\r)(?:.*?)(?:\r\n|\n|\r)(?:\s*)\\2(?=\s+|[;,.)])}s', 'null', $contents); + $contents = preg_replace('{<<<[ \t]*([\'"]?)(\w+)\\1(?:\r\n|\n|\r)(?:.*(?=[\r\n]+[ \t]*\\2))[\r\n]+[ \t]*\\2(?=\s*[;,.)])}s', 'null', $contents); // strip strings $contents = preg_replace('{"[^"\\\\]*+(\\\\.[^"\\\\]*+)*+"|\'[^\'\\\\]*+(\\\\.[^\'\\\\]*+)*+\'}s', 'null', $contents); // strip leading non-php code if needed