Skip to content

Commit

Permalink
Merge pull request #7107 from AndrolGenhald/feature/5482-load-extensi…
Browse files Browse the repository at this point in the history
…ons-based-on-composer-config

Enable extensions based on composer.json instead of those loaded at runtime (fixes #5482).
  • Loading branch information
orklah committed Jan 28, 2022
2 parents 8914793 + d705d5e commit 2966f1c
Show file tree
Hide file tree
Showing 28 changed files with 390 additions and 169 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Expand Up @@ -82,7 +82,7 @@ jobs:
ini-values: zend.assertions=1, assert.exception=1
tools: composer:v2
coverage: none
extensions: decimal
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 .github/workflows/windows-ci.yml
Expand Up @@ -52,6 +52,7 @@ jobs:
ini-values: zend.assertions=1, assert.exception=1
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 @@ -931,7 +950,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 @@ -966,6 +984,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);
}
}
foreach ($config->php_extensions as $ext => $_) {
$config->php_extensions[$ext] = isset($composer_json["require"]["ext-$ext"]);
}

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 @@ -1969,7 +2017,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 @@ -1984,39 +2031,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

0 comments on commit 2966f1c

Please sign in to comment.