Restrict array_key_exists optimization to integer and string keys (#130)

master
yavon007 3 weeks ago committed by GitHub
parent f9a843e11c
commit 88ba4780c9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 23
      phpunit/code/array-key-exists-optimization.php
  2. 33
      phpunit/src/ArrayKeyExistsOptimizationTest.php
  3. 5
      src/Optimizer/FuncCallOptimizer.php
  4. 90
      tests/compiler/stdlib/array-key-exists-key-types.phpt

@ -0,0 +1,23 @@
<?php
/**
* This file is part of TypePHP(AOT).
*
* @link https://www.swoole.com/aot/
* @contact service@swoole.com
*/
namespace KeyExistsOptimization;
function check(array $array, int $integer, string $string, mixed $mixed, bool $boolean, float $float): void
{
array_key_exists($integer, $array);
array_key_exists($string, $array);
array_key_exists($mixed, $array);
array_key_exists($boolean, $array);
array_key_exists($float, $array);
array_key_exists(null, $array);
array_key_exists([], $array);
array_key_exists(new \stdClass(), $array);
key_exists($mixed, $array);
$array->keyExists($mixed);
}

@ -0,0 +1,33 @@
<?php
/**
* This file is part of TypePHP(AOT).
*
* @link https://www.swoole.com/aot/
* @contact service@swoole.com
*/
use TypePhp\CompilerTest;
/**
* @internal
* @coversNothing
*/
final class ArrayKeyExistsOptimizationTest extends BaseTest
{
public function testOnlyIntegerAndStringKeysUseDirectLookup(): void
{
global $translator;
$compiler = CompilerTest::create(TYPEPHP_ROOT_PATH);
$translator = $compiler;
$source = TYPEPHP_ROOT_PATH . '/phpunit/code/array-key-exists-optimization.php';
$compiler->addFiles([$source]);
$compiler->prepareFile($source);
$code = file_get_contents($compiler->convertFile($source));
self::assertIsString($code);
self::assertSame(2, substr_count($code, '.offsetExists('));
self::assertSame(8, substr_count($code, 'php::call('));
self::assertStringNotContainsString('php::fn::array_key_exists(', $code);
}
}

@ -1177,6 +1177,11 @@ trait FuncCallOptimizer
if (!$this->hasOptimizerSafeReflectedArguments($n, $e, $c)) { if (!$this->hasOptimizerSafeReflectedArguments($n, $e, $c)) {
return false; return false;
} }
// offsetExists only matches PHP key semantics for integer/string keys.
// Other keys need Zend's conversions, diagnostics and TypeErrors.
if (!in_array($this->detectTypeOfExpr($e->args[0]->value), [Type::INT, Type::STR], true)) {
return false;
}
// The C++ receiver is PHP's second argument, but PHP still evaluates // The C++ receiver is PHP's second argument, but PHP still evaluates
// the key first. Resolve both in source order before rearranging them. // the key first. Resolve both in source order before rearranging them.
$key = $this->getArg($e, 0); $key = $this->getArg($e, 0);

@ -0,0 +1,90 @@
--TEST--
array_key_exists preserves key conversion, diagnostics and invalid-key errors
--FILE--
<?php
namespace KeyExistsKeys {
function lookup(mixed $key, array $array): void
{
try {
var_dump(array_key_exists($key, $array));
} catch (\Throwable $error) {
echo get_class($error), "\n";
}
}
function run(): void
{
$diagnostics = [];
set_error_handler(function (int $severity, string $message, string $file, int $line) use (&$diagnostics): bool {
$diagnostics[] = $severity;
return true;
});
lookup(false, [0 => null]);
lookup(false, ['' => true]);
lookup(true, [1 => null]);
lookup('1', [1 => null]);
lookup('01', [1 => null]);
var_dump(array_key_exists(1, [1 => null]), array_key_exists('1', [1 => null]));
var_dump(array_key_exists(false, [0 => null]));
lookup(1.5, [1 => null]);
var_dump(array_key_exists(1.5, [1 => null]));
var_dump($diagnostics === [E_DEPRECATED, E_DEPRECATED]);
$diagnostics = [];
lookup(null, ['' => null]);
var_dump(array_key_exists(null, ['' => null]));
var_dump(count($diagnostics) === (PHP_VERSION_ID >= 80500 ? 2 : 0));
$diagnostics = [];
$resource = fopen(__FILE__, 'r');
lookup($resource, [(int) $resource => null]);
var_dump($diagnostics === [E_WARNING]);
fclose($resource);
$diagnostics = [];
lookup([], ['Array' => null]);
lookup(new \stdClass(), []);
var_dump($diagnostics === []);
$array = [0 => null];
$key = \std::any(false);
var_dump(key_exists($key, $array), $array->keyExists($key));
var_dump(array_key_exists(array: $array, key: $key));
var_dump(array_key_exists(...[$key, $array]));
restore_error_handler();
}
}
namespace {
function main(): void
{
\KeyExistsKeys\run();
}
}
?>
--EXPECT--
bool(true)
bool(false)
bool(true)
bool(true)
bool(false)
bool(true)
bool(true)
bool(true)
bool(true)
bool(true)
bool(true)
bool(true)
bool(true)
bool(true)
bool(true)
bool(true)
TypeError
TypeError
bool(true)
bool(true)
bool(true)
bool(true)
bool(true)
Loading…
Cancel
Save