Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable extensions based on composer.json instead of those loaded at runtime (fixes #5482). #7107

Merged
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Expand Up @@ -81,7 +81,7 @@ jobs:
php-version: '8.0'
tools: composer:v2
coverage: none
extensions: decimal
extensions: none, curl, dom, filter, json, libxml, mbstring, openssl, pcre, phar, reflection, simplexml, spl, tokenizer, xml, xmlwriter
orklah marked this conversation as resolved.
Show resolved Hide resolved

- uses: actions/checkout@v2

Expand Down
1 change: 1 addition & 0 deletions .github/workflows/windows-ci.yml
Expand Up @@ -51,6 +51,7 @@ jobs:
php-version: '8.0'
tools: composer:v2
coverage: none
extensions: none, curl, dom, filter, json, libxml, mbstring, openssl, pcre, phar, reflection, simplexml, spl, tokenizer, xml, xmlwriter

- uses: actions/checkout@v2

Expand Down
1 change: 1 addition & 0 deletions UPGRADING.md
Expand Up @@ -165,6 +165,7 @@
- [BC] Property `Psalm\Config::$allow_phpstorm_generics` was removed
- [BC] Property `Psalm\Config::$exit_functions` was removed
- [BC] Property `Psalm\Config::$forbid_echo` was removed
- [BC] Property `Psalm\Config::$load_xdebug_stub` was removed
- [BC] Method `Psalm\Type::getEmpty()` was removed
- [BC] Legacy hook interfaces have been removed:
- `Psalm\Plugin\Hook\MethodReturnTypeProviderInterface`
Expand Down
39 changes: 27 additions & 12 deletions config.xsd
Expand Up @@ -21,6 +21,8 @@
<xs:element name="ignoreExceptions" type="ExceptionsType" minOccurs="0" maxOccurs="1" />
<xs:element name="globals" type="GlobalsType" minOccurs="0" maxOccurs="1" />
<xs:element name="universalObjectCrates" type="UniversalObjectCratesType" minOccurs="0" maxOccurs="1" />
<xs:element name="enableExtensions" type="ExtensionsType" minOccurs="0" maxOccurs="1" />
<xs:element name="disableExtensions" type="ExtensionsType" minOccurs="0" maxOccurs="1" />
</xs:choice>

<xs:attribute name="autoloader" type="xs:string" />
Expand All @@ -47,18 +49,6 @@
<xs:attribute name="ignoreInternalFunctionNullReturn" type="xs:boolean" default="true" />
<xs:attribute name="includePhpVersionsInErrorBaseline" type="xs:boolean" default="false" />
<xs:attribute name="inferPropertyTypesFromConstructor" type="xs:boolean" default="true" />
<xs:attribute name="loadXdebugStub" type="xs:boolean">
<xs:annotation>
<xs:documentation xml:lang="en">
Default is runtime-specific: if not present, Psalm will only load the Xdebug stub if psalm has unloaded the extension.
</xs:documentation>

<!-- note: for PHPStorm to mark the attribute as deprecated the doc entry has to be *single line* and start with the word `deprecated` -->
<xs:documentation xml:lang="en">
Deprecated. In Psalm 5 extensions will be loaded based on composer.json and overridden with enableExtensions/disableExtensions.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="memoizeMethodCallResults" type="xs:boolean" default="false" />
<xs:attribute name="rememberPropertyAssignmentsAfterCall" type="xs:boolean" default="true" />
<xs:attribute name="resolveFromConfigFile" type="xs:boolean" default="true" />
Expand Down Expand Up @@ -682,4 +672,29 @@
<xs:enumeration value="always"/>
</xs:restriction>
</xs:simpleType>

<xs:complexType name="ExtensionsType">
<xs:sequence>
<xs:element name="extension" maxOccurs="unbounded" type="ExtensionAttributeType" />
</xs:sequence>
</xs:complexType>

<xs:simpleType name="ExtensionType">
<xs:restriction base="xs:string">
<xs:enumeration value="decimal"/>
<xs:enumeration value="dom"/>
<xs:enumeration value="ds"/>
<xs:enumeration value="geos"/>
<xs:enumeration value="gmp"/>
<xs:enumeration value="mongodb"/>
<xs:enumeration value="mysqli"/>
<xs:enumeration value="pdo"/>
<xs:enumeration value="soap"/>
<xs:enumeration value="xdebug"/>
</xs:restriction>
</xs:simpleType>

<xs:complexType name="ExtensionAttributeType">
<xs:attribute name="name" type="ExtensionType" use="required" />
</xs:complexType>
</xs:schema>
3 changes: 0 additions & 3 deletions dictionaries/InternalTaintSinkMap.php
Expand Up @@ -43,9 +43,6 @@
'mysqli_stmt::prepare' => [['sql']],
'passthru' => [['shell']],
'pcntl_exec' => [['shell']],
'PDO::prepare' => [['sql']],
'PDO::query' => [['sql']],
'PDO::exec' => [['sql']],
'pg_exec' => [[], ['sql']],
'pg_prepare' => [[], [], ['sql']],
'pg_put_line' => [[], ['sql']],
Expand Down
31 changes: 19 additions & 12 deletions docs/running_psalm/configuration.md
Expand Up @@ -246,16 +246,6 @@ When `true`, Psalm will attempt to find all unused code (including unused variab
```
When `true`, Psalm will report all `@psalm-suppress` annotations that aren't used, the equivalent of running with `--find-unused-psalm-suppress`. Defaults to `false`.

#### loadXdebugStub
```xml
<psalm
loadXdebugStub="[bool]"
>
```
If not present, Psalm will only load the Xdebug stub if Psalm has unloaded the extension.
When `true`, Psalm will load the Xdebug extension stub (as the extension is unloaded when Psalm runs).
Setting to `false` prevents the stub from loading.

#### ensureArrayStringOffsetsExist
```xml
<psalm
Expand Down Expand Up @@ -336,7 +326,7 @@ When `false`, Psalm will not consider issue at lower level than `errorLevel` as
#### allowNamedArgumentCalls

```xml
<psalm
<psalm
allowNamedArgumentCalls="[bool]"
>
```
Expand Down Expand Up @@ -432,6 +422,23 @@ Optional. Same format as `<projectFiles>`. Directories Psalm should load but not
#### &lt;fileExtensions&gt;
Optional. A list of extensions to search over. See [Checking non-PHP files](checking_non_php_files.md) to understand how to extend this.

#### &lt;enableExtensions&gt;
Optional. A list of extensions to enable. By default, only extensions required by your composer.json will be enabled.
```xml
<enableExtensions>
<extension name="decimal"/>
<extension name="pdo"/>
</enableExtensions>
```

#### &lt;disableExtensions&gt;
Optional. A list of extensions to disable. By default, only extensions required by your composer.json will be enabled.
```xml
<disableExtensions>
<extension name="gmp"/>
</disableExtensions>
```

#### &lt;plugins&gt;
Optional. A list of `<plugin filename="path_to_plugin.php" />` entries. See the [Plugins](plugins/using_plugins.md) section for more information.

Expand Down Expand Up @@ -483,7 +490,7 @@ The following configuration declares custom types for super-globals (`$GLOBALS`
```xml
<globals>
<var name="$GLOBALS" type="array{DB: MyVendor\DatabaseConnection, VIEW: MyVendor\TemplateView}" />
<var name="$_GET" type="array{data: array<string, string>}" />
<var name="$_GET" type="array{data: array<string, string>}" />
</globals>
```

Expand Down
14 changes: 2 additions & 12 deletions psalm-baseline.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<files psalm-version="dev-master@c90cffd382bb7a5d390387d4ad2a02a4935fba5c">
<files psalm-version="dev-master@093edcbe1d5c90ad574efcddd62fe741819e7cf2">
<file src="examples/TemplateChecker.php">
<PossiblyUndefinedIntArrayOffset occurrences="2">
<code>$comment_block-&gt;tags['variablesfrom'][0]</code>
Expand All @@ -24,9 +24,6 @@
<code>getAdditionalFileTypeAnalyzers</code>
<code>getAdditionalFileTypeScanners</code>
</DeprecatedMethod>
<DeprecatedProperty occurrences="1">
<code>$this-&gt;load_xdebug_stub</code>
</DeprecatedProperty>
</file>
<file src="src/Psalm/Config/FileFilter.php">
<PossiblyUndefinedIntArrayOffset occurrences="1">
Expand Down Expand Up @@ -203,12 +200,6 @@
<code>$stmt-&gt;expr-&gt;getArgs()[0]</code>
</PossiblyUndefinedIntArrayOffset>
</file>
<file src="src/Psalm/Internal/Cli/Psalm.php">
<DeprecatedProperty occurrences="2">
<code>$config-&gt;load_xdebug_stub</code>
<code>$config-&gt;load_xdebug_stub</code>
</DeprecatedProperty>
</file>
<file src="src/Psalm/Internal/Codebase/InternalCallMapHandler.php">
<PossiblyUndefinedIntArrayOffset occurrences="2">
<code>$callables[0]</code>
Expand Down Expand Up @@ -286,8 +277,7 @@
<DeprecatedProperty occurrences="1">
<code>$storage-&gt;template_extended_count</code>
</DeprecatedProperty>
<PossiblyUndefinedIntArrayOffset occurrences="4">
<code>$imported_type_data[3]</code>
<PossiblyUndefinedIntArrayOffset occurrences="3">
<code>$l[4]</code>
<code>$r[4]</code>
<code>$var_line_parts[0]</code>
Expand Down
109 changes: 64 additions & 45 deletions src/Psalm/Config.php
Expand Up @@ -46,6 +46,7 @@
use XdgBaseDir\Xdg;
use stdClass;

use function array_key_exists;
use function array_map;
use function array_merge;
use function array_pad;
Expand All @@ -58,7 +59,6 @@
use function count;
use function dirname;
use function explode;
use function extension_loaded;
use function file_exists;
use function file_get_contents;
use function filetype;
Expand Down Expand Up @@ -196,15 +196,6 @@ class Config
*/
public $throw_exception = false;

/**
* Whether or not to load Xdebug stub
*
* @deprecated going to be removed in Psalm 5
*
* @var bool|null
*/
public $load_xdebug_stub;

/**
* The directory to store PHP Parser (and other) caches
*
Expand Down Expand Up @@ -574,6 +565,34 @@ class Config
/** @var ?int */
public $threads;

/**
* @psalm-readonly-allow-private-mutation
* @var array{
* decimal: bool,
* dom: bool,
* ds: bool,
* geos: bool,
* gmp: bool,
* mongodb: bool,
* mysqli: bool,
* pdo: bool,
* soap: bool,
* xdebug: bool,
* }
*/
public $php_extensions = [
"decimal" => false,
"dom" => false,
"ds" => false,
"geos" => false,
"gmp" => false,
"mongodb" => false,
"mysqli" => false,
"pdo" => false,
"soap" => false,
"xdebug" => false,
];

protected function __construct()
{
self::$instance = $this;
Expand Down Expand Up @@ -929,7 +948,6 @@ private static function fromXmlAndPaths(
'ignoreInternalFunctionFalseReturn' => 'ignore_internal_falsable_issues',
'ignoreInternalFunctionNullReturn' => 'ignore_internal_nullable_issues',
'includePhpVersionsInErrorBaseline' => 'include_php_versions_in_error_baseline',
'loadXdebugStub' => 'load_xdebug_stub',
'ensureArrayStringOffsetsExist' => 'ensure_array_string_offsets_exist',
'ensureArrayIntOffsetsExist' => 'ensure_array_int_offsets_exist',
'reportMixedIssues' => 'show_mixed_issues',
Expand Down Expand Up @@ -964,6 +982,36 @@ private static function fromXmlAndPaths(
$base_dir = $current_dir;
}

$composer_json_path = Composer::getJsonFilePath($config->base_dir);

$composer_json = null;
if (file_exists($composer_json_path)) {
if (!$composer_json = json_decode(file_get_contents($composer_json_path), true)) {
throw new UnexpectedValueException('Invalid composer.json at ' . $composer_json_path);
orklah marked this conversation as resolved.
Show resolved Hide resolved
}
}
foreach ($config->php_extensions as $ext => $_) {
$config->php_extensions[$ext] = isset($composer_json["require"]["ext-$ext"]);
orklah marked this conversation as resolved.
Show resolved Hide resolved
}

if (isset($config_xml->enableExtensions) && isset($config_xml->enableExtensions->extension)) {
foreach ($config_xml->enableExtensions->extension as $extension) {
assert(isset($extension["name"]));
$extensionName = (string) $extension["name"];
assert(array_key_exists($extensionName, $config->php_extensions));
$config->php_extensions[$extensionName] = true;
}
}

if (isset($config_xml->disableExtensions) && isset($config_xml->disableExtensions->extension)) {
foreach ($config_xml->disableExtensions->extension as $extension) {
assert(isset($extension["name"]));
$extensionName = (string) $extension["name"];
assert(array_key_exists($extensionName, $config->php_extensions));
$config->php_extensions[$extensionName] = false;
}
}

if (isset($config_xml['phpVersion'])) {
$config->configured_php_version = (string) $config_xml['phpVersion'];
}
Expand Down Expand Up @@ -1967,7 +2015,6 @@ public function visitStubFiles(Codebase $codebase, ?Progress $progress = null):
$dir_lvl_2 . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'CoreGenericClasses.phpstub',
$dir_lvl_2 . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'CoreGenericIterators.phpstub',
$dir_lvl_2 . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'CoreImmutableClasses.phpstub',
$dir_lvl_2 . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'DOM.phpstub',
$dir_lvl_2 . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'Reflection.phpstub',
$dir_lvl_2 . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'SPL.phpstub',
];
Expand All @@ -1982,39 +2029,11 @@ public function visitStubFiles(Codebase $codebase, ?Progress $progress = null):
$this->internal_stubs[] = $stringable_path;
}

if (extension_loaded('PDO')) {
$ext_pdo_path = $dir_lvl_2 . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'pdo.phpstub';
$this->internal_stubs[] = $ext_pdo_path;
}

if (extension_loaded('soap')) {
$ext_soap_path = $dir_lvl_2 . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'soap.phpstub';
$this->internal_stubs[] = $ext_soap_path;
}

if (extension_loaded('ds')) {
$ext_ds_path = $dir_lvl_2 . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'ext-ds.phpstub';
$this->internal_stubs[] = $ext_ds_path;
}

if (extension_loaded('mongodb')) {
$ext_mongodb_path = $dir_lvl_2 . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'mongodb.phpstub';
$this->internal_stubs[] = $ext_mongodb_path;
}

if ($this->load_xdebug_stub) {
$xdebug_stub_path = $dir_lvl_2 . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'Xdebug.phpstub';
$this->internal_stubs[] = $xdebug_stub_path;
}

if (extension_loaded('mysqli')) {
$ext_mysqli_path = $dir_lvl_2 . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'mysqli.phpstub';
$this->internal_stubs[] = $ext_mysqli_path;
}

if (extension_loaded('decimal')) {
$ext_decimal_path = $dir_lvl_2 . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'decimal.phpstub';
$this->internal_stubs[] = $ext_decimal_path;
foreach ($this->php_extensions as $ext => $enabled) {
if ($enabled) {
$this->internal_stubs[] = $dir_lvl_2 . DIRECTORY_SEPARATOR . "stubs"
. DIRECTORY_SEPARATOR . "extensions" . DIRECTORY_SEPARATOR . "$ext.phpstub";
}
}

foreach ($this->internal_stubs as $stub_path) {
Expand Down
8 changes: 2 additions & 6 deletions src/Psalm/Internal/Cli/Psalm.php
Expand Up @@ -253,7 +253,7 @@ public static function run(array $argv): void

self::emitMacPcreWarning($options, $threads);

self::restart($options, $config, $threads);
self::restart($options, $threads);

if (isset($options['debug-emitted-issues'])) {
$config->debug_emitted_issues = true;
Expand Down Expand Up @@ -882,7 +882,7 @@ private static function emitMacPcreWarning(array $options, int $threads): void
}
}

private static function restart(array $options, Config $config, int $threads): void
private static function restart(array $options, int $threads): void
{
$ini_handler = new PsalmRestarter('PSALM');

Expand All @@ -907,10 +907,6 @@ private static function restart(array $options, Config $config, int $threads): v

// If Xdebug is enabled, restart without it
$ini_handler->check();

if ($config->load_xdebug_stub === null && PsalmRestarter::getSkippedVersion() !== '') {
$config->load_xdebug_stub = true;
}
}

private static function detectThreads(array $options, Config $config, bool $in_ci): int
Expand Down