Skip to content

Commit

Permalink
Enable extensions based on composer.json instead of those loaded at r…
Browse files Browse the repository at this point in the history
…untime (fixes vimeo#5482).
  • Loading branch information
AndrolGenhald committed Dec 8, 2021
1 parent eefdb95 commit ed22d0f
Show file tree
Hide file tree
Showing 21 changed files with 250 additions and 117 deletions.
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: :pdo

- uses: actions/checkout@v2

Expand Down
3 changes: 0 additions & 3 deletions dictionaries/InternalTaintSinkMap.php
Expand Up @@ -41,9 +41,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
102 changes: 67 additions & 35 deletions src/Psalm/Config.php
Expand Up @@ -53,7 +53,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 @@ -569,6 +568,34 @@ class Config
*/
public $internal_stubs = [];

/**
* @readonly
* @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 @@ -933,6 +960,32 @@ 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 ([
"decimal",
"dom",
"ds",
"geos",
"mongodb",
"mysqli",
"pdo",
"soap",
"xdebug",
] as $ext) {
$config->enableExtension($ext, isset($composer_json["require"]["ext-$ext"]));
}

if ($config->load_xdebug_stub !== null) {
$config->enableExtension("xdebug", $config->load_xdebug_stub);
}

if (isset($config_xml['phpVersion'])) {
$config->configured_php_version = (string) $config_xml['phpVersion'];
}
Expand Down Expand Up @@ -1924,7 +1977,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 @@ -1939,39 +1991,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 Expand Up @@ -2301,4 +2325,12 @@ public function getUniversalObjectCrates(): array
{
return array_map('strtolower', $this->universal_object_crates);
}

/**
* @param "decimal"|"dom"|"ds"|"geos"|"gmp"|"mongodb"|"mysqli"|"pdo"|"soap"|"xdebug" $ext
*/
private function enableExtension(string $ext, bool $enable = true): void
{
$this->php_extensions[$ext] = $enable;
}
}
@@ -1,13 +1,11 @@
<?php
namespace Psalm\Internal\Provider\ReturnTypeProvider;

use PDO;
use Psalm\Config;
use Psalm\Plugin\EventHandler\Event\MethodReturnTypeProviderEvent;
use Psalm\Plugin\EventHandler\MethodReturnTypeProviderInterface;
use Psalm\Type;

use function class_exists;

class PdoStatementReturnTypeProvider implements MethodReturnTypeProviderInterface
{
public static function getClassLikeNames(): array
Expand All @@ -17,19 +15,20 @@ public static function getClassLikeNames(): array

public static function getMethodReturnType(MethodReturnTypeProviderEvent $event): ?Type\Union
{
$config = Config::getInstance();
$source = $event->getSource();
$call_args = $event->getCallArgs();
$method_name_lowercase = $event->getMethodNameLowercase();
if ($method_name_lowercase === 'fetch'
&& class_exists('PDO')
&& $config->php_extensions["pdo"]
&& isset($call_args[0])
&& ($first_arg_type = $source->getNodeTypeProvider()->getType($call_args[0]->value))
&& $first_arg_type->isSingleIntLiteral()
) {
$fetch_mode = $first_arg_type->getSingleIntLiteral()->value;

switch ($fetch_mode) {
case PDO::FETCH_ASSOC: // array<string,scalar|null>|false
case 2: // PDO::FETCH_ASSOC - array<string,scalar|null>|false
return new Type\Union([
new Type\Atomic\TArray([
Type::getString(),
Expand All @@ -41,7 +40,7 @@ public static function getMethodReturnType(MethodReturnTypeProviderEvent $event)
new Type\Atomic\TFalse(),
]);

case PDO::FETCH_BOTH: // array<array-key,scalar|null>|false
case 4: // PDO::FETCH_BOTH - array<array-key,scalar|null>|false
return new Type\Union([
new Type\Atomic\TArray([
Type::getArrayKey(),
Expand All @@ -53,24 +52,24 @@ public static function getMethodReturnType(MethodReturnTypeProviderEvent $event)
new Type\Atomic\TFalse(),
]);

case PDO::FETCH_BOUND: // bool
case 6: // PDO::FETCH_BOUND - bool
return Type::getBool();

case PDO::FETCH_CLASS: // object|false
case 8: // PDO::FETCH_CLASS - object|false
return new Type\Union([
new Type\Atomic\TObject(),
new Type\Atomic\TFalse(),
]);

case PDO::FETCH_LAZY: // object|false
case 1: // PDO::FETCH_LAZY - object|false
// This actually returns a PDORow object, but that class is
// undocumented, and its attributes are all dynamic anyway
return new Type\Union([
new Type\Atomic\TObject(),
new Type\Atomic\TFalse(),
]);

case PDO::FETCH_NAMED: // array<string, scalar|list<scalar>>|false
case 11: // PDO::FETCH_NAMED - array<string, scalar|list<scalar>>|false
return new Type\Union([
new Type\Atomic\TArray([
Type::getString(),
Expand All @@ -82,7 +81,7 @@ public static function getMethodReturnType(MethodReturnTypeProviderEvent $event)
new Type\Atomic\TFalse(),
]);

case PDO::FETCH_NUM: // list<scalar|null>|false
case 3: // PDO::FETCH_NUM - list<scalar|null>|false
return new Type\Union([
new Type\Atomic\TList(
new Type\Union([
Expand All @@ -93,7 +92,7 @@ public static function getMethodReturnType(MethodReturnTypeProviderEvent $event)
new Type\Atomic\TFalse(),
]);

case PDO::FETCH_OBJ: // stdClass|false
case 5: // PDO::FETCH_OBJ - stdClass|false
return new Type\Union([
new Type\Atomic\TNamedObject('stdClass'),
new Type\Atomic\TFalse(),
Expand Down
@@ -1,7 +1,6 @@
<?php
namespace Psalm\Internal\Provider\ReturnTypeProvider;

use PDO;
use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
use Psalm\Internal\Analyzer\StatementsAnalyzer;
use Psalm\Plugin\EventHandler\Event\MethodParamsProviderEvent;
Expand Down Expand Up @@ -58,7 +57,7 @@ public static function getMethodParams(MethodParamsProviderEvent $event): ?array
$value = $first_call_arg_type->getSingleIntLiteral()->value;

switch ($value) {
case PDO::FETCH_COLUMN:
case 7: // PDO::FETCH_COLUMN
$params[] = new FunctionLikeParameter(
'colno',
false,
Expand All @@ -69,7 +68,7 @@ public static function getMethodParams(MethodParamsProviderEvent $event): ?array
);
break;

case PDO::FETCH_CLASS:
case 8: // PDO::FETCH_CLASS
$params[] = new FunctionLikeParameter(
'classname',
false,
Expand All @@ -89,7 +88,7 @@ public static function getMethodParams(MethodParamsProviderEvent $event): ?array
);
break;

case PDO::FETCH_INTO:
case 9: // PDO::FETCH_INTO
$params[] = new FunctionLikeParameter(
'object',
false,
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
11 changes: 11 additions & 0 deletions stubs/extensions/gmp.phpstub
@@ -0,0 +1,11 @@
<?php

class GMP implements Serializable {
private function __construct() {}

public function __toString(): string {}

public function serialize(): string {}

public function unserialize(string $data): void {}
}
File renamed without changes.
File renamed without changes.

0 comments on commit ed22d0f

Please sign in to comment.