Safer autoloading with respect to file-scope code

Many files were in the autoloader despite having potentially harmful
file-scope code.

* Exclude all CommandLineInc maintenance scripts from the autoloader.
* Introduce  "NO_AUTOLOAD" tag which excludes the file containing it
  from the autoloader. Use it on CommandLineInc.php and a few
  suspicious-looking files without classes in case they are refactored
  to add classes in the future.
* Add a test which parses all non-PSR4 class files and confirms that
  they do not contain dangerous file-scope code. It's slow (15s) but
  its results were enlightening.
* Several maintenance scripts define constants in the file scope,
  intending to modify the behaviour of MediaWiki. Either move the
  define() to a later setup function, or protect with NO_AUTOLOAD.
* Use require_once consistently with Maintenance.php and
  doMaintenance.php, per the original convention which is supposed to
  allow one maintenance script to use the class of another maintenance
  script. Using require breaks autoloading of these maintenance class
  files.
* When Maintenance.php is included, check if MediaWiki has already
  started, and if so, return early. Revert the fix for T250003 which
  is incompatible with this safety measure. Hopefully it was superseded
  by splitting out the class file.
* In runScript.php add a redundant PHP_SAPI check since it does some
  things in file-scope code before any other check will be run.
* Change the if(false) class_alias(...) to something more hackish and
  more compatible with the new test.
* Some site-related scripts found Maintenance.php in a non-standard way.
  Use the standard way.
* fileOpPerfTest.php called error_reporting(). Probably debugging code
  left in; removed.
* Moved mediawiki.compress.7z registration from the class file to the
  caller.

Change-Id: I1b1be90343a5ab678df6f1b1bdd03319dcf6537f
This commit is contained in:
Tim Starling 2021-01-08 13:16:02 +11:00
parent 320af70270
commit 20d06b34bb
32 changed files with 185 additions and 96 deletions

View file

