diff --git a/lib/nikic/php-parser/lib/PhpParser/ConstExprEvaluator.php b/lib/nikic/php-parser/lib/PhpParser/ConstExprEvaluator.php index 79de55005..74fcc2fb8 100644 --- a/lib/nikic/php-parser/lib/PhpParser/ConstExprEvaluator.php +++ b/lib/nikic/php-parser/lib/PhpParser/ConstExprEvaluator.php @@ -3,10 +3,6 @@ namespace PhpParser; use PhpParser\Node\Expr; -use PhpParser\Node\Expr\ClassConstFetch; -use PhpParser\Node\Expr\FuncCall; -use PhpParser\Node\Expr\StaticCall; -use PhpParser\Node\Expr\Variable; use PhpParser\Node\Identifier; use PhpParser\Node\Name; use PhpParser\Node\Scalar; @@ -37,13 +33,13 @@ class ConstExprEvaluator { /** @var callable|null */ private $fallbackEvaluator; - /** @var array $functions_whitelist */ - private $functions_whitelist; + /** @var array $functionsWhiteList */ + private $functionsWhiteList; - /** @var array staticcalls_whitelist */ - private $staticcalls_whitelist; + /** @var array $staticCallsWhitelist */ + private $staticCallsWhitelist; - /** + /** * Create a constant expression evaluator. * * The provided fallback evaluator is invoked whenever a subexpression cannot be evaluated. See @@ -58,19 +54,19 @@ class ConstExprEvaluator { ); }; - $this->functions_whitelist=[]; - $this->staticcalls_whitelist=[]; + $this->functionsWhiteList = []; + $this->staticCallsWhitelist = []; } - public function setFunctionsWhitelist(array $functions_whitelist): void + public function setFunctionsWhitelist(array $functionsWhiteList): void { - $this->functions_whitelist = $functions_whitelist; + $this->functionsWhiteList = $functionsWhiteList; } - public function setStaticcallsWhitelist(array $staticcalls_whitelist): void + public function setStaticCallsWhitelist(array $staticCallsWhitelist): void { - $this->staticcalls_whitelist = $staticcalls_whitelist; - } + $this->staticCallsWhitelist = $staticCallsWhitelist; + } /** * Silently evaluates a constant expression into a PHP value. @@ -141,6 +137,10 @@ class ConstExprEvaluator { return $this->evaluateArray($expr); } + if ($expr instanceof Expr\Variable) { + return $this->evaluateVariable($expr); + } + // Unary operators if ($expr instanceof Expr\UnaryPlus) { return +$this->evaluate($expr->expr); @@ -191,10 +191,6 @@ class ConstExprEvaluator { return $this->evaluateFuncCall($expr); } - if ($expr instanceof Expr\Variable) { - return $this->evaluateVariable($expr); - } - if ($expr instanceof Expr\StaticCall) { return $this->evaluateStaticCall($expr); } @@ -236,12 +232,14 @@ class ConstExprEvaluator { /** @return mixed */ private function evaluateBinaryOp(Expr\BinaryOp $expr) { - if ($expr instanceof Expr\BinaryOp\Coalesce - && $expr->left instanceof Expr\ArrayDimFetch - ) { - // This needs to be special cased to respect BP_VAR_IS fetch semantics - return $this->evaluate($expr->left->var)[$this->evaluate($expr->left->dim)] - ?? $this->evaluate($expr->right); + if ($expr instanceof Expr\BinaryOp\Coalesce) { + try { + $var = $this->evaluate($expr->left); + return $var ?? $this->evaluate($expr->right); + } catch(\Throwable $t){ + //handle when isset($expr->left->var)===false + return $this->evaluate($expr->right); + } } // The evaluate() calls are repeated in each branch, because some of the operators are @@ -291,7 +289,7 @@ class ConstExprEvaluator { if(! is_string($name)){ //PHP_VERSION_ID usecase $name = $name->name; - } + } if (defined($name)){ return constant($name); @@ -314,7 +312,7 @@ class ConstExprEvaluator { return true; } catch(\Throwable $t){ return false; - }; + } } /** @return mixed */ @@ -410,13 +408,14 @@ class ConstExprEvaluator { private function evaluateFuncCall(Expr\FuncCall $expr) { try { - if ($expr->name instanceof Name){ - $function = $expr->name->name; + $name = $expr->name; + if ($name instanceof Name){ + $function = $name->name; } else { - $function = $this->evaluate($expr->name); + $function = $this->evaluate($name); } - if (! in_array($function, $this->functions_whitelist)){ + if (! in_array($function, $this->functionsWhiteList)){ throw new Exception("FuncCall $function not supported"); } @@ -439,11 +438,16 @@ class ConstExprEvaluator { { try { $name = $expr->name; - if (! is_null($name) && isset($name)) { + if (array_key_exists($name, get_defined_vars())) { + return $$name; + } + + if (array_key_exists($name, $GLOBALS)) { global $$name; return $$name; } - } catch (\Throwable $t) {} + } catch (\Throwable $t) { + } return ($this->fallbackEvaluator)($expr); } @@ -452,15 +456,15 @@ class ConstExprEvaluator { private function evaluateStaticCall(Expr\StaticCall $expr) { try { - $classname = $expr->class->name; + $class = $expr->class->name; if ($expr->name instanceof Identifier){ - $methodname = $expr->name->name; + $method = $expr->name->name; } else { - $methodname = $this->evaluate($expr->name); + $method = $this->evaluate($expr->name); } - $static_call_description = "$classname::$methodname"; - if (! in_array($static_call_description, $this->staticcalls_whitelist)){ + $static_call_description = "$class::$method"; + if (! in_array($static_call_description, $this->staticCallsWhitelist)){ throw new Exception("StaticCall $static_call_description not supported"); } @@ -470,8 +474,8 @@ class ConstExprEvaluator { $args[]=$arg->value->value; } - $class = new \ReflectionClass($classname); - $method = $class->getMethod($methodname); + $class = new \ReflectionClass($class); + $method = $class->getMethod($method); if ($method->isPublic()){ return $method->invokeArgs(null, $args); } @@ -480,59 +484,79 @@ class ConstExprEvaluator { return ($this->fallbackEvaluator)($expr); } - /** @return mixed */ - private function evaluatePropertyFetch(Expr\NullsafePropertyFetch|Expr\PropertyFetch $expr) + /** + * @param \PhpParser\Node\Expr\NullsafePropertyFetch|\PhpParser\Node\Expr\PropertyFetch $expr + * + * @return mixed + */ + private function evaluatePropertyFetch($expr) { try { - $var = $this->evaluateVariable($expr->var); - if (is_null($var)) { - return null; - } + $var = $this->evaluate($expr->var); + } catch (\Throwable $t) { + $var = null; + } - if ($expr->name instanceof Identifier){ - $name = $expr->name->name; - } else { - $name = $this->evaluate($expr->name); - } + if (! is_null($var)) { + try { + if ($expr->name instanceof Identifier) { + $name = $expr->name->name; + } else { + $name = $this->evaluate($expr->name); + } - $reflectionClass = new \ReflectionClass(get_class($var)); - $property = $reflectionClass->getProperty($name); - if ($property->isPublic()){ - return $property->getValue($var); + $reflectionClass = new \ReflectionClass(get_class($var)); + $property = $reflectionClass->getProperty($name); + if ($property->isPublic()) { + return $property->getValue($var); + } } - } catch (\Throwable $t) {} + catch (\Throwable $t) {} + } else if ($expr instanceof Expr\NullsafePropertyFetch){ + return null; + } return ($this->fallbackEvaluator)($expr); } - /** @return mixed */ - private function evaluateMethodCall(Expr\MethodCall|Expr\NullsafeMethodCall $expr) + /** + * @param \PhpParser\Node\Expr\MethodCall|\PhpParser\Node\Expr\NullsafeMethodCall $expr + * + * @return mixed + */ + private function evaluateMethodCall($expr) { try { - $var = $this->evaluateVariable($expr->var); - if (is_null($var)) { - return null; - } + $var = $this->evaluate($expr->var); + } catch (\Throwable $t) { + $var = null; + } - $args=[]; - foreach ($expr->args as $arg){ + if (! is_null($var)) { + try { + $args = []; + foreach ($expr->args as $arg) { /** @var \PhpParser\Node\Arg $arg */ - $args[]=$arg->value->value; + $args[] = $arg->value->value; } - if ($expr->name instanceof Identifier){ + if ($expr->name instanceof Identifier) { $name = $expr->name->name; } else { $name = $this->evaluate($expr->name); } + $reflectionClass = new \ReflectionClass(get_class($var)); $method = $reflectionClass->getMethod($name); - if ($method->isPublic()){ - return $method->invokeArgs($var, $args); + if ($method->isPublic()) { + return $method->invokeArgs($var, $args); + } } - } catch (\Throwable $t) {} + catch (\Throwable $t) {} + } else if ($expr instanceof Expr\NullsafeMethodCall){ + return null; + } return ($this->fallbackEvaluator)($expr); } - } diff --git a/sources/PhpParser/Evaluation/IdenticalEvaluator.php b/sources/PhpParser/Evaluation/IdenticalEvaluator.php new file mode 100644 index 000000000..9798eedbe --- /dev/null +++ b/sources/PhpParser/Evaluation/IdenticalEvaluator.php @@ -0,0 +1,16 @@ +vars as $oVar){ - $var = PhpExpressionEvaluator::GetInstance()->EvaluateExpression($oVar); - if (! isset($var)){ + try{ + $var = PhpExpressionEvaluator::GetInstance()->EvaluateExpression($oVar); + if (is_null($var)){ + return false; + } + } catch (\Throwable $t) { return false; } } diff --git a/sources/PhpParser/Evaluation/PhpExpressionEvaluator.php b/sources/PhpParser/Evaluation/PhpExpressionEvaluator.php index 8abad0263..4b870c31d 100644 --- a/sources/PhpParser/Evaluation/PhpExpressionEvaluator.php +++ b/sources/PhpParser/Evaluation/PhpExpressionEvaluator.php @@ -12,10 +12,19 @@ class PhpExpressionEvaluator { /** @var iExprEvaluator[] $aPhpParserEvaluators */ private static array $aPhpParserEvaluators; + private int $iMode=self::ITOP_ALGO; protected function __construct() { } + const LIB_AND_FALLBACK=1; + const LIB_ONLY=2; + const ITOP_ALGO=3; + public function SetMode($iMode) + { + $this->iMode =$iMode; + } + final public static function GetInstance(): PhpExpressionEvaluator { if (!isset(static::$oInstance)) { static::$oInstance = new static(); @@ -58,13 +67,13 @@ class PhpExpressionEvaluator { static::$oInstance = $oInstance; } - public function EvaluateExpression(Expr $oExpression, int $iMode=self::LIB_AND_FALLBACK) : mixed + public function EvaluateExpression(Expr $oExpression) : mixed { - if ($iMode==self::ITOP_ALGO){ + if ($this->iMode===self::ITOP_ALGO){ return $this->EvaluateExpressionLocally($oExpression); } - if ($iMode==self::LIB_ONLY){ + if ($this->iMode==self::LIB_ONLY){ $oConstExprEvaluator = new ConstExprEvaluator(); } else { $oConstExprEvaluator = new ConstExprEvaluator([$this, "EvaluateExpressionLocally"]); @@ -97,10 +106,7 @@ class PhpExpressionEvaluator { return $this->ParseAndEvaluateExpression($sBooleanExpr); } - const LIB_AND_FALLBACK=1; - const LIB_ONLY=2; - const ITOP_ALGO=3; - public function ParseAndEvaluateExpression(string $sExpr, int $iMode=self::LIB_AND_FALLBACK) : mixed + public function ParseAndEvaluateExpression(string $sExpr) : mixed { $sPhpContent = <<ParsePhpCode($sPhpContent); $oExpr = $aNodes[0]; - return $this->EvaluateExpression($oExpr->expr, $iMode); + return $this->EvaluateExpression($oExpr->expr); } catch (\Throwable $t) { throw new ModuleFileReaderException("Eval of '$sExpr' caused an error:".$t->getMessage()); } diff --git a/sources/PhpParser/Evaluation/VariableEvaluator.php b/sources/PhpParser/Evaluation/VariableEvaluator.php index 967224c9b..f1b8acea1 100644 --- a/sources/PhpParser/Evaluation/VariableEvaluator.php +++ b/sources/PhpParser/Evaluation/VariableEvaluator.php @@ -9,19 +9,20 @@ class VariableEvaluator extends AbstractExprEvaluator { public function GetHandledExpressionType(): ?string { return Variable::class; } - public function Evaluate(Expr $oExpr): mixed { /** @var Variable $oExpr */ - if (is_null($oExpr->name)){ - return null; + $sName = $oExpr->name; + + if (array_key_exists($sName, get_defined_vars())) { + return $$sName; } - if (! isset($oExpr->name)) { - return null; + if (array_key_exists($sName, $GLOBALS)) { + global $$sName; + return $$sName; } - $sVarname=$oExpr->name; - global $$sVarname; - return $$sVarname; + return null; } + } \ No newline at end of file diff --git a/tests/php-unit-tests/unitary-tests/setup/modulediscovery/ModuleFileReaderTest.php b/tests/php-unit-tests/unitary-tests/setup/modulediscovery/ModuleFileReaderTest.php index b3b7c9941..7b0c52b93 100644 --- a/tests/php-unit-tests/unitary-tests/setup/modulediscovery/ModuleFileReaderTest.php +++ b/tests/php-unit-tests/unitary-tests/setup/modulediscovery/ModuleFileReaderTest.php @@ -27,7 +27,7 @@ class ModuleFileReaderTest extends ItopDataTestCase $this->assertEquals('Bridge - Request management ITIL + Incident management ITIL', $aRes[2]['label'] ?? null); } - /*public function testAllReadModuleFileConfiguration() + public function testAllReadModuleFileConfiguration() { $_SERVER=[ 'SERVER_NAME' => 'titi' @@ -54,7 +54,7 @@ class ModuleFileReaderTest extends ItopDataTestCase } $this->assertEquals([], $aErrors, var_export($aErrors, true)); - }*/ + } public static function ReadModuleFileConfigurationFileNameProvider() { diff --git a/tests/php-unit-tests/unitary-tests/sources/PhpParser/Evaluation/PhpExpressionEvaluatorTest.php b/tests/php-unit-tests/unitary-tests/sources/PhpParser/Evaluation/PhpExpressionEvaluatorTest.php index ed1d07fd7..1e722dc8f 100644 --- a/tests/php-unit-tests/unitary-tests/sources/PhpParser/Evaluation/PhpExpressionEvaluatorTest.php +++ b/tests/php-unit-tests/unitary-tests/sources/PhpParser/Evaluation/PhpExpressionEvaluatorTest.php @@ -10,6 +10,12 @@ class PhpExpressionEvaluatorTest extends ItopDataTestCase { private static $PRIVATE_STATIC_PROPERTY = 123; private const PRIVATE_CONSTANT = 123; + protected function tearDown(): void + { + parent::tearDown(); // TODO: Change the autogenerated stub + PhpExpressionEvaluator::GetInstance()->SetMode(PhpExpressionEvaluator::ITOP_ALGO); + } + public static function EvaluateExpressionProvider() { return [ 'Array: [1000 => "a"]' => ['sExpression' => '[1000 => "a"]'], @@ -19,7 +25,7 @@ class PhpExpressionEvaluatorTest extends ItopDataTestCase { 'BinaryOperator: false|true' => [ 'sExpression' => 'false|true'], 'BinaryOperator: false||true' => [ 'sExpression' => 'false||true'], 'BinaryOperator: false&&true' => [ 'sExpression' => 'false&&true'], - 'BinaryOperator: true&&true&&true&&false' => [ 'sExpression' => 'true&&true&&true&&false'], + 'BinaryOperator: true&&true&&true&&false' => [ 'sExpression' => 'true && true && true && false'], 'BinaryOperator: false&true' => [ 'sExpression' => 'false&true'], 'BinaryOperator: ! true' => [ 'sExpression' => '! true'], 'BinaryOperator: 10 * 5' => [ 'sExpression' => '10 * 5'], @@ -30,6 +36,7 @@ class PhpExpressionEvaluatorTest extends ItopDataTestCase { 'BinaryOperator: PHP_VERSION_ID != PHP_VERSION_ID' => [ 'sExpression' => 'PHP_VERSION_ID != PHP_VERSION_ID'], 'BitwiseNot: ~3' => ['sExpression' => '~3'], 'BitwiseXor: 3^2' => ['sExpression' => '3^2'], + 'BooleanAnd: true && false' => ['sExpression' => 'true && false'], 'Cast: (array)3' => ['sExpression' => '(array)3'], 'Cast: (bool)1' => ['sExpression' => '(bool)1'], 'Cast: (bool)0' => ['sExpression' => '(bool)0'], @@ -48,14 +55,21 @@ class PhpExpressionEvaluatorTest extends ItopDataTestCase { ], 'Coalesce: $oNullVar ?? 1' => ['sExpression' => '$oNullVar ?? 1', 1], 'Coalesce: $oNonNullVar ?? 1' => ['sExpression' => '$oNonNullVar ?? 1', 1], + 'Coalesce: $_SERVER["toto"] ?? 1' => ['sExpression' => '$_SERVER["toto"] ?? 1', "titi"], + 'Coalesce: $_SERVER["unknown_key"] ?? 1' => ['sExpression' => '$_SERVER["unknown_key"] ?? 1', 1], 'Coalesce: $oGlobalNonNullVar ?? 1' => ['sExpression' => '$oGlobalNonNullVar ?? 1', "a"], + 'Coalesce: $oGlobalNullVar ?? 1' => ['sExpression' => '$oGlobalNullVar ?? 1', 1], 'Concat: "a"."b"' => ['sExpression' => '"a"."b"'], 'ConstFetch: false' => [ 'sExpression' => 'false'], 'ConstFetch: (false)' => [ 'sExpression' => 'false'], 'ConstFetch: true' => [ 'sExpression' => 'true'], 'ConstFetch: (true)' => [ 'sExpression' => 'true'], + 'Equal: 1 == true' => [ 'sExpression' => '1 == true', true], + 'Equal: 1 == false' => [ 'sExpression' => '1 == false', false], 'FuncCall: function_exists(\'ldap_connect\')' => [ 'sExpression' => 'function_exists(\'ldap_connect\')'], 'FuncCall: function_exists(\'gabuzomeushouldnotexist\')' => [ 'sExpression' => 'function_exists(\'gabuzomeushouldnotexist\')'], + 'Identical: 1==="1"' => ['sExpression' => '1==="1"', false], + 'Identical: "1"==="1"' => ['sExpression' => '"1"==="1"', true], 'Isset: isset($oNonNullVar)' => ['sExpression' => 'isset($oNonNullVar)', false], 'Isset: isset($oGlobalNonNullVar)' => ['sExpression' => 'isset($oGlobalNonNullVar)', true], 'Isset: isset($a, $_SERVER)' => ['sExpression' => 'isset($a, $_SERVER)', false], @@ -85,6 +99,7 @@ class PhpExpressionEvaluatorTest extends ItopDataTestCase { 'Variable: $_SERVER' => ['sExpression' => '$_SERVER', ['toto' => 'titi']], 'Variable: $oNonNullVar' => ['sExpression' => '$oNonNullVar', null], 'Variable: $oGlobalNonNullVar' => ['sExpression' => '$oGlobalNonNullVar', "a"], + 'Variable: $oEvaluationFakeClass' => ['sExpression' => '$oEvaluationFakeClass', new EvaluationFakeClass()], ]; } @@ -116,6 +131,10 @@ class PhpExpressionEvaluatorTest extends ItopDataTestCase { { global $oGlobalNonNullVar; $oGlobalNonNullVar="a"; + + global $oGlobalNullVar; + $oGlobalNullVar=null; + $oNonNullVar="a"; $oNullVar=null; @@ -126,7 +145,8 @@ class PhpExpressionEvaluatorTest extends ItopDataTestCase { global $oEvaluationFakeClass; $oEvaluationFakeClass = new EvaluationFakeClass(); - $res = PhpExpressionEvaluator::GetInstance()->ParseAndEvaluateExpression($sExpression, $iMode); + PhpExpressionEvaluator::GetInstance()->SetMode($iMode); + $res = PhpExpressionEvaluator::GetInstance()->ParseAndEvaluateExpression($sExpression); if ($forced_expected === "NOTPROVIDED"){ $this->assertEquals($this->UnprotectedComputeExpression($sExpression), $res, $sExpression); } else {