file_provider = new FakeFileProvider(); } private function getProjectAnalyzerWithConfig(Config $config): ProjectAnalyzer { $project_analyzer = new ProjectAnalyzer( $config, new Providers( $this->file_provider, new FakeParserCacheProvider(), ), ); $project_analyzer->setPhpVersion('7.4', 'tests'); $config->setIncludeCollector(new IncludeCollector()); $config->visitComposerAutoloadFiles($project_analyzer, null); return $project_analyzer; } public function testNonexistentStubFile(): void { $this->expectException(ConfigException::class); $this->expectExceptionMessage('Cannot resolve stubfile path'); $this->project_analyzer = $this->getProjectAnalyzerWithConfig( Config::loadFromXML( dirname(__DIR__), ' ', ), ); } public function testStubFileClass(): void { $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__), ' ', ), ); $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, 'foo(5, "hello"); $c = \SystemClass::bar(5, "hello"); echo \SystemClass::HELLO;', ); $this->analyzeFile($file_path, new Context()); } /** * @psalm-pure */ private function getOperatingSystemStyledPath(string $file): string { return implode(DIRECTORY_SEPARATOR, explode('/', $file)); } public function testLoadStubFileWithRelativePath(): void { $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__), ' ', ), ); $path = $this->getOperatingSystemStyledPath('tests/fixtures/stubs/systemclass.phpstub'); $stub_files = $this->project_analyzer->getConfig()->getStubFiles(); assert(!empty($stub_files)); $this->assertStringContainsString($path, reset($stub_files)); } public function testLoadStubFileWithAbsolutePath(): void { $runDir = dirname(__DIR__); $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( $runDir, ' ', ), ); $path = $this->getOperatingSystemStyledPath('tests/fixtures/stubs/systemclass.phpstub'); $stub_files = $this->project_analyzer->getConfig()->getStubFiles(); assert(!empty($stub_files)); $this->assertStringContainsString($path, reset($stub_files)); } public function testStubFileConstant(): void { $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__), ' ', ), ); $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, 'analyzeFile($file_path, new Context()); } public function testStubFileParentClass(): void { $this->expectException(CodeException::class); $this->expectExceptionMessage('ImplementedParamTypeMismatch'); $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__), ' ', ), ); $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, 'analyzeFile($file_path, new Context()); } public function testStubFileCircularReference(): void { $this->expectException(CodeException::class); $this->expectExceptionMessage('CircularReference'); $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__), ' ', ), ); $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, 'analyzeFile($file_path, new Context()); } public function testPhpStormMetaParsingFile(): void { $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__), ' ', ), ); $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, 'creAte("object"); $a2 = (new \Ns\MyClass)->creaTe("exception"); $y1 = (new \Ns\MyClass)->creAte2("object"); $y2 = (new \Ns\MyClass)->creaTe2("exception"); $const1 = (new \Ns\MyClass)->creAte3(\Ns\MyClass::OBJECT); $const2 = (new \Ns\MyClass)->creaTe3("exception"); $b1 = \Create("object"); $b2 = \cReate("exception"); $e2 = \creAte(\LogicException::class); $z1 = \Create2("object"); $z2 = \cReate2("exception"); $x2 = \creAte2(\LogicException::class); $c1 = (new \Ns\MyClass)->foo(5); $c2 = (new \Ns\MyClass)->bar(["hello"]); $d1 = \foO(5); $d2 = \baR(["hello"]); }', ); $context = new Context(); $this->analyzeFile($file_path, $context); $this->assertContextVars( [ '$a1===' => 'stdClass', '$a2===' => 'Exception', '$y1===' => 'stdClass', '$y2===' => 'Exception', '$const1===' => 'stdClass', '$const2===' => 'Exception', '$b1===' => 'stdClass', '$b2===' => 'Exception', '$e2===' => 'LogicException', '$z1===' => 'stdClass', '$z2===' => 'Exception', '$x2===' => 'LogicException', '$c1===' => "5", '$c2===' => "'hello'", '$d1===' => "5", '$d2===' => "'hello'", ], $context, ); } /** @param array $assertions */ private function assertContextVars(array $assertions, Context $context): void { $actual_vars = []; foreach ($assertions as $var => $_) { $exact = false; if ($var && strpos($var, '===') === strlen($var) - 3) { $var = substr($var, 0, -3); $exact = true; } if (isset($context->vars_in_scope[$var])) { $value = $context->vars_in_scope[$var]->getId($exact); if ($exact) { $actual_vars[$var . '==='] = $value; } else { $actual_vars[$var] = $value; } } } $this->assertSame($assertions, $actual_vars); } public function testNamespacedStubClass(): void { $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__), ' ', ), ); $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, 'foo(5, "hello"); $c = Foo\SystemClass::bar(5, "hello"); echo Foo\BAR;', ); $this->analyzeFile($file_path, new Context()); } public function testStubRegularFunction(): void { $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__), ' ', ), ); $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, 'analyzeFile($file_path, new Context()); } public function testStubVariadicFunction(): void { $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__), ' ', ), ); $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, 'analyzeFile($file_path, new Context()); } public function testStubVariadicFunctionWrongArgType(): void { $this->expectExceptionMessage('InvalidScalarArgument'); $this->expectException(CodeException::class); $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__), ' ', ), ); $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, 'analyzeFile($file_path, new Context()); } public function testUserVariadicWithFalseVariadic(): void { $this->expectExceptionMessage('TooManyArguments'); $this->expectException(CodeException::class); $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__), ' ', ), ); $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, 'analyzeFile($file_path, new Context()); } /** * @runInSeparateProcess */ public function testPolyfilledFunction(): void { $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__), ' ', ), ); $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, 'analyzeFile($file_path, new Context()); } /** * @runInSeparateProcess */ public function testConditionalConstantDefined(): void { $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__), ' ', ), ); $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, 'analyzeFile($file_path, new Context()); } /** * @runInSeparateProcess */ public function testStubbedConstantVarCommentType(): void { $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__), ' ', ), ); $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, 'analyzeFile($file_path, new Context()); } /** * @runInSeparateProcess */ public function testClassAlias(): void { $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__), ' ', ), ); $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, 'foo; echo $a->bar("hello"); function f(): A { return new A; } function getAliased(): B { return f(); } $d = new D(); D::bat(); $d::bat(); class E implements IAlias {}', ); $this->analyzeFile($file_path, new Context()); } public function testStubFunctionWithFunctionExists(): void { $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__), ' ', ), ); $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, 'analyzeFile($file_path, new Context()); } public function testNamespacedStubFunctionWithFunctionExists(): void { $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__), ' ', ), ); $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, 'analyzeFile($file_path, new Context()); } public function testNoStubFunction(): void { $this->expectExceptionMessage('UndefinedFunction - /src/somefile.php:2:22 - Function barBar does not exist'); $this->expectException(CodeException::class); $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__), ' ', ), ); $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, 'analyzeFile($file_path, new Context()); } public function testNamespacedStubFunction(): void { $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__), ' ', ), ); $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, 'analyzeFile($file_path, new Context()); } public function testConditionalNamespacedStubFunction(): void { $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__), ' ', ), ); $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, 'analyzeFile($file_path, new Context()); } public function testConditionallyExtendingInterface(): void { $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__), ' ', ), ); $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, 'getMessage(); } function bar(I6 $d) : void { $d->getMessage(); } function bat(I7 $d) : void { $d->getMessage(); } function baz(I8 $d) : void { $d->getMessage(); }', ); $this->analyzeFile($file_path, new Context()); } public function testStubFileWithExistingClassDefinition(): void { $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__), ' ', ), ); $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, 'analyzeFile($file_path, new Context()); } /** @return iterable */ public function versionDependentStubsProvider(): iterable { yield '7.0' => [ '7.0', 'something("zzz");', ]; yield '8.0' => [ '8.0', 'something();', ]; } /** @dataProvider versionDependentStubsProvider */ public function testVersionDependentStubs(string $php_version, string $code): void { $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__), ' ', ), ); $this->project_analyzer->setPhpVersion($php_version, 'tests'); $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile($file_path, $code); $this->analyzeFile($file_path, new Context()); } public function testStubFileWithPartialClassDefinitionWithMoreMethods(): void { $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__), ' ', ), ); $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, 'foo(A::class); (new PartiallyStubbedClass())->bar(5);', ); $this->analyzeFile($file_path, new Context()); } public function testExtendOnlyStubbedClass(): void { $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__), ' ', ), ); $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, 'foo(A::class);', ); $this->analyzeFile($file_path, new Context()); } public function testStubFileWithExtendedStubbedClass(): void { $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__), ' ', ), ); $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, 'analyzeFile($file_path, new Context()); } public function testStubFileWithTemplatedClassDefinitionAndMagicMethodOverride(): void { $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__), ' ', ), ); $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, 'analyzeFile($file_path, new Context()); } public function testInheritedMethodUsedInStub(): void { $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__), ' ', ), ); $this->project_analyzer->getCodebase()->reportUnusedCode(); $vendor_file_path = (string) getcwd() . '/vendor/vendor_class.php'; $this->addFile( $vendor_file_path, 'foo(); } }', ); $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, 'analyzeFile($file_path, new Context(), false); $this->project_analyzer->consolidateAnalyzedData(); } public function testStubOverridingMissingClass(): void { $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__), ' ', ), ); $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, 'expectException(InvalidClasslikeOverrideException::class); $this->analyzeFile($file_path, new Context()); } public function testStubOverridingMissingMethod(): void { $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__), ' ', ), ); $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, 'expectException(InvalidMethodOverrideException::class); $this->analyzeFile($file_path, new Context()); } public function testStubReplacingInterfaceDocblock(): void { $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__), ' ', ), ); $this->addFile( (string) getcwd() . '/vendor/doctrine/import.php', 'addFile( $file_path, 'getReference(A::class, 1); }', ); $this->expectException(CodeException::class); $this->expectExceptionMessage('A|null'); $this->analyzeFile($file_path, new Context()); } /** * This covers the following case encountered by mmcev106: * - A function was defined without a docblock * - The autoloader defined a global containing the path to that file * - The code being scanned required the path specified by the autoloader defined global * - A docblock was added via a stub that marked the function as a taint source * - The stub docblock was incorrectly ignored, causing the the taint source to be ignored */ public function testAutoloadDefinedRequirePath(): void { $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__), ' ', ), ); $this->project_analyzer->trackTaintedInputs(); $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, 'expectExceptionMessage('TaintedHtml - /src/somefile.php'); $this->analyzeFile($file_path, new Context()); } }