fix(compiler): 修复多处运算符和表达式编译副作用处理

pull/13/head
韩天峰 2 months ago
parent aab412084f
commit b6d0488ea6
  1. 40
      src/Php/CompilerBase.php
  2. 10
      src/Php/Parser/AssignOpTrait.php
  3. 33
      tests/aot/array/foreach-list-write-targets.phpt
  4. 16
      tests/aot/arrow_fn/static-arrow-capture.phpt
  5. 32
      tests/aot/basic/clone-expression-side-effects.phpt
  6. 23
      tests/aot/basic/error-suppress-restore.phpt
  7. 24
      tests/aot/basic/globals-assign-ref.phpt
  8. 21
      tests/aot/basic/globals-ref-arg.phpt
  9. 26
      tests/aot/basic/include-expression-side-effects.phpt
  10. 3
      tests/aot/basic/test_include_return.inc
  11. 33
      tests/aot/class/instanceof-dynamic-expression.phpt
  12. 37
      tests/aot/coalesce/assign-coalesce-complex-targets.phpt
  13. 68
      tests/aot/nullsafe/nullsafe-chain-middle-null-lazy.phpt
  14. 36
      tests/aot/static/static-vars-dynamic-init-once.phpt

@ -756,26 +756,7 @@ class CompilerBase extends \PhpAot\Core\Translator implements PropertyAccessCont
protected function genExtraNamedVariadicArgs(string $var): string
{
$keyVar = $var . '_named_key';
$valueVar = $var . '_named_value';
$code = $this->getIndent() . 'if (zend_array *named_args = php::getCallExtraNamedArgs()) {' . PHP_EOL;
$this->indentLevel++;
$code .= $this->getIndent() . 'zend_string *' . $keyVar . ';' . PHP_EOL;
$code .= $this->getIndent() . 'zval *' . $valueVar . ';' . PHP_EOL;
$code .= $this->getIndent() . 'ZEND_HASH_MAP_FOREACH_STR_KEY_VAL(named_args, ' . $keyVar . ', ' . $valueVar . ') {' . PHP_EOL;
$this->indentLevel++;
$code .= $this->getIndent() . 'if (' . $keyVar . ') {' . PHP_EOL;
$this->indentLevel++;
$code .= $this->getIndent() . $var . '.set(' . $keyVar . ', php::Variant(' . $valueVar . ', php::Ctor::CopyRef));' . PHP_EOL;
$this->indentLevel--;
$code .= $this->getIndent() . '}' . PHP_EOL;
$this->indentLevel--;
$code .= $this->getIndent() . '} ZEND_HASH_FOREACH_END();' . PHP_EOL;
$this->indentLevel--;
$code .= $this->getIndent() . '}' . PHP_EOL;
return $code;
return $this->getIndent() . 'php::appendCallExtraNamedArgs(' . $var . ');' . PHP_EOL;
}
public function writeFile(string $file, string $content): void
@ -4466,13 +4447,18 @@ class CompilerBase extends \PhpAot\Core\Translator implements PropertyAccessCont
protected function parseInstanceof(Expr\Instanceof_ $expr): string
{
$this->assertExprCanBeUsedAsValue($expr->expr, 'instanceof operand');
$value = $this->parseExprAsValue($expr->expr);
if ($this->isNameExpr($expr->class)) {
$value = $this->parseExprAsValue($expr->expr);
$className = $this->getNamespacedClassName($this->parseIdentifier($expr->class));
$className = $this->getClassEntryPtr($className);
return 'php::instanceOf(' . $value . ', ' . $className . ')';
} else {
return 'php::instanceOf(' . $value . ', ' . $this->identifierToStr($expr->class) . ')';
[$value, $beforeStmts, $afterStmts] = $this->parseExprWithCapturedStmts($expr->expr);
$tmpVar = $this->addTmpVar(self::TYPE_VAR);
$this->appendCapturedStmtLinesToContext($beforeStmts);
$this->context->beforeStmtLines[] = $tmpVar . ' = ' . $value . ';';
$this->appendCapturedStmtLinesToContext($afterStmts);
return 'php::instanceOf(' . $tmpVar . ', ' . $this->identifierToStr($expr->class) . ')';
}
}
@ -4768,12 +4754,10 @@ class CompilerBase extends \PhpAot\Core\Translator implements PropertyAccessCont
protected function parseExit(Expr\Exit_ $node): string
{
if (!$node->expr) {
return 'std::exit(0)';
return 'php::aotExit()';
}
$status = $this->parseExprAsValue($node->expr);
return '([&]() -> php::Var { php::Var exit_status = ' . $status . '; '
. 'if (exit_status.isInt()) { std::exit(exit_status.toInt()); } '
. 'php::echo(php::toString(exit_status)); std::exit(0); return php::null; })()';
return 'php::aotExit(' . $status . ')';
}
protected function getFixedObjectPropDefaultValue(PropertyDef $def): ?string
@ -6453,9 +6437,7 @@ class CompilerBase extends \PhpAot\Core\Translator implements PropertyAccessCont
if ($type != self::TYPE_VAR) {
$this->fatalError($expr, 'Can only throw objects');
}
$tmp = $this->genTmpVarName();
$this->addLocalVar($tmp, self::TYPE_VAR);
return '([&]() -> php::Var { ' . $tmp . ' = ' . $ex . '; if (!' . $tmp . '.isObject()) { php::throwError("Can only throw objects"); } return php::throwException(php::Object(' . $tmp . ')); })()';
return 'php::throwValue(' . $ex . ')';
}
protected function parseTryCatch(mixed $v): string

