修复常量字面量运算溢出未转浮点的问题

pull/45/head
韩天峰 3 weeks ago
parent 09b99e357d
commit f2c9309857
  1. 2
      src/CompilerBase.php
  2. 90
      src/Parser/BinaryOpTrait.php
  3. 7
      src/Parser/UnaryExpressionTrait.php
  4. 25
      tests/compiler/constant-int-arithmetic-overflow.phpt

@ -243,9 +243,11 @@ class CompilerBase implements PropertyAccessContext
protected array $propMap = [];
protected array $zendTypeMap = [
'int' => Type::INT,
'integer' => Type::INT,
'float' => Type::FLOAT,
'double' => Type::FLOAT,
'bool' => Type::BOOL,
'boolean' => Type::BOOL,
'false' => Type::BOOL,
'true' => Type::BOOL,
'void' => Type::VOID,

@ -132,9 +132,99 @@ trait BinaryOpTrait
return 'php::fn::mod(' . $leftExpr . ', ' . $rightExpr . ')';
}
$folded = $this->tryFoldConstantIntArithmetic($left, $right, $op);
if ($folded !== null) {
return $folded;
}
return '((' . $leftExpr . ') ' . $op . ' (' . $rightExpr . '))';
}
/**
* Fold constant int arithmetic that would overflow int64 in generated C++.
*
* PHP promotes an overflowing integer operation to float; raw C++ constant
* expressions overflow at compile time (UB) and wrap instead. When both
* operands are compile-time int constants and the PHP result is no longer
* an int, emit the promoted float literal instead of the raw C++ expression.
* With native_types the intentional wrap semantics are kept.
*/
protected function tryFoldConstantIntArithmetic(NodeAbstract $left, NodeAbstract $right, string $op): ?string
{
if ($this->nativeTypes) {
return null;
}
if (!in_array($op, ['+', '-', '*', '/'], true)) {
return null;
}
$leftValue = $this->constantIntValue($left);
$rightValue = $this->constantIntValue($right);
if ($leftValue === null || $rightValue === null) {
return null;
}
if ($op === '/' && $rightValue === 0) {
// Division by zero is rejected by guardLiteralDivisionByZero / runtime.
return null;
}
// PHP itself promotes overflowing int arithmetic to float, which is
// exactly the semantics we want for the generated literal.
$result = match ($op) {
'+' => $leftValue + $rightValue,
'-' => $leftValue - $rightValue,
'*' => $leftValue * $rightValue,
'/' => $leftValue / $rightValue,
};
if (is_int($result)) {
// No overflow — keep the plain C++ expression.
return null;
}
return $this->genFloatLiteral($result);
}
/**
* Resolve a compile-time integer constant value, or null when the
* expression is not a statically known int constant.
*/
protected function constantIntValue(NodeAbstract $expr): ?int
{
if ($expr instanceof Node\Scalar\Int_) {
return $expr->value;
}
if ($expr instanceof Node\Expr\UnaryPlus) {
return $this->constantIntValue($expr->expr);
}
if ($expr instanceof Node\Expr\UnaryMinus) {
$value = $this->constantIntValue($expr->expr);
if ($value === null || $value === PHP_INT_MIN) {
// -PHP_INT_MIN promotes to float in PHP; not an int constant.
return null;
}
return -$value;
}
if ($expr instanceof Node\Expr\ConstFetch) {
$name = strtolower($expr->name->toString());
return match ($name) {
'php_int_max' => PHP_INT_MAX,
'php_int_min' => PHP_INT_MIN,
default => null,
};
}
return null;
}
protected function genFloatLiteral(float $value): string
{
$text = sprintf('%.17g', $value);
// Make sure the literal is parsed as a C++ double.
if (!str_contains($text, '.') && !str_contains(strtolower($text), 'e')) {
$text .= '.0';
}
return $text;
}
protected function shouldMaterializeOrderedOperand(NodeAbstract $expr): bool
{
if ($expr instanceof Expr\BinaryOp) {

@ -80,6 +80,13 @@ trait UnaryExpressionTrait
if ($type === Type::DECIMAL) {
return 'php::Decimal::neg(' . $this->parseExprAsValue($expr->expr) . ')';
}
if (!$this->nativeTypes && $type === Type::INT) {
$value = $this->constantIntValue($expr->expr);
if ($value === PHP_INT_MIN) {
// -PHP_INT_MIN overflows int64 and promotes to float in PHP.
return $this->genFloatLiteral(-(float) PHP_INT_MIN);
}
}
$code = $this->parseExprAsValue($expr->expr);
return '-' . $code;

@ -0,0 +1,25 @@
--TEST--
Constant integer arithmetic overflow promotes to float like PHP
--FILE--
<?php
declare(strict_types=1);
function main(): void
{
var_dump(PHP_INT_MAX + 1);
var_dump(PHP_INT_MIN - 1);
var_dump(PHP_INT_MAX * 2);
var_dump(PHP_INT_MIN / -1);
var_dump(1 + 2);
var_dump(PHP_INT_MAX + 0);
var_dump(-PHP_INT_MIN);
}
?>
--EXPECTF--
float(9.223372036854776E+18)
float(-9.223372036854776E+18)
float(1.8446744073709552E+19)
float(9.223372036854776E+18)
int(3)
int(9223372036854775807)
float(9.223372036854776E+18)
Loading…
Cancel
Save