From 50d7546057c1089228a2a6d7d4a49d78d72887e3 Mon Sep 17 00:00:00 2001 From: Florian Schmidt Date: Mon, 17 Apr 2017 22:36:30 +0200 Subject: [PATCH] registration: Only allow one extension to set a specific config setting ExtensionProcessor would previously just blindly overwrite duplicate config settings, which ends up depending upon load order. It's relatively hard to debug since it is silently overwritten. This now throws exceptions in case of duplicate config settings. This will also have some side-effects of catching people putting things like "ResourceModules" in their "config" section when it should be a top-level item. Bug: T152929 Change-Id: Iaef32efab397e82ff70ddca8ac79c545c5b7d2bb --- includes/registration/ExtensionProcessor.php | 19 ++++++++- .../registration/ExtensionProcessorTest.php | 42 +++++++++++++++++++ 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/includes/registration/ExtensionProcessor.php b/includes/registration/ExtensionProcessor.php index ce262bd23e..14d822295c 100644 --- a/includes/registration/ExtensionProcessor.php +++ b/includes/registration/ExtensionProcessor.php @@ -450,7 +450,7 @@ class ExtensionProcessor implements Processor { } foreach ( $info['config'] as $key => $val ) { if ( $key[0] !== '@' ) { - $this->globals["$prefix$key"] = $val; + $this->addConfigGlobal( "$prefix$key", $val ); } } } @@ -478,11 +478,26 @@ class ExtensionProcessor implements Processor { if ( isset( $data['path'] ) && $data['path'] ) { $value = "$dir/$value"; } - $this->globals["$prefix$key"] = $value; + $this->addConfigGlobal( "$prefix$key", $value ); } } } + /** + * Helper function to set a value to a specific global, if it isn't set already. + * + * @param string $key The config key with the prefix and anything + * @param mixed $value The value of the config + */ + private function addConfigGlobal( $key, $value ) { + if ( array_key_exists( $key, $this->globals ) ) { + throw new RuntimeException( + "The configuration setting '$key' was already set by another extension," + . " and cannot be set again." ); + } + $this->globals[$key] = $value; + } + protected function extractServiceWiringFiles( $dir, array $info ) { if ( isset( $info['ServiceWiringFiles'] ) ) { foreach ( $info['ServiceWiringFiles'] as $path ) { diff --git a/tests/phpunit/includes/registration/ExtensionProcessorTest.php b/tests/phpunit/includes/registration/ExtensionProcessorTest.php index 7b56def1c3..5ef30e872a 100644 --- a/tests/phpunit/includes/registration/ExtensionProcessorTest.php +++ b/tests/phpunit/includes/registration/ExtensionProcessorTest.php @@ -220,6 +220,48 @@ class ExtensionProcessorTest extends MediaWikiTestCase { $this->assertEquals( 'somevalue', $extracted['globals']['egBar'] ); } + /** + * @covers ExtensionProcessor::addConfigGlobal() + * @expectedException RuntimeException + */ + public function testDuplicateConfigKey1() { + $processor = new ExtensionProcessor; + $info = [ + 'config' => [ + 'Bar' => '', + ] + ] + self::$default; + $info2 = [ + 'config' => [ + 'Bar' => 'g', + ], + 'name' => 'FooBar2', + ]; + $processor->extractInfo( $this->dir, $info, 1 ); + $processor->extractInfo( $this->dir, $info2, 1 ); + } + + /** + * @covers ExtensionProcessor::addConfigGlobal() + * @expectedException RuntimeException + */ + public function testDuplicateConfigKey2() { + $processor = new ExtensionProcessor; + $info = [ + 'config' => [ + 'Bar' => [ 'value' => 'somevalue' ], + ] + ] + self::$default; + $info2 = [ + 'config' => [ + 'Bar' => [ 'value' => 'somevalue' ], + ], + 'name' => 'FooBar2', + ]; + $processor->extractInfo( $this->dir, $info, 2 ); + $processor->extractInfo( $this->dir, $info2, 2 ); + } + public static function provideExtractExtensionMessagesFiles() { $dir = __DIR__ . '/FooBar/'; return [ -- 2.20.1