@ -22,6 +22,13 @@ trait AssignOpTrait
if ($this->isPropertyFetch($left)) {
return $this->parseAssignPropertyArrayDim($left, $right);
}
if ($this->isVarExpr($left->var) && $left->var->name === 'GLOBALS') {
$target = $this->parseGlobalsArrayDimFetch($left);
$value = $this->parseExprAsValue($right);
$tmp = $this->genTmpVarName();
$this->addLocalVar($tmp, self::TYPE_VAR);
return '((' . $tmp . ' = ' . $value . ', ' . $target . ' = ' . $tmp . '), ' . $tmp . ')';
}
$oriInAssignExpr = $this->context->inAssignExpr;
$this->context->inAssignExpr = true;
$array = $this->parseIdentifier($left->var);
@ -394,6 +401,9 @@ trait AssignOpTrait
$this->convertExprType($expr, $type, $rightType) . ';';
}
if ($this->isVarExpr($node->var->var) && $node->var->var->name === 'GLOBALS') {
return $var . ' = ' . $tmpVar;
}
return $this->parseArrayDimStore($node->var->var, $dim, $tmpVar);
}

@ -0,0 +1,33 @@
--TEST--
foreach list destructuring can write into object properties and array dimensions
--FILE--
<?php
class ForeachListWriteBox
{
public $name = '';
public $value = 0;
}
function main(): void
{
$rows = [
['first', 10],
['second', 20],
];
$box = new ForeachListWriteBox();
$out = [];
foreach ($rows as [$box->name, $out['value']]) {
$box->value += $out['value'];
var_dump($box->name, $out['value'], $box->value);
}
}
?>
--EXPECT--
string(5) "first"
int(10)
int(10)
string(6) "second"
int(20)
int(30)

@ -0,0 +1,16 @@
--TEST--
static arrow function captures local variables by value
--FILE--
<?php
function main(): void
{
$factor = 3;
$map = static fn (int $value): int => $value * $factor;
$factor = 10;
var_dump($map(4));
}
?>
--EXPECT--
int(12)

@ -0,0 +1,32 @@
--TEST--
clone operand expression is evaluated once and __clone runs
--FILE--
<?php
class CloneSideEffect
{
public int $value = 1;
public function __clone()
{
echo "__clone\n";
$this->value++;
}
}
function make_clone_source(): CloneSideEffect
{
echo "make\n";
return new CloneSideEffect();
}
function main(): void
{
$copy = clone make_clone_source();
var_dump($copy->value);
}
?>
--EXPECT--
make
__clone
int(2)

@ -0,0 +1,23 @@
--TEST--
Error suppression should restore error_reporting after expression evaluation
--FILE--
<?php
function suppressed_reporting_level(): int
{
return error_reporting();
}
function main(): void
{
$before = error_reporting();
$inside = @suppressed_reporting_level();
$after = error_reporting();
var_dump($inside === $before);
var_dump($after === $before);
}
?>
--EXPECT--
bool(false)
bool(true)

@ -0,0 +1,24 @@
--TEST--
$GLOBALS array assignment and reference parameter update global slot
--FILE--
<?php
function globals_assign_ref_inc(&$value): void
{
$value++;
}
function main(): void
{
$GLOBALS['aot_globals_assign_ref_value'] = 40;
$GLOBALS['aot_globals_assign_ref_value'] += 1;
globals_assign_ref_inc($GLOBALS['aot_globals_assign_ref_value']);
global $aot_globals_assign_ref_value;
var_dump($aot_globals_assign_ref_value);
var_dump($GLOBALS['aot_globals_assign_ref_value']);
}
?>
--EXPECT--
int(42)
int(42)

