From ba19a82c069ad22b9455803834dbbb4d87c7a811 Mon Sep 17 00:00:00 2001 From: Brad Jorsch Date: Thu, 24 Mar 2016 10:08:29 -0400 Subject: [PATCH] Add handling for PCRE errors in ustringGsub Bug: T130823 Change-Id: I6fab71c82ddab92daf6b369cb9857d9892f2d246 --- engines/LuaCommon/UstringLibrary.php | 46 +++++++++++++++++++ .../LuaCommon/UstringLibraryPureLuaTest.php | 14 ++++++ .../engines/LuaCommon/UstringLibraryTest.php | 37 +++++++++++++++ 3 files changed, 97 insertions(+) diff --git a/engines/LuaCommon/UstringLibrary.php b/engines/LuaCommon/UstringLibrary.php index 6b8ec2c5..7511a1b6 100644 --- a/engines/LuaCommon/UstringLibrary.php +++ b/engines/LuaCommon/UstringLibrary.php @@ -695,6 +695,52 @@ class Scribunto_LuaUstringLibrary extends Scribunto_LuaLibraryBase { $count = 0; $s2 = preg_replace_callback( $re, $cb, $s, $n, $count ); + if ( $s2 === null ) { + self::handlePCREError( preg_last_error(), $pattern ); + } return array( $s2, $count ); } + + /** + * Handle a PCRE error + * @param int $error From preg_last_error() + * @param string $pattern Pattern being matched + * @throws Scribunto_LuaError + */ + private function handlePCREError( $error, $pattern ) { + $PREG_JIT_STACKLIMIT_ERROR = defined( 'PREG_JIT_STACKLIMIT_ERROR' ) + ? PREG_JIT_STACKLIMIT_ERROR + : 'PREG_JIT_STACKLIMIT_ERROR'; + + $error = preg_last_error(); + switch ( $error ) { + case PREG_NO_ERROR: + // Huh? + break; + case PREG_INTERNAL_ERROR: + throw new Scribunto_LuaError( "PCRE internal error" ); + case PREG_BACKTRACK_LIMIT_ERROR: + throw new Scribunto_LuaError( + "PCRE backtrack limit reached while matching pattern '$pattern'" + ); + case PREG_RECURSION_LIMIT_ERROR: + throw new Scribunto_LuaError( + "PCRE recursion limit reached while matching pattern '$pattern'" + ); + case PREG_BAD_UTF8_ERROR: + // Should have alreay been caught, but just in case + throw new Scribunto_LuaError( "PCRE bad UTF-8 error" ); + case PREG_BAD_UTF8_OFFSET_ERROR: + // Shouldn't happen, but just in case + throw new Scribunto_LuaError( "PCRE bad UTF-8 offset error" ); + case $PREG_JIT_STACKLIMIT_ERROR: + throw new Scribunto_LuaError( + "PCRE JIT stack limit reached while matching pattern '$pattern'" + ); + default: + throw new Scribunto_LuaError( + "PCRE error code $error while matching pattern '$pattern'" + ); + } + } } diff --git a/tests/engines/LuaCommon/UstringLibraryPureLuaTest.php b/tests/engines/LuaCommon/UstringLibraryPureLuaTest.php index ad55265f..89e3be68 100644 --- a/tests/engines/LuaCommon/UstringLibraryPureLuaTest.php +++ b/tests/engines/LuaCommon/UstringLibraryPureLuaTest.php @@ -18,4 +18,18 @@ class Scribunto_LuaUstringLibraryPureLuaTests extends Scribunto_LuaUstringLibrar ', 'fortest' ) ); } + + /** + * @dataProvider providePCREErrors + */ + public function testPCREErrors( $ini, $args, $error ) { + // Not applicable + $this->assertTrue( true ); + } + + public static function providePCREErrors() { + return array( + array( array(), array(), null ), + ); + } } diff --git a/tests/engines/LuaCommon/UstringLibraryTest.php b/tests/engines/LuaCommon/UstringLibraryTest.php index 4c8ef81e..918c3846 100644 --- a/tests/engines/LuaCommon/UstringLibraryTest.php +++ b/tests/engines/LuaCommon/UstringLibraryTest.php @@ -60,6 +60,43 @@ class Scribunto_LuaUstringLibraryTests extends Scribunto_LuaEngineTestBase { $this->assertSame( $expected, $actual ); $this->luaTestName = null; } + + /** + * @dataProvider providePCREErrors + */ + public function testPCREErrors( $ini, $args, $error ) { + $reset = array(); + foreach ( $ini as $key => $value ) { + $old = ini_set( $key, $value ); + if ( $old === false ) { + $this->markTestSkipped( "Failed to set ini setting $key = $value" ); + } + $reset[] = new ScopedCallback( 'ini_set', array( $key, $old ) ); + } + + $interpreter = $this->getEngine()->getInterpreter(); + $func = $interpreter->loadString( 'return mw.ustring.gsub( ... )', 'fortest' ); + try { + call_user_func_array( + array( $interpreter, 'callFunction' ), + array_merge( array( $func ), $args ) + ); + $this->fail( 'Expected exception not thrown' ); + } catch ( Scribunto_LuaError $e ) { + $this->assertSame( $error, $e->getMessage() ); + } + } + + public static function providePCREErrors() { + return array( + array( + array( 'pcre.backtrack_limit' => 10 ), + array( 'zzzzzzzzzzzzzzzzzzzz', '^(.-)[abc]*$', '%1' ), + 'Lua error: PCRE backtrack limit reached while matching pattern \'^(.-)[abc]*$\'.' + ), + // @TODO: Figure out patterns that hit other PCRE limits + ); + } } class UstringLibraryNormalizationTestProvider extends Scribunto_LuaDataProvider {