fix(parser): handle PHP array overflow in compound assignments and improve method name parsing

- Change temporary variable type to Variant for PHP arrays to prevent undefined C++ behavior
- Add methodNameToStr helper that treats 'self' and 'static' as ordinary member names
- Replace identifierToStr with methodNameToStr for method calls to respect lexical rules
- Update nullsafe access trait to use proper method name parsing
- Add test case for array element compound arithmetic overflow handling
- Add test case for nullsafe chain preserving typed method returns
master v0.6.1
韩天峰 5 days ago
parent 70d13a3601
commit 65d3710a61
  1. 9
      src/CompilerBase.php
  2. 6
      src/Parser/AssignOpTrait.php
  3. 12
      src/Parser/MethodCallTrait.php
  4. 4
      src/Parser/NullsafeAccessTrait.php
  5. 23
      tests/compiler/array/compound-overflow.phpt
  6. 37
      tests/compiler/nullsafe/nullsafe-chain-typed-return.phpt

@ -4631,6 +4631,15 @@ class CompilerBase implements PropertyAccessContext
return $this->identifierToStr($node, $require, $literal); return $this->identifierToStr($node, $require, $literal);
} }
/**
* Method identifiers share the same lexical rules as property names:
* `self` and `static` are ordinary member names here, not class keywords.
*/
protected function methodNameToStr(NodeAbstract $node, bool $require = true, bool $literal = false): string
{
return $this->propertyNameToStr($node, $require, $literal);
}
protected function requireVar($node, string $var): void protected function requireVar($node, string $var): void
{ {
if (!$this->hasVar($var)) { if (!$this->hasVar($var)) {

@ -897,7 +897,11 @@ trait AssignOpTrait
$type = $this->detectVarType($node->var); $type = $this->detectVarType($node->var);
$rightType = $this->detectTypeOfExpr($node->expr); $rightType = $this->detectTypeOfExpr($node->expr);
$tmpVar = $this->genTmpVarName(); $tmpVar = $this->genTmpVarName();
$this->addLocalVar($tmpVar, $rightType); // PHP arrays are dynamically typed even when SSA can currently
// infer an element as int. Keep the compound result in Variant so
// Zend arithmetic promotes overflowing integers to float instead
// of evaluating a signed C++ expression with undefined behavior.
$this->addLocalVar($tmpVar, Type::VAR);
$dim = $this->parseIdentifier($node->var->dim); $dim = $this->parseIdentifier($node->var->dim);
$readVar = $this->parseArrayDimFetchRead($node->var); $readVar = $this->parseArrayDimFetchRead($node->var);
$binaryOp = $this->removeAssignOp($op); $binaryOp = $this->removeAssignOp($op);

@ -322,7 +322,7 @@ trait MethodCallTrait
// object's parent. Resolve the method there; the receiver below is // object's parent. Resolve the method there; the receiver below is
// selected from the current static/instance context. // selected from the current static/instance context.
$methodPtr = 'php::getMethod(' . $this->getClassEntryPtr($parentClass) . ', ' $methodPtr = 'php::getMethod(' . $this->getClassEntryPtr($parentClass) . ', '
. $this->identifierToStr($expr->name) . ')'; . $this->methodNameToStr($expr->name) . ')';
// A dynamic parent call made from a static method cannot have an // A dynamic parent call made from a static method cannot have an
// object receiver. Zend validates the resolved method at runtime. // object receiver. Zend validates the resolved method at runtime.
$staticCall = (bool) ($this->methodDef->flags & Modifiers::STATIC); $staticCall = (bool) ($this->methodDef->flags & Modifiers::STATIC);
@ -437,7 +437,7 @@ trait MethodCallTrait
} }
$magicMethod = false; $magicMethod = false;
$method = $this->identifierToStr($expr->name, literal: true); $method = $this->methodNameToStr($expr->name, literal: true);
$pythonFacadeCall = $this->parsePythonNativeFacadeMethodCall($expr, $object); $pythonFacadeCall = $this->parsePythonNativeFacadeMethodCall($expr, $object);
if ($pythonFacadeCall !== null) { if ($pythonFacadeCall !== null) {
@ -795,9 +795,9 @@ trait MethodCallTrait
goto _do_call; goto _do_call;
} }
if ($this->getVarType($var) == Type::OBJECT) { if ($this->getVarType($var) == Type::OBJECT) {
$fn = 'php::concat({' . $var . '.getClassName(), "::", ' . $this->identifierToStr($expr->name) . '})'; $fn = 'php::concat({' . $var . '.getClassName(), "::", ' . $this->methodNameToStr($expr->name) . '})';
} else { } else {
$fn = 'php::concat({' . $this->identifierToStr($expr->class) . ', "::", ' . $this->identifierToStr($expr->name) . '})'; $fn = 'php::concat({' . $this->identifierToStr($expr->class) . ', "::", ' . $this->methodNameToStr($expr->name) . '})';
} }
$placeHolder = $fn; $placeHolder = $fn;
} elseif ($this->isNameExpr($expr->class) and $class === 'static') { } elseif ($this->isNameExpr($expr->class) and $class === 'static') {
@ -808,7 +808,7 @@ trait MethodCallTrait
); );
} }
$method = $this->parseIdentifier($expr->name); $method = $this->parseIdentifier($expr->name);
$methodPtr = $this->identifierToStr($expr->name, literal: true); $methodPtr = $this->methodNameToStr($expr->name, literal: true);
$fn = Symbol::getCalledCe() . ', php::getMethod(' . Symbol::getCalledCe() . ', ' . $methodPtr . ')'; $fn = Symbol::getCalledCe() . ', php::getMethod(' . Symbol::getCalledCe() . ', ' . $methodPtr . ')';
if ($this->debug) { if ($this->debug) {
$this->context->beforeStmtLines[] = $this->formatCppLineComment( $this->context->beforeStmtLines[] = $this->formatCppLineComment(
@ -882,7 +882,7 @@ trait MethodCallTrait
$fn = $this->getLiteralString($class . '::' . $method); $fn = $this->getLiteralString($class . '::' . $method);
$placeHolder = $this->genArray($callScope); $placeHolder = $this->genArray($callScope);
} else { } else {
$fn = 'php::concat({' . $this->identifierToStr($expr->class) . ', "::", ' . $this->identifierToStr($expr->name) . '})'; $fn = 'php::concat({' . $this->identifierToStr($expr->class) . ', "::", ' . $this->methodNameToStr($expr->name) . '})';
$placeHolder = $fn; $placeHolder = $fn;
} }

@ -54,13 +54,13 @@ trait NullsafeAccessTrait
$list[] = ['property', $this->propertyNameToStr($expr->name, literal: true), $expr, true]; $list[] = ['property', $this->propertyNameToStr($expr->name, literal: true), $expr, true];
$expr = $expr->var; $expr = $expr->var;
} elseif ($expr instanceof Expr\NullsafeMethodCall) { } elseif ($expr instanceof Expr\NullsafeMethodCall) {
$list[] = ['method', $this->identifierToStr($expr->name, literal: true), $expr->args, true, $expr]; $list[] = ['method', $this->methodNameToStr($expr->name, literal: true), $expr->args, true, $expr];
$expr = $expr->var; $expr = $expr->var;
} elseif ($expr instanceof Expr\PropertyFetch) { } elseif ($expr instanceof Expr\PropertyFetch) {
$list[] = ['property', $this->propertyNameToStr($expr->name, literal: true), $expr, false]; $list[] = ['property', $this->propertyNameToStr($expr->name, literal: true), $expr, false];
$expr = $expr->var; $expr = $expr->var;
} elseif ($expr instanceof Expr\MethodCall) { } elseif ($expr instanceof Expr\MethodCall) {
$list[] = ['method', $this->identifierToStr($expr->name, literal: true), $expr->args, false, $expr]; $list[] = ['method', $this->methodNameToStr($expr->name, literal: true), $expr->args, false, $expr];
$expr = $expr->var; $expr = $expr->var;
} else { } else {
if ($this->isVarExpr($expr)) { if ($this->isVarExpr($expr)) {

@ -0,0 +1,23 @@
--TEST--
Array element compound arithmetic promotes overflowing integers to float
--FILE--
<?php
declare(strict_types=1);
function main(): void
{
$maximum = PHP_INT_MAX;
$large = intdiv(PHP_INT_MAX, 2) + 1;
$values = ['add' => $maximum, 'multiply' => $large];
$values['add'] += 1;
$values['multiply'] *= 2;
var_dump($values['add'], $values['multiply']);
echo gettype($values['add']), ',', gettype($values['multiply']), "\n";
}
?>
--EXPECT--
float(9.223372036854776E+18)
float(9.223372036854776E+18)
double,double

@ -0,0 +1,37 @@
--TEST--
Nullsafe chain preserves typed method returns and member names
--FILE--
<?php
declare(strict_types=1);
final class NullsafeTypedReturnLeaf
{
public string $value = 'forward';
}
final class NullsafeTypedReturnNode
{
public NullsafeTypedReturnLeaf $leaf;
public function __construct()
{
$this->leaf = new NullsafeTypedReturnLeaf();
}
public function self(): NullsafeTypedReturnNode
{
return $this;
}
}
function main(): void
{
$target = new NullsafeTypedReturnNode();
$weak = WeakReference::create($target);
echo $target->self()->leaf->value, "\n";
echo $weak->get()?->self()?->leaf->value, "\n";
}
?>
--EXPECT--
forward
forward
Loading…
Cancel
Save