@ -0,0 +1,21 @@
--TEST--
$GLOBALS array element can be passed to reference parameter
--FILE--
<?php
function globals_ref_inc(&$value): void
{
$value++;
}
function main(): void
{
global $aot_globals_ref_value;
$aot_globals_ref_value = 41;
globals_ref_inc($GLOBALS['aot_globals_ref_value']);
var_dump($aot_globals_ref_value);
}
?>
--EXPECT--
int(42)

@ -0,0 +1,26 @@
--TEST--
include path expression side effects and return value
--FILE--
<?php
function include_target(string $name): string
{
echo "target:$name\n";
return __DIR__ . '/' . $name;
}
function main(): void
{
$ret = include include_target('test_include_return.inc');
var_dump($ret);
$ret = include_once include_target('test_include_return.inc');
var_dump($ret);
}
?>
--EXPECT--
target:test_include_return.inc
included:test_include_return.inc
int(123)
target:test_include_return.inc
bool(true)

@ -0,0 +1,3 @@
<?php
echo "included:" . basename(__FILE__) . "\n";
return 123;

@ -0,0 +1,33 @@
--TEST--
instanceof dynamic class expression side effects
--FILE--
<?php
interface InstanceofMarker {}
class InstanceofSubject implements InstanceofMarker {}
function make_instanceof_subject(): object
{
echo "object\n";
return new InstanceofSubject();
}
function make_instanceof_class(string $class): string
{
echo "class:$class\n";
return $class;
}
function main(): void
{
var_dump(make_instanceof_subject() instanceof (make_instanceof_class(InstanceofMarker::class)));
var_dump(make_instanceof_subject() instanceof (make_instanceof_class(stdClass::class)));
}
?>
--EXPECT--
object
class:InstanceofMarker
bool(true)
object
class:stdClass
bool(false)

@ -0,0 +1,37 @@
--TEST--
null coalescing assignment with object property and array dim targets
--FILE--
<?php
class CoalesceBox
{
public $value = null;
public array $items = [];
}
function make_default(string $label): string
{
echo "default:$label\n";
return $label;
}
function main(): void
{
$box = new CoalesceBox();
$box->value ??= make_default('prop');
$box->value ??= make_default('prop-skip');
var_dump($box->value);
$box->items['name'] ??= make_default('array');
$box->items['name'] ??= make_default('array-skip');
var_dump($box->items);
}
?>
--EXPECT--
default:prop
string(4) "prop"
default:array
array(1) {
["name"]=>
string(5) "array"
}

@ -0,0 +1,68 @@
--TEST--
Nullsafe chain should stop at middle null and skip later arguments
--FILE--
<?php
class NullsafeChainLeaf
{
public function value(string $arg): string
{
echo "leaf:$arg\n";
return $arg;
}
}
class NullsafeChainMiddle
{
public function __construct(private ?NullsafeChainLeaf $leaf)
{
}
public function leaf(string $arg): ?NullsafeChainLeaf
{
echo "middle:$arg\n";
return $this->leaf;
}
}
class NullsafeChainRoot
{
public function __construct(private ?NullsafeChainMiddle $middle)
{
}
public function middle(string $arg): ?NullsafeChainMiddle
{
echo "root:$arg\n";
return $this->middle;
}
}
function make_arg(string $name): string
{
echo "arg:$name\n";
return $name;
}
function main(): void
{
$root = new NullsafeChainRoot(new NullsafeChainMiddle(null));
var_dump($root?->middle(make_arg('root'))?->leaf(make_arg('middle'))?->value(make_arg('leaf')));
$root = new NullsafeChainRoot(new NullsafeChainMiddle(new NullsafeChainLeaf()));
var_dump($root?->middle(make_arg('root2'))?->leaf(make_arg('middle2'))?->value(make_arg('leaf2')));
}
?>
--EXPECT--
arg:root
root:root
arg:middle
middle:middle
NULL
arg:root2
root:root2
arg:middle2
middle:middle2
arg:leaf2
leaf:leaf2
string(5) "leaf2"

@ -0,0 +1,36 @@
--TEST--
static variable dynamic initializer runs only once
--FILE--
<?php
function make_static_items(int $seed): array
{
echo "init:$seed\n";
return [$seed];
}
function use_static_items(int $seed): void
{
static $items = make_static_items($seed);
var_dump($items);
$items[] = $seed;
}
function main(): void
{
use_static_items(10);
use_static_items(20);
}
?>
--EXPECT--
init:10
array(1) {
[0]=>
int(10)
}
array(2) {
[0]=>
int(10)
[1]=>
int(10)
}
Loading…
Cancel
Save