Skip to content

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

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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