Skip to content

Analysis can crash when encountering files with classes and side effects #748

@Firehed

Description

@Firehed

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions