fix(optimizer): preserve function_exists runtime name semantics (#127) --skip-tests

master
yavon007 3 weeks ago committed by GitHub
parent f84d20e346
commit c7cdc8b23e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 29
      src/Optimizer/FuncCallOptimizer.php
  2. 68
      tests/compiler/stdlib/function-exists-literal-names.phpt

@ -1421,17 +1421,26 @@ trait FuncCallOptimizer
}
$funcName = $expr->args[0]->value;
if ($this->isScalarString($funcName)) {
$nameLower = strtolower(trim($funcName->value, '\\'));
$nativeFunction = $this->findNativeFunction($nameLower);
if ($nativeFunction) {
// A function whose ABI contains Native pointers is callable
// only from generated TypePHP C++. It has no Zend wrapper and
// therefore must remain invisible to function_exists().
return $this->functionRequiresNativeAbi($this->getFunction($nativeFunction))
? 'false'
: 'true';
// Runtime function names are absolute, case-insensitive strings.
// PHP accepts one leading slash, but neither namespace/import
// resolution nor trimming other slashes applies to these names.
$nameLower = strtolower($funcName->value);
if (str_starts_with($nameLower, '\\')) {
$nameLower = substr($nameLower, 1);
}
$funcName = $this->getLiteralString($nameLower);
$nativeFunction = $this->escapeNamespace($nameLower);
$this->checkFunction($nativeFunction);
if ($this->hasFunction($nativeFunction)) {
$function = $this->getFunction($nativeFunction);
// Escaped C++ names are not an exact PHP-name lookup: a
// namespace separator and literal underscores can collide.
if (!$function->method && strtolower($function->getNamespacedName()) === $nameLower) {
// Native-pointer ABIs have no Zend wrapper and must
// remain invisible to function_exists().
return $this->functionRequiresNativeAbi($function) ? 'false' : 'true';
}
}
$funcName = $this->getLiteralString($funcName->value);
return 'php::fn::function_exists(' . $funcName . ')';
}
return 'php::fn::function_exists(' . $this->parseIdentifier($funcName) . ')';

@ -0,0 +1,68 @@
--TEST--
function_exists literal names follow runtime lookup rather than source call resolution
--FILE--
<?php
namespace ExistsLibrary {
function target(): void {}
}
namespace ExistsScope {
use function ExistsLibrary\target as imported_target;
use ExistsLibrary as LibraryAlias;
function local_target(): void {}
function check(): void
{
echo json_encode([
function_exists('local_target'),
function_exists('imported_target'),
function_exists('LibraryAlias\\target'),
function_exists('ExistsLibrary\\target'),
function_exists('\\ExistsLibrary\\target'),
function_exists('ExistsScope\\local_target'),
]), "\n";
}
}
namespace {
function literal_target(): void {}
function main(): void
{
echo json_encode([
function_exists('literal_target'),
function_exists('LITERAL_TARGET'),
function_exists('\\literal_target'),
function_exists('\\\\literal_target'),
function_exists('literal_target\\'),
function_exists('\\literal_target\\'),
]), "\n";
echo json_encode([
function_exists('strlen'),
function_exists('STRLEN'),
function_exists('\\strlen'),
function_exists('\\\\strlen'),
function_exists('strlen\\'),
function_exists('\\strlen\\'),
]), "\n";
echo json_encode([
function_exists('ExistsLibrary\\target'),
function_exists('EXISTSLIBRARY\\TARGET'),
function_exists('\\ExistsLibrary\\target'),
function_exists('\\\\ExistsLibrary\\target'),
function_exists('ExistsLibrary\\target\\'),
function_exists('ExistsLibrary_target'),
function_exists('ExistsLibrary__target'),
function_exists(''),
function_exists('\\'),
]), "\n";
ExistsScope\check();
}
}
?>
--EXPECT--
[true,true,true,false,false,false]
[true,true,true,false,false,false]
[true,true,true,false,false,false,false,false,false]
[false,false,false,true,true,true]
Loading…
Cancel
Save