If the phpstan-doctrine extension analyzes a file containing a class definition, it may use native autoloading to inspect the file rather than BetterReflction. If the file has side effects, they will be executed resulting in both unexpected behavior and potentially a crash.
Sample file causing crash: https://github.com/openemr/openemr/blob/master/library/classes/OFX.class.php
Repro: run composer phpstan after checking out https://github.com/openemr/openemr/pull/10944/changes. The last commit on this branch sets up this extension.
My understanding is that the default configuration of this extension (e.g. using the extension installer without setting any parameters) there should be no code execution, in line with general phpstan behavior. IIRC that's no longer strictly true when configuring objectManagerLoader, but that's not configured here.
Internal error: Failed opening required '/config.php'
(include_path='/repopath/.phpstan/../interface/main/calendar/:/repopath/.phpstan/../interface/main/calendar/includes/:/repopath/.phpstan/../portal/patient/libs/:/repopath/.phpstan/../portal/patient/fwk/libs/:/repopath/vendor/pear/archive_tar:/repopath/vendor/pear/console_getopt:/repopath/vendor/pear/pear-core-minimal/src:/repopath/vendor/pear/pear_exception:.:/opt/homebrew/Cellar/php/8.5.4/share/php/pear') while analysing file
/repopath/library/classes/OFX.class.php
Post the following stack trace to https://github.com/phpstan/phpstan/issues/new?template=Bug_report.yaml:
## /repopath/library/classes/OFX.class.php(5)
#0 phar:///repopath/vendor/phpstan/phpstan/phpstan.phar/vendor/composer/ClassLoader.php(576): include()
#1 phar:///repopath/vendor/phpstan/phpstan/phpstan.phar/vendor/composer/ClassLoader.php(427): {closure:Composer\Autoload\ClassLoader::initializeIncludeClosure():575}('/Users/firehed/...')
#2 [internal function]: Composer\Autoload\ClassLoader->loadClass('OFX')
#3 /repopath/vendor/phpstan/phpstan-doctrine/src/Type/Doctrine/ObjectMetadataResolver.php(98): class_exists('OFX')
#4 /repopath/vendor/phpstan/phpstan-doctrine/src/Type/Doctrine/ObjectMetadataResolver.php(142): PHPStan\Type\Doctrine\ObjectMetadataResolver->isTransient('OFX')
#5 /repopath/vendor/phpstan/phpstan-doctrine/src/Rules/Doctrine/ORM/EntityColumnRule.php(78): PHPStan\Type\Doctrine\ObjectMetadataResolver->getClassMetadata('OFX')
#6 phar:///repopath/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/FileAnalyserCallback.php(129): PHPStan\Rules\Doctrine\ORM\EntityColumnRule->processNode(Object(PHPStan\Node\ClassPropertyNode), Object(PHPStan\Analyser\Fiber\FiberScope))
#7 phar:///repopath/vendor/phpstan/phpstan/phpstan.phar/src/Node/ClassStatementsGatherer.php(118): PHPStan\Analyser\FileAnalyserCallback->__invoke(Object(PHPStan\Node\ClassPropertyNode), Object(PHPStan\Analyser\Fiber\FiberScope))
#8 phar:///repopath/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/Fiber/FiberNodeScopeResolver.php(37): PHPStan\Node\ClassStatementsGatherer->__invoke(Object(PHPStan\Node\ClassPropertyNode), Object(PHPStan\Analyser\Fiber\FiberScope))
#9 [internal function]: PHPStan\Analyser\Fiber\FiberNodeScopeResolver::{closure:PHPStan\Analyser\Fiber\FiberNodeScopeResolver::callNodeCallback():34}()
#10 phar:///repopath/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/Fiber/FiberNodeScopeResolver.php(32): Fiber->resume(Array)
#11 phar:///repopath/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/NodeScopeResolver.php(652): PHPStan\Analyser\Fiber\FiberNodeScopeResolver->callNodeCallback(Object(PHPStan\Node\ClassStatementsGatherer), Object(PHPStan\Node\ClassPropertyNode), Object(PHPStan\Analyser\MutatingScope), Object(PHPStan\Analyser\ExpressionResultStorage))
#12 phar:///repopath/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/NodeScopeResolver.php(470): PHPStan\Analyser\NodeScopeResolver->processStmtNode(Object(PhpParser\Node\Stmt\ClassMethod), Object(PHPStan\Analyser\MutatingScope), Object(PHPStan\Analyser\ExpressionResultStorage), Object(PHPStan\Node\ClassStatementsGatherer),
Object(PHPStan\Analyser\StatementContext))
#13 phar:///repopath/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/NodeScopeResolver.php(448): PHPStan\Analyser\NodeScopeResolver->processStmtNodesInternalWithoutFlushingPendingFibers(Object(PhpParser\Node\Stmt\Class_), Array, Object(PHPStan\Analyser\MutatingScope), Object(PHPStan\Analyser\ExpressionResultStorage),
Object(PHPStan\Node\ClassStatementsGatherer), Object(PHPStan\Analyser\StatementContext))
#14 phar:///repopath/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/NodeScopeResolver.php(854): PHPStan\Analyser\NodeScopeResolver->processStmtNodesInternal(Object(PhpParser\Node\Stmt\Class_), Array, Object(PHPStan\Analyser\MutatingScope), Object(PHPStan\Analyser\ExpressionResultStorage), Object(PHPStan\Node\ClassStatementsGatherer),
Object(PHPStan\Analyser\StatementContext))
#15 phar:///repopath/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/NodeScopeResolver.php(392): PHPStan\Analyser\NodeScopeResolver->processStmtNode(Object(PhpParser\Node\Stmt\Class_), Object(PHPStan\Analyser\MutatingScope), Object(PHPStan\Analyser\ExpressionResultStorage), Object(PHPStan\Analyser\FileAnalyserCallback),
Object(PHPStan\Analyser\StatementContext))
#16 phar:///repopath/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/FileAnalyser.php(108): PHPStan\Analyser\NodeScopeResolver->processNodes(Array, Object(PHPStan\Analyser\MutatingScope), Object(PHPStan\Analyser\FileAnalyserCallback))
#17 phar:///repopath/vendor/phpstan/phpstan/phpstan.phar/src/Command/WorkerCommand.php(150): PHPStan\Analyser\FileAnalyser->analyseFile('/Users/firehed/...', Array, Object(PHPStan\Rules\LazyRegistry), Object(PHPStan\Collectors\Registry), NULL)
#18 phar:///repopath/vendor/phpstan/phpstan/phpstan.phar/vendor/evenement/evenement/src/EventEmitterTrait.php(111): PHPStan\Command\WorkerCommand::{closure:PHPStan\Command\WorkerCommand::runWorker():126}(Array)
#19 phar:///repopath/vendor/phpstan/phpstan/phpstan.phar/vendor/clue/ndjson-react/src/Decoder.php(117): _PHPStan_c161e9ff7\Evenement\EventEmitter->emit('data', Array)
#20 phar:///repopath/vendor/phpstan/phpstan/phpstan.phar/vendor/evenement/evenement/src/EventEmitterTrait.php(111): _PHPStan_c161e9ff7\Clue\React\NDJson\Decoder->handleData(Array)
#21 phar:///repopath/vendor/phpstan/phpstan/phpstan.phar/vendor/react/stream/src/Util.php(62): _PHPStan_c161e9ff7\Evenement\EventEmitter->emit('data', Array)
#22 phar:///repopath/vendor/phpstan/phpstan/phpstan.phar/vendor/evenement/evenement/src/EventEmitterTrait.php(111): _PHPStan_c161e9ff7\React\Stream\Util::{closure:_PHPStan_c161e9ff7\React\Stream\Util::forwardEvents():61}('{"action":"anal...')
#23 phar:///repopath/vendor/phpstan/phpstan/phpstan.phar/vendor/react/stream/src/DuplexResourceStream.php(168): _PHPStan_c161e9ff7\Evenement\EventEmitter->emit('data', Array)
#24 phar:///repopath/vendor/phpstan/phpstan/phpstan.phar/vendor/react/event-loop/src/StreamSelectLoop.php(201): _PHPStan_c161e9ff7\React\Stream\DuplexResourceStream->handleData(Resource id #15520)
#25 phar:///repopath/vendor/phpstan/phpstan/phpstan.phar/vendor/react/event-loop/src/StreamSelectLoop.php(173): _PHPStan_c161e9ff7\React\EventLoop\StreamSelectLoop->waitForStreamActivity(NULL)
#26 phar:///repopath/vendor/phpstan/phpstan/phpstan.phar/src/Command/WorkerCommand.php(105): _PHPStan_c161e9ff7\React\EventLoop\StreamSelectLoop->run()
#27 phar:///repopath/vendor/phpstan/phpstan/phpstan.phar/vendor/symfony/console/Command/Command.php(259): PHPStan\Command\WorkerCommand->execute(Object(_PHPStan_c161e9ff7\Symfony\Component\Console\Input\ArgvInput), Object(_PHPStan_c161e9ff7\Symfony\Component\Console\Output\ConsoleOutput))
#28 phar:///repopath/vendor/phpstan/phpstan/phpstan.phar/vendor/symfony/console/Application.php(868): _PHPStan_c161e9ff7\Symfony\Component\Console\Command\Command->run(Object(_PHPStan_c161e9ff7\Symfony\Component\Console\Input\ArgvInput), Object(_PHPStan_c161e9ff7\Symfony\Component\Console\Output\ConsoleOutput))
#29 phar:///repopath/vendor/phpstan/phpstan/phpstan.phar/vendor/symfony/console/Application.php(261): _PHPStan_c161e9ff7\Symfony\Component\Console\Application->doRunCommand(Object(PHPStan\Command\WorkerCommand), Object(_PHPStan_c161e9ff7\Symfony\Component\Console\Input\ArgvInput), Object(_PHPStan_c161e9ff7\Symfony\Component\Console\Output\ConsoleOutput))
#30 phar:///repopath/vendor/phpstan/phpstan/phpstan.phar/vendor/symfony/console/Application.php(157): _PHPStan_c161e9ff7\Symfony\Component\Console\Application->doRun(Object(_PHPStan_c161e9ff7\Symfony\Component\Console\Input\ArgvInput), Object(_PHPStan_c161e9ff7\Symfony\Component\Console\Output\ConsoleOutput))
#31 phar:///repopath/vendor/phpstan/phpstan/phpstan.phar/bin/phpstan(106): _PHPStan_c161e9ff7\Symfony\Component\Console\Application->run()
#32 phar:///repopath/vendor/phpstan/phpstan/phpstan.phar/bin/phpstan(107): {closure:phar:///repopath/vendor/phpstan/phpstan/phpstan.phar/bin/phpstan:16}()
#33 /repopath/vendor/phpstan/phpstan/phpstan(8): require('phar:///Users/f...')
#34 /repopath/vendor/bin/phpstan(119): include('/Users/firehed/...')
#35 {main}
Experimentally I changed the class_exists in that stack (ObjectMetadataResolver:98) to pass autoload: false and it didn't crash, but I'm guessing the fix won't be so simple in reality.
If the phpstan-doctrine extension analyzes a file containing a class definition, it may use native autoloading to inspect the file rather than
BetterReflction. If the file has side effects, they will be executed resulting in both unexpected behavior and potentially a crash.Sample file causing crash: https://github.com/openemr/openemr/blob/master/library/classes/OFX.class.php
Repro: run
composer phpstanafter checking out https://github.com/openemr/openemr/pull/10944/changes. The last commit on this branch sets up this extension.My understanding is that the default configuration of this extension (e.g. using the extension installer without setting any parameters) there should be no code execution, in line with general phpstan behavior. IIRC that's no longer strictly true when configuring
objectManagerLoader, but that's not configured here.Experimentally I changed the class_exists in that stack (
ObjectMetadataResolver:98) to passautoload: falseand it didn't crash, but I'm guessing the fix won't be so simple in reality.