@ -169,7 +169,6 @@ $wgAutoloadLocalClasses = [
'AugmentPageProps' => __DIR__ . '/includes/search/AugmentPageProps.php',
'AuthManagerSpecialPage' => __DIR__ . '/includes/specialpage/AuthManagerSpecialPage.php',
'AutoCommitUpdate' => __DIR__ . '/includes/deferred/AutoCommitUpdate.php',
'AutoLoader' => __DIR__ . '/includes/AutoLoader.php',
'AutoloadGenerator' => __DIR__ . '/includes/utils/AutoloadGenerator.php',
'Autopromote' => __DIR__ . '/includes/Autopromote.php',
'AvroValidator' => __DIR__ . '/includes/utils/AvroValidator.php',
@ -238,7 +237,6 @@ $wgAutoloadLocalClasses = [
'CdnCacheUpdate' => __DIR__ . '/includes/deferred/CdnCacheUpdate.php',
'CdnPurgeJob' => __DIR__ . '/includes/jobqueue/jobs/CdnPurgeJob.php',
'CentralIdLookup' => __DIR__ . '/includes/user/CentralIdLookup.php',
'CgzCopyTransaction' => __DIR__ . '/maintenance/storage/recompressTracked.php',
'ChangePassword' => __DIR__ . '/maintenance/changePassword.php',
'ChangeTags' => __DIR__ . '/includes/changetags/ChangeTags.php',
'ChangeTagsList' => __DIR__ . '/includes/changetags/ChangeTagsList.php',
@ -261,7 +259,6 @@ $wgAutoloadLocalClasses = [
'CheckDependencies' => __DIR__ . '/maintenance/checkDependencies.php',
'CheckImages' => __DIR__ . '/maintenance/checkImages.php',
'CheckLess' => __DIR__ . '/maintenance/checkLess.php',
'CheckStorage' => __DIR__ . '/maintenance/storage/checkStorage.php',
'CheckUsernames' => __DIR__ . '/maintenance/checkUsernames.php',
'ClassCollector' => __DIR__ . '/includes/utils/ClassCollector.php',
'CleanupAncientTables' => __DIR__ . '/maintenance/cleanupAncientTables.php',
@ -287,8 +284,6 @@ $wgAutoloadLocalClasses = [
'CollapsibleFieldsetLayout' => __DIR__ . '/includes/htmlform/CollapsibleFieldsetLayout.php',
'Collation' => __DIR__ . '/includes/collation/Collation.php',
'CollationCkb' => __DIR__ . '/includes/collation/CollationCkb.php',
'CommandLineInc' => __DIR__ . '/maintenance/CommandLineInc.php',
'CommandLineInstaller' => __DIR__ . '/maintenance/install.php',
'CommentStore' => __DIR__ . '/includes/CommentStore.php',
'CommentStoreComment' => __DIR__ . '/includes/CommentStoreComment.php',
'CompareParserCache' => __DIR__ . '/maintenance/compareParserCache.php',
@ -1139,7 +1134,6 @@ $wgAutoloadLocalClasses = [
'MergeHistory' => __DIR__ . '/includes/MergeHistory.php',
'MergeHistoryPager' => __DIR__ . '/includes/specials/pagers/MergeHistoryPager.php',
'MergeLogFormatter' => __DIR__ . '/includes/logging/MergeLogFormatter.php',
'MergeMessageFileList' => __DIR__ . '/maintenance/mergeMessageFileList.php',
'MergeableUpdate' => __DIR__ . '/includes/deferred/MergeableUpdate.php',
'Message' => __DIR__ . '/includes/language/Message.php',
'MessageBlobStore' => __DIR__ . '/includes/resourceloader/MessageBlobStore.php',
@ -1215,9 +1209,6 @@ $wgAutoloadLocalClasses = [
'PPDStack_Hash' => __DIR__ . '/includes/parser/PPDStack_Hash.php',
'PPFrame' => __DIR__ . '/includes/parser/PPFrame.php',
'PPFrame_Hash' => __DIR__ . '/includes/parser/PPFrame_Hash.php',
'PPFuzzTest' => __DIR__ . '/maintenance/preprocessorFuzzTest.php',
'PPFuzzTester' => __DIR__ . '/maintenance/preprocessorFuzzTest.php',
'PPFuzzUser' => __DIR__ . '/maintenance/preprocessorFuzzTest.php',
'PPNode' => __DIR__ . '/includes/parser/PPNode.php',
'PPNode_Hash_Array' => __DIR__ . '/includes/parser/PPNode_Hash_Array.php',
'PPNode_Hash_Attr' => __DIR__ . '/includes/parser/PPNode_Hash_Attr.php',
@ -1349,7 +1340,6 @@ $wgAutoloadLocalClasses = [
'RebuildTextIndex' => __DIR__ . '/maintenance/rebuildtextindex.php',
'RecentChange' => __DIR__ . '/includes/changes/RecentChange.php',
'RecentChangesUpdateJob' => __DIR__ . '/includes/jobqueue/jobs/RecentChangesUpdateJob.php',
'RecompressTracked' => __DIR__ . '/maintenance/storage/recompressTracked.php',
'RecountCategories' => __DIR__ . '/maintenance/recountCategories.php',
'RedirectSpecialArticle' => __DIR__ . '/includes/specialpage/RedirectSpecialArticle.php',
'RedirectSpecialPage' => __DIR__ . '/includes/specialpage/RedirectSpecialPage.php',
@ -1685,7 +1675,6 @@ $wgAutoloadLocalClasses = [
'TitleParser' => __DIR__ . '/includes/title/TitleParser.php',
'TitlePrefixSearch' => __DIR__ . '/includes/search/TitlePrefixSearch.php',
'TitleValue' => __DIR__ . '/includes/title/TitleValue.php',
'TrackBlobs' => __DIR__ . '/maintenance/storage/trackBlobs.php',
'TrackingCategories' => __DIR__ . '/includes/TrackingCategories.php',
'TraditionalImageGallery' => __DIR__ . '/includes/gallery/TraditionalImageGallery.php',
'TransactionRoundAwareUpdate' => __DIR__ . '/includes/deferred/TransactionRoundAwareUpdate.php',
@ -1711,8 +1700,6 @@ $wgAutoloadLocalClasses = [
'UpdateCollation' => __DIR__ . '/maintenance/updateCollation.php',
'UpdateDoubleWidthSearch' => __DIR__ . '/maintenance/updateDoubleWidthSearch.php',
'UpdateExtensionJsonSchema' => __DIR__ . '/maintenance/updateExtensionJsonSchema.php',
'UpdateLogging' => __DIR__ . '/maintenance/archives/upgradeLogging.php',
'UpdateMediaWiki' => __DIR__ . '/maintenance/update.php',
'UpdateRestrictions' => __DIR__ . '/maintenance/updateRestrictions.php',
'UpdateSearchIndex' => __DIR__ . '/maintenance/updateSearchIndex.php',
'UpdateSpecialPages' => __DIR__ . '/maintenance/updateSpecialPages.php',

View file

@ -20,6 +20,8 @@
* @file
*/
// NO_AUTOLOAD -- file scope code, can't load self
/**
* Locations of core classes
* Extension classes are specified with $wgAutoloadClasses

View file

@ -3,20 +3,24 @@
use Composer\Package\Package;
use Composer\Script\Event;
$GLOBALS['IP'] = __DIR__ . '/../../';
require_once __DIR__ . '/../AutoLoader.php';
/**
* @license GPL-2.0-or-later
* @author Jeroen De Dauw < jeroendedauw@gmail.com >
*/
class ComposerHookHandler {
private static function startAutoloader() {
$GLOBALS['IP'] = __DIR__ . '/../../';
require_once __DIR__ . '/../AutoLoader.php';
}
public static function onPreUpdate( Event $event ) {
self::startAutoloader();
self::handleChangeEvent( $event );
}
public static function onPreInstall( Event $event ) {
self::startAutoloader();
self::handleChangeEvent( $event );
}

View file

@ -142,13 +142,11 @@ class ConcatenatedGzipHistoryBlob implements HistoryBlob {
}
}
// @phan-suppress-next-next-line PhanImpossibleCondition
// phpcs:ignore Generic.CodeAnalysis.UnconditionalIfStatement.Found
if ( false ) {
// Blobs generated by MediaWiki < 1.5 on PHP 4 were serialized with the
// class name coerced to lowercase. We can improve efficiency by adding
// autoload entries for the lowercase variants of these classes (T166759).
// The code below is never executed, but it is picked up by the AutoloadGenerator
// parser, which scans for class_alias() calls.
class_alias( ConcatenatedGzipHistoryBlob::class, 'concatenatedgziphistoryblob' );
}
// Blobs generated by MediaWiki < 1.5 on PHP 4 were serialized with the
// class name coerced to lowercase. We can improve efficiency by adding
// autoload entries for the lowercase variants of these classes (T166759).
// The code below is never executed, but it is picked up by the AutoloadGenerator
// parser, which scans for class_alias() calls.
/*
class_alias( ConcatenatedGzipHistoryBlob::class, 'concatenatedgziphistoryblob' );
*/

View file

@ -62,13 +62,11 @@ class HistoryBlobCurStub {
}
}
// @phan-suppress-next-next-line PhanImpossibleCondition
// phpcs:ignore Generic.CodeAnalysis.UnconditionalIfStatement.Found
if ( false ) {
// Blobs generated by MediaWiki < 1.5 on PHP 4 were serialized with the
// class name coerced to lowercase. We can improve efficiency by adding
// autoload entries for the lowercase variants of these classes (T166759).
// The code below is never executed, but it is picked up by the AutoloadGenerator
// parser, which scans for class_alias() calls.
class_alias( HistoryBlobCurStub::class, 'historyblobcurstub' );
}
// Blobs generated by MediaWiki < 1.5 on PHP 4 were serialized with the
// class name coerced to lowercase. We can improve efficiency by adding
// autoload entries for the lowercase variants of these classes (T166759).
// The code below is never executed, but it is picked up by the AutoloadGenerator
// parser, which scans for class_alias() calls.
/*
class_alias( HistoryBlobCurStub::class, 'historyblobcurstub' );
*/

View file

@ -143,13 +143,11 @@ class HistoryBlobStub {
}
}
// @phan-suppress-next-next-line PhanImpossibleCondition
// phpcs:ignore Generic.CodeAnalysis.UnconditionalIfStatement.Found
if ( false ) {
// Blobs generated by MediaWiki < 1.5 on PHP 4 were serialized with the
// class name coerced to lowercase. We can improve efficiency by adding
// autoload entries for the lowercase variants of these classes (T166759).
// The code below is never executed, but it is picked up by the AutoloadGenerator
// parser, which scans for class_alias() calls.
class_alias( HistoryBlobStub::class, 'historyblobstub' );
}
// Blobs generated by MediaWiki < 1.5 on PHP 4 were serialized with the
// class name coerced to lowercase. We can improve efficiency by adding
// autoload entries for the lowercase variants of these classes (T166759).
// The code below is never executed, but it is picked up by the AutoloadGenerator
// parser, which scans for class_alias() calls.
/*
class_alias( HistoryBlobStub::class, 'historyblobstub' );
*/

View file

@ -171,9 +171,22 @@ class AutoloadGenerator {
if ( $this->shouldExclude( $inputPath ) ) {
return;
}
$result = $this->collector->getClasses(
file_get_contents( $inputPath )
);
$fileContents = file_get_contents( $inputPath );
// Skip files that declare themselves excluded
if ( preg_match( '!^// *NO_AUTOLOAD!m', $fileContents ) ) {
return;
}
// Skip files that use CommandLineInc since these execute file-scope
// code when included
if ( preg_match(
'/(require|require_once)[ (].*(CommandLineInc.php|commandLine.inc)/',
$fileContents )
) {
return;
}
$result = $this->collector->getClasses( $fileContents );
// Filter out classes that will be found by PSR4
$result = array_filter( $result, function ( $class ) use ( $inputPath ) {

View file

@ -21,6 +21,8 @@
* @ingroup Maintenance
*/
// NO_AUTOLOAD -- unsafe file-scope code
require_once __DIR__ . '/Maintenance.php';
global $optionsWithArgs, $optionsWithoutArgs, $allowUnregisteredOptions;
@ -71,4 +73,4 @@ class CommandLineInc extends Maintenance {
}
$maintClass = CommandLineInc::class;
require RUN_MAINTENANCE_IF_MAIN;
require_once RUN_MAINTENANCE_IF_MAIN;

View file

@ -20,10 +20,16 @@
* @defgroup Maintenance Maintenance
*/
if ( !defined( 'MW_ENTRY_POINT' ) ) {
define( 'MW_ENTRY_POINT', 'cli' );
if ( defined( 'MEDIAWIKI' ) ) {
// This file is included by many autoloaded class files, and so may
// potentially be invoked in the context of a web request or another CLI
// script. It's not appropriate to run the following file-scope code in
// such a case.
return;
}
define( 'MW_ENTRY_POINT', 'cli' );
// Bail on old versions of PHP, or if composer has not been run yet to install
// dependencies.
require_once __DIR__ . '/../includes/PHPVersionCheck.php';

View file

@ -2,9 +2,7 @@
use MediaWiki\MediaWikiServices;
$basePath = getenv( 'MW_INSTALL_PATH' ) !== false ? getenv( 'MW_INSTALL_PATH' ) : __DIR__ . '/..';
require_once $basePath . '/maintenance/Maintenance.php';
require_once __DIR__ . '/Maintenance.php';
/**
* Maintenance script for adding a site definition into the sites table.

View file

@ -22,7 +22,7 @@
* @ingroup Benchmark
*/
require __DIR__ . '/../Maintenance.php';
require_once __DIR__ . '/../Maintenance.php';
use MediaWiki\MediaWikiServices;
use MediaWiki\Revision\RevisionRecord;
@ -194,4 +194,4 @@ class BenchmarkParse extends Maintenance {
}
$maintClass = BenchmarkParse::class;
require RUN_MAINTENANCE_IF_MAIN;
require_once RUN_MAINTENANCE_IF_MAIN;

View file

@ -21,7 +21,7 @@
use MediaWiki\MediaWikiServices;
require __DIR__ . '/../includes/Benchmarker.php';
require_once __DIR__ . '/../includes/Benchmarker.php';
class BenchmarkTidy extends Benchmarker {
public function __construct() {
@ -79,4 +79,4 @@ class BenchmarkTidy extends Benchmarker {
}
$maintClass = BenchmarkTidy::class;
require RUN_MAINTENANCE_IF_MAIN;
require_once RUN_MAINTENANCE_IF_MAIN;

View file

@ -1,8 +1,6 @@
<?php
$basePath = getenv( 'MW_INSTALL_PATH' ) !== false ? getenv( 'MW_INSTALL_PATH' ) : __DIR__ . '/..';
require_once $basePath . '/maintenance/Maintenance.php';
require_once __DIR__ . '/Maintenance.php';
/**
* Maintenance script for exporting site definitions from XML into the sites table.

View file

@ -23,7 +23,6 @@
use MediaWiki\MediaWikiServices;
error_reporting( E_ALL );
require_once __DIR__ . '/Maintenance.php';
/**

View file

@ -1,8 +1,6 @@
<?php
$basePath = getenv( 'MW_INSTALL_PATH' ) !== false ? getenv( 'MW_INSTALL_PATH' ) : __DIR__ . '/..';
require_once $basePath . '/maintenance/Maintenance.php';
require_once __DIR__ . '/Maintenance.php';
/**
* Maintenance script for importing site definitions from XML into the sites table.

View file

@ -36,6 +36,14 @@ use MediaWiki\Shell\Shell;
class SevenZipStream {
protected $stream;
public static function register() {
static $done = false;
if ( !$done ) {
$done = true;
stream_wrapper_register( 'mediawiki.compress.7z', self::class );
}
}
private function stripPath( $path ) {
$prefix = 'mediawiki.compress.7z://';
@ -93,5 +101,3 @@ class SevenZipStream {
return fseek( $this->stream, $offset, $whence );
}
}
stream_wrapper_register( 'mediawiki.compress.7z', SevenZipStream::class );

View file

@ -26,7 +26,6 @@
*/
require_once __DIR__ . '/BackupDumper.php';
require_once __DIR__ . '/SevenZipStream.php';
require_once __DIR__ . '/../../includes/export/WikiExporter.php';
use MediaWiki\MediaWikiServices;
@ -149,6 +148,8 @@ TEXT
$this->loadWithArgv( $args );
$this->processOptions();
}
SevenZipStream::register();
}
/**

View file

@ -21,6 +21,8 @@
* @ingroup Maintenance
*/
// NO_AUTOLOAD -- file-scope define() used to modify behaviour
require_once __DIR__ . '/Maintenance.php';
define( 'MW_CONFIG_CALLBACK', 'Installer::overrideConfig' );

View file

@ -22,6 +22,8 @@
* @ingroup Maintenance
*/
// NO_AUTOLOAD -- file-scope define() used to modify behaviour
# Start from scratch
define( 'MW_NO_EXTENSION_MESSAGES', 1 );

View file

@ -22,7 +22,7 @@
* @ingroup Maintenance
*/
require __DIR__ . '/Maintenance.php';
require_once __DIR__ . '/Maintenance.php';
use MediaWiki\MediaWikiServices;

View file

@ -21,12 +21,6 @@
* @ingroup Maintenance
*/
if ( !defined( 'MEDIAWIKI' ) ) {
// So extensions (and other code) can check whether they're running in job mode.
// This is not defined if this script is included from installer/updater or phpunit.
define( 'MEDIAWIKI_JOB_RUNNER', true );
}
require_once __DIR__ . '/Maintenance.php';
use MediaWiki\MediaWikiServices;
@ -49,6 +43,13 @@ class RunJobs extends Maintenance {
$this->addOption( 'wait', 'Wait for new jobs instead of exiting', false, false );
}
public function finalSetup() {
// So extensions (and other code) can check whether they're running in job mode.
// This is not defined if this script is included from installer/updater or phpunit.
define( 'MEDIAWIKI_JOB_RUNNER', true );
parent::finalSetup();
}
public function memoryLimit() {
if ( $this->hasOption( 'memory-limit' ) ) {
return parent::memoryLimit();

View file

@ -35,6 +35,12 @@
* @file
* @ingroup Maintenance
*/
if ( PHP_SAPI !== 'cli' && PHP_SAPI !== 'phpdbg' ) {
echo "This script must be run from the command line\n";
exit( 1 );
}
$IP = getenv( 'MW_INSTALL_PATH' );
if ( $IP === false ) {

View file

@ -21,6 +21,8 @@
* @ingroup Maintenance ExternalStorage
*/
// NO_AUTOLOAD -- file scope code
use MediaWiki\MediaWikiServices;
define( 'REPORTING_INTERVAL', 1 );

View file

@ -25,6 +25,8 @@
* @ingroup Maintenance
*/
// NO_AUTOLOAD -- due to hashbang above
require_once __DIR__ . '/Maintenance.php';
use MediaWiki\MediaWikiServices;

View file

@ -22,6 +22,8 @@
* http://www.gnu.org/copyleft/gpl.html
*/
// NO_AUTOLOAD -- file-scope code
if ( PHP_SAPI != 'cli' ) {
die( "This script can only be run from the command line.\n" );
}

View file

@ -197,4 +197,4 @@ WARN
}
$maintClass = UserOptionsMaintenance::class;
require RUN_MAINTENANCE_IF_MAIN;
require_once RUN_MAINTENANCE_IF_MAIN;

View file

@ -1,6 +1,6 @@
<?php
require __DIR__ . '/../../maintenance/Maintenance.php';
require_once __DIR__ . '/../../maintenance/Maintenance.php';
define( 'MW_PARSER_TEST', true );
@ -485,4 +485,4 @@ class ParserEditTests extends Maintenance {
}
$maintClass = ParserEditTests::class;
require RUN_MAINTENANCE_IF_MAIN;
require_once RUN_MAINTENANCE_IF_MAIN;

View file

@ -2,10 +2,7 @@
use Wikimedia\ScopedCallback;
require __DIR__ . '/../../maintenance/Maintenance.php';
// Make RequestContext::resetMain() happy
define( 'MW_PARSER_TEST', 1 );
require_once __DIR__ . '/../../maintenance/Maintenance.php';
class ParserFuzzTest extends Maintenance {
private $parserTest;
@ -24,6 +21,9 @@ class ParserFuzzTest extends Maintenance {
}
public function finalSetup() {
// Make RequestContext::resetMain() happy
define( 'MW_PARSER_TEST', 1 );
self::requireTestsAutoloader();
TestSetup::applyInitialConfig();
}
@ -196,4 +196,4 @@ class ParserFuzzTest extends Maintenance {
}
$maintClass = ParserFuzzTest::class;
require RUN_MAINTENANCE_IF_MAIN;
require_once RUN_MAINTENANCE_IF_MAIN;

View file

@ -24,11 +24,7 @@
* @ingroup Testing
*/
// Some methods which are discouraged for normal code throw exceptions unless
// we declare this is just a test.
define( 'MW_PARSER_TEST', true );
require __DIR__ . '/../../maintenance/Maintenance.php';
require_once __DIR__ . '/../../maintenance/Maintenance.php';
use MediaWiki\MediaWikiServices;
@ -77,6 +73,10 @@ class ParserTestsMaintenance extends Maintenance {
}
public function finalSetup() {
// Some methods which are discouraged for normal code throw exceptions unless
// we declare this is just a test.
define( 'MW_PARSER_TEST', true );
parent::finalSetup();
self::requireTestsAutoloader();
TestSetup::applyInitialConfig();

View file

@ -7,7 +7,7 @@
// Start up MediaWiki in command-line mode
require_once __DIR__ . "/../../../../maintenance/Maintenance.php";
require __DIR__ . "/RandomImageGenerator.php";
require_once __DIR__ . "/RandomImageGenerator.php";
class GenerateRandomImages extends Maintenance {
@ -41,4 +41,4 @@ class GenerateRandomImages extends Maintenance {
}
$maintClass = GenerateRandomImages::class;
require RUN_MAINTENANCE_IF_MAIN;
require_once RUN_MAINTENANCE_IF_MAIN;

View file

@ -6,10 +6,6 @@
* @file
*/
// Set a flag which can be used to detect when other scripts have been entered
// through this entry point or not.
define( 'MW_PHPUNIT_TEST', true );
// Start up MediaWiki in command-line mode
require_once dirname( dirname( __DIR__ ) ) . "/maintenance/Maintenance.php";
@ -29,6 +25,10 @@ class PHPUnitMaintClass extends Maintenance {
}
public function setup() {
// Set a flag which can be used to detect when other scripts have been entered
// through this entry point or not.
define( 'MW_PHPUNIT_TEST', true );
parent::setup();
require_once __DIR__ . '/../common/TestSetup.php';
@ -131,4 +131,4 @@ class PHPUnitMaintClass extends Maintenance {
}
$maintClass = PHPUnitMaintClass::class;
require RUN_MAINTENANCE_IF_MAIN;
require_once RUN_MAINTENANCE_IF_MAIN;

View file

@ -1,5 +1,10 @@
<?php
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Stmt;
use PhpParser\ParserFactory;
class AutoLoaderStructureTest extends MediaWikiIntegrationTestCase {
/**
* Assert that there were no classes loaded that are not registered with the AutoLoader.
@ -175,4 +180,63 @@ class AutoLoaderStructureTest extends MediaWikiIntegrationTestCase {
$this->assertSame( [], $missing );
}
public static function provideAutoloadNoFileScope() {
global $wgAutoloadLocalClasses;
$files = array_unique( $wgAutoloadLocalClasses );
$args = [];
foreach ( $files as $file ) {
$args[$file] = [ $file ];
}
return $args;
}
/**
* Confirm that all files in $wgAutoloadLocalClasses have no file-scope code
* apart from specific exemptions.
*
* This is slow (~15s). Running it arguably renders all the performance
* optimisations above obsolete.
*
* @dataProvider provideAutoloadNoFileScope
*/
public function testAutoloadNoFileScope( $file ) {
$parser = ( new ParserFactory )->create( ParserFactory::ONLY_PHP7 );
$ast = $parser->parse( file_get_contents( $file ) );
foreach ( $ast as $node ) {
if ( $node instanceof Stmt\ClassLike
|| $node instanceof Stmt\Namespace_
|| $node instanceof Stmt\Use_
|| $node instanceof Stmt\Nop
|| $node instanceof Stmt\Declare_
|| $node instanceof Stmt\Function_
) {
continue;
}
if ( $node instanceof Stmt\Expression ) {
$expr = $node->expr;
if ( $expr instanceof Expr\FuncCall ) {
if ( $expr->name instanceof Node\Name ) {
if ( in_array( $expr->name->toString(), [
'class_alias',
'define'
] ) ) {
continue;
}
}
} elseif ( $expr instanceof Expr\Include_ ) {
if ( $expr->type === Expr\Include_::TYPE_REQUIRE_ONCE ) {
continue;
}
} elseif ( $expr instanceof Expr\Assign ) {
if ( $expr->var->name === 'maintClass' ) {
continue;
}
}
}
$line = $node->getLine();
$this->assertNull( $node, "Found file scope code in $file at line $line" );
}
$this->assertTrue( true );
}
}