diff --git a/dictionaries/CallMap.php b/dictionaries/CallMap.php index 9f31a7a44..92219eda2 100644 --- a/dictionaries/CallMap.php +++ b/dictionaries/CallMap.php @@ -2109,9 +2109,9 @@ return [ 'DOMDocument::getElementsByTagNameNS' => ['DOMNodeList', 'namespaceuri'=>'string', 'localname'=>'string'], 'DOMDocument::importNode' => ['DOMNode|false', 'importednode'=>'DOMNode', 'deep='=>'bool'], 'DOMDocument::load' => ['DOMDocument|bool', 'filename'=>'string', 'options='=>'int'], -'DOMDocument::loadHTML' => ['bool', 'source'=>'string', 'options='=>'int'], +'DOMDocument::loadHTML' => ['bool', 'source'=>'non-empty-string', 'options='=>'int'], 'DOMDocument::loadHTMLFile' => ['bool', 'filename'=>'string', 'options='=>'int'], -'DOMDocument::loadXML' => ['DOMDocument|bool', 'source'=>'string', 'options='=>'int'], +'DOMDocument::loadXML' => ['DOMDocument|bool', 'source'=>'non-empty-string', 'options='=>'int'], 'DOMDocument::normalizeDocument' => ['void'], 'DOMDocument::registerNodeClass' => ['bool', 'baseclass'=>'string', 'extendedclass'=>'string'], 'DOMDocument::relaxNGValidate' => ['bool', 'filename'=>'string'], diff --git a/dictionaries/CallMap_historical.php b/dictionaries/CallMap_historical.php index 362a181db..813dffa7b 100644 --- a/dictionaries/CallMap_historical.php +++ b/dictionaries/CallMap_historical.php @@ -939,9 +939,9 @@ return [ 'DOMDocument::getElementsByTagNameNS' => ['DOMNodeList', 'namespaceuri'=>'string', 'localname'=>'string'], 'DOMDocument::importNode' => ['DOMNode|false', 'importednode'=>'DOMNode', 'deep='=>'bool'], 'DOMDocument::load' => ['DOMDocument|bool', 'filename'=>'string', 'options='=>'int'], - 'DOMDocument::loadHTML' => ['bool', 'source'=>'string', 'options='=>'int'], + 'DOMDocument::loadHTML' => ['bool', 'source'=>'non-empty-string', 'options='=>'int'], 'DOMDocument::loadHTMLFile' => ['bool', 'filename'=>'string', 'options='=>'int'], - 'DOMDocument::loadXML' => ['DOMDocument|bool', 'source'=>'string', 'options='=>'int'], + 'DOMDocument::loadXML' => ['DOMDocument|bool', 'source'=>'non-empty-string', 'options='=>'int'], 'DOMDocument::normalizeDocument' => ['void'], 'DOMDocument::registerNodeClass' => ['bool', 'baseclass'=>'string', 'extendedclass'=>'string'], 'DOMDocument::relaxNGValidate' => ['bool', 'filename'=>'string'], diff --git a/src/Psalm/Config.php b/src/Psalm/Config.php index 2e16c505e..6392d72d4 100644 --- a/src/Psalm/Config.php +++ b/src/Psalm/Config.php @@ -646,6 +646,10 @@ class Config throw new InvalidArgumentException('Cannot open ' . $file_path); } + if ($file_contents === '') { + throw new InvalidArgumentException('Invalid empty file ' . $file_path); + } + try { $config = self::loadFromXML($base_dir, $file_contents, $current_dir, $file_path); $config->hash = sha1($file_contents . PSALM_VERSION); @@ -669,6 +673,7 @@ class Config /** * Creates a new config object from an XML string * @param string|null $current_dir Current working directory, if different to $base_dir + * @param non-empty-string $file_contents * * @throws ConfigException */ @@ -687,6 +692,9 @@ class Config return self::fromXmlAndPaths($base_dir, $file_contents, $current_dir, $file_path); } + /** + * @param non-empty-string $file_contents + */ private static function loadDomDocument(string $base_dir, string $file_contents): DOMDocument { $dom_document = new DOMDocument(); @@ -704,6 +712,8 @@ class Config } /** + * @param non-empty-string $file_contents + * * @throws ConfigException */ private static function validateXmlConfig(string $base_dir, string $file_contents): void @@ -731,7 +741,9 @@ class Config $psalm_node->setAttribute('xmlns', self::CONFIG_NAMESPACE); $old_dom_document = $dom_document; - $dom_document = self::loadDomDocument($base_dir, $old_dom_document->saveXML()); + $old_file_contents = $old_dom_document->saveXML(); + assert($old_file_contents !== false && $old_file_contents !== ''); + $dom_document = self::loadDomDocument($base_dir, $old_file_contents); } // Enable user error handling @@ -857,6 +869,8 @@ class Config } /** + * @param non-empty-string $file_contents + * * @psalm-suppress MixedMethodCall * @psalm-suppress MixedAssignment * @psalm-suppress MixedArgument diff --git a/src/Psalm/Config/Creator.php b/src/Psalm/Config/Creator.php index 36760726e..f67eb5cf3 100644 --- a/src/Psalm/Config/Creator.php +++ b/src/Psalm/Config/Creator.php @@ -52,6 +52,9 @@ class Creator '; + /** + * @return non-empty-string + */ public static function getContents( string $current_dir, ?string $suggested_dir, @@ -80,6 +83,7 @@ class Creator ); } + /** @var non-empty-string */ return str_replace( 'errorLevel="1"', 'errorLevel="' . $level . '"', diff --git a/src/Psalm/Internal/PluginManager/ConfigFile.php b/src/Psalm/Internal/PluginManager/ConfigFile.php index daab9d8a3..4fd025425 100644 --- a/src/Psalm/Internal/PluginManager/ConfigFile.php +++ b/src/Psalm/Internal/PluginManager/ConfigFile.php @@ -7,6 +7,7 @@ use DomElement; use Psalm\Config; use RuntimeException; +use function assert; use function file_get_contents; use function file_put_contents; use function strpos; @@ -120,6 +121,7 @@ class ConfigFile } } + assert($file_contents !== ''); $doc->loadXML($file_contents); return $doc; diff --git a/tests/Config/ConfigTest.php b/tests/Config/ConfigTest.php index 806ea4e7d..6a15f41c9 100644 --- a/tests/Config/ConfigTest.php +++ b/tests/Config/ConfigTest.php @@ -1487,15 +1487,14 @@ class ConfigTest extends TestCase FileTypeSelfRegisteringPlugin::$names = $names; FileTypeSelfRegisteringPlugin::$flags = $flags; + /** @var non-empty-string $xml */ + $xml = sprintf( + ' + ', + FileTypeSelfRegisteringPlugin::class + ); $projectAnalyzer = $this->getProjectAnalyzerWithConfig( - TestConfig::loadFromXML( - dirname(__DIR__, 2), - sprintf( - ' - ', - FileTypeSelfRegisteringPlugin::class - ) - ) + TestConfig::loadFromXML(dirname(__DIR__, 2), $xml) ); diff --git a/tests/Config/PluginTest.php b/tests/Config/PluginTest.php index 0734f7e0f..6fd8b4976 100644 --- a/tests/Config/PluginTest.php +++ b/tests/Config/PluginTest.php @@ -982,24 +982,23 @@ class PluginTest extends TestCase public function testPluginFilenameCanBeAbsolute(): void { + /** @var non-empty-string $xml */ + $xml = sprintf( + ' + + + + + + + + ', + __DIR__ . '/../..' + ); $this->project_analyzer = $this->getProjectAnalyzerWithConfig( - TestConfig::loadFromXML( - dirname(__DIR__, 2) . DIRECTORY_SEPARATOR, - sprintf( - ' - - - - - - - - ', - __DIR__ . '/../..' - ) - ) + TestConfig::loadFromXML(dirname(__DIR__, 2) . DIRECTORY_SEPARATOR, $xml) ); $this->project_analyzer->getCodebase()->config->initializePlugins($this->project_analyzer); @@ -1010,24 +1009,23 @@ class PluginTest extends TestCase $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('does-not-exist/plugins/StringChecker.php'); + /** @var non-empty-string $xml */ + $xml = sprintf( + ' + + + + + + + + ', + __DIR__ . '/..' + ); $this->project_analyzer = $this->getProjectAnalyzerWithConfig( - TestConfig::loadFromXML( - dirname(__DIR__, 2) . DIRECTORY_SEPARATOR, - sprintf( - ' - - - - - - - - ', - __DIR__ . '/..' - ) - ) + TestConfig::loadFromXML(dirname(__DIR__, 2) . DIRECTORY_SEPARATOR, $xml) ); $this->project_analyzer->getCodebase()->config->initializePlugins($this->project_analyzer); diff --git a/tests/MethodCallTest.php b/tests/MethodCallTest.php index 8676075fd..dde7c1523 100644 --- a/tests/MethodCallTest.php +++ b/tests/MethodCallTest.php @@ -387,6 +387,7 @@ class MethodCallTest extends TestCase ], 'domElementIteratorOrEmptyArray' => [ 'loadXML($XML);