From f2e063b41d7f841789b78bc3681f25225d5ac78a Mon Sep 17 00:00:00 2001 From: Yurun Date: Wed, 17 Jun 2026 13:34:16 +0800 Subject: [PATCH 1/7] =?UTF-8?q?fix(compiler):=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E5=91=BD=E5=90=8D=E7=A9=BA=E9=97=B4=E5=86=85=E7=B1=BB=E5=90=8D?= =?UTF-8?q?=E8=A7=A3=E6=9E=90=E5=8F=8Ause=E5=88=AB=E5=90=8D=E6=9F=A5?= =?UTF-8?q?=E6=89=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Php/CompilerBase.php | 11 +++-- tests/aot/namespace/ns-same-as-class.phpt | 52 +++++++++++++++++++++++ 2 files changed, 57 insertions(+), 6 deletions(-) create mode 100644 tests/aot/namespace/ns-same-as-class.phpt diff --git a/src/Php/CompilerBase.php b/src/Php/CompilerBase.php index 0172422f..91c678e4 100644 --- a/src/Php/CompilerBase.php +++ b/src/Php/CompilerBase.php @@ -4732,8 +4732,10 @@ class CompilerBase extends \PhpAot\Core\Translator $this->fatalError($expr, 'Cannot access parent:: when current class does not extend any class'); } $class = $this->classDef->extends; + } else { + $class = $this->getNamespacedClassName($class); } - $nativeProperty = $this->findNativeProperty($expr, $propertyName, $class, $this->namespace, true); + $nativeProperty = $this->findNativeProperty($expr, $propertyName, $class, true); if ($nativeProperty) { $expr->setAttribute('nativeProperty', $nativeProperty); return $nativeProperty; @@ -4745,13 +4747,10 @@ class CompilerBase extends \PhpAot\Core\Translator /** * @param NodeAbstract $expr 仅用于输出错误日志 */ - protected function findNativeProperty(NodeAbstract $expr, string $property, string $class, string $namespace = '', bool $static = false): ?string + protected function findNativeProperty(NodeAbstract $expr, string $property, string $class, bool $static = false): ?string { $findClass = $class; - if ($namespace) { - $findClass = $namespace . '\\' . $class; - } - $scope = $this->class ? ltrim($namespace . '\\' . $class, '\\') : ''; + $scope = $this->class ? $class : ''; $propertyDef = null; $classDef = null; while (true) { diff --git a/tests/aot/namespace/ns-same-as-class.phpt b/tests/aot/namespace/ns-same-as-class.phpt new file mode 100644 index 00000000..9e2fbd7b --- /dev/null +++ b/tests/aot/namespace/ns-same-as-class.phpt @@ -0,0 +1,52 @@ +--TEST-- +use class A\B (without alias) inside namespace A\B, call B::$v +--FILE-- + +--EXPECT-- +int(123) +int(123) +int(123) +int(123) From a36547467498483a5a59773653fbbfac5554119e Mon Sep 17 00:00:00 2001 From: Yurun Date: Wed, 17 Jun 2026 19:37:26 +0800 Subject: [PATCH 2/7] =?UTF-8?q?fix(tests):=20=E4=BF=AE=E5=A4=8D=20Windows?= =?UTF-8?q?=20=E6=B5=8B=E8=AF=95=E5=BC=B9=E5=87=BA=20UAC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/aot/basic/{update-object-arg.phpt => object-arg.phpt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/aot/basic/{update-object-arg.phpt => object-arg.phpt} (100%) diff --git a/tests/aot/basic/update-object-arg.phpt b/tests/aot/basic/object-arg.phpt similarity index 100% rename from tests/aot/basic/update-object-arg.phpt rename to tests/aot/basic/object-arg.phpt From 30ef9352a245f5c9052cc832c33c2ea39f0c7ba3 Mon Sep 17 00:00:00 2001 From: Yurun Date: Wed, 17 Jun 2026 19:52:08 +0800 Subject: [PATCH 3/7] =?UTF-8?q?fix(const):=20=E4=BF=AE=E5=A4=8D=E5=B8=B8?= =?UTF-8?q?=E9=87=8F=E5=90=8D=E4=B8=8E=E5=8F=98=E9=87=8F=E5=90=8D=E5=86=B2?= =?UTF-8?q?=E7=AA=81=E9=97=AE=E9=A2=98=E3=80=81=E5=B8=B8=E9=87=8F=E5=90=8D?= =?UTF-8?q?=E5=A4=A7=E5=B0=8F=E5=86=99=E6=95=8F=E6=84=9F=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Php/CompilerBase.php | 9 +++++---- src/Php/Generator/Utils.php | 5 +++++ tests/aot/const/const-same-name.phpt | 15 +++++++++++++++ tests/aot/const/const-var-same-name.phpt | 15 +++++++++++++++ 4 files changed, 40 insertions(+), 4 deletions(-) create mode 100644 tests/aot/const/const-same-name.phpt create mode 100644 tests/aot/const/const-var-same-name.phpt diff --git a/src/Php/CompilerBase.php b/src/Php/CompilerBase.php index 91c678e4..569da39b 100644 --- a/src/Php/CompilerBase.php +++ b/src/Php/CompilerBase.php @@ -143,6 +143,7 @@ class CompilerBase extends \PhpAot\Core\Translator public const string DYNAMIC_CALLED_CLASS = '__dynamic_called_class__'; public const string STATIC_VAR = '_static_var_'; public const string GLOBAL_VAR = '_global_var_'; + public const string CONST_VAR = '_const_var_'; public const string OBJECT_PROP = '_object_prop_'; public const string CLASS_MAP = 'class_map'; public const string FUNC_MAP = 'func_map'; @@ -5021,22 +5022,22 @@ class CompilerBase extends \PhpAot\Core\Translator $constInfo->type = $this->detectStrValueType($value); $constInfo->namespace = $this->namespace; $constInfo->name = $name; - $this->constants[$this->escapeNamespace($name)] = $constInfo; + $this->constants[$this->escapeConstVar($name)] = $constInfo; } protected function hasConstant(string $name): bool { - return isset($this->constants[$this->escapeNamespace($name)]); + return isset($this->constants[$this->escapeConstVar($name)]); } protected function getConstant(string $name): string { - return $this->escapeNamespace($name); + return $this->escapeConstVar($name); } protected function getConstantType(string $name): string { - return $this->constants[$this->escapeNamespace($name)]->type; + return $this->constants[$this->escapeConstVar($name)]->type; } protected function detectStrValueType(mixed $constant): string diff --git a/src/Php/Generator/Utils.php b/src/Php/Generator/Utils.php index 89391995..284bcf8f 100644 --- a/src/Php/Generator/Utils.php +++ b/src/Php/Generator/Utils.php @@ -95,6 +95,11 @@ trait Utils return self::GLOBAL_VAR . $name; } + protected function escapeConstVar(string $name): string + { + return self::CONST_VAR . str_replace('\\', self::NAMESPACE_SEPARATOR, $name); + } + protected function escapeNamespace(string $ns): string { return str_replace('\\', self::NAMESPACE_SEPARATOR, strtolower($ns)); diff --git a/tests/aot/const/const-same-name.phpt b/tests/aot/const/const-same-name.phpt new file mode 100644 index 00000000..8a024fa3 --- /dev/null +++ b/tests/aot/const/const-same-name.phpt @@ -0,0 +1,15 @@ +--TEST-- +Constant with the same name (case-insensitive) resolve independently +--FILE-- + +--EXPECT-- +int(123) +int(456) diff --git a/tests/aot/const/const-var-same-name.phpt b/tests/aot/const/const-var-same-name.phpt new file mode 100644 index 00000000..28bf1a9c --- /dev/null +++ b/tests/aot/const/const-var-same-name.phpt @@ -0,0 +1,15 @@ +--TEST-- +Constant and variable with the same name (case-insensitive) resolve independently +--FILE-- + +--EXPECT-- +int(123) +int(456) From 8e5b9e8076931426c2e0c3b8254f20fef0527ae6 Mon Sep 17 00:00:00 2001 From: Yurun Date: Wed, 17 Jun 2026 20:12:18 +0800 Subject: [PATCH 4/7] =?UTF-8?q?fix(compiler)):=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E9=99=A4=E4=BB=A5=E9=9B=B6MSVC=E7=BC=96=E8=AF=91=E4=B8=8D?= =?UTF-8?q?=E9=80=9A=E8=BF=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Php/Parser/BinaryOpTrait.php | 18 ++++++++++++++++++ tests/aot/float_edge/edge-cases.phpt | 2 ++ 2 files changed, 20 insertions(+) diff --git a/src/Php/Parser/BinaryOpTrait.php b/src/Php/Parser/BinaryOpTrait.php index c69f639a..efd214ec 100644 --- a/src/Php/Parser/BinaryOpTrait.php +++ b/src/Php/Parser/BinaryOpTrait.php @@ -125,6 +125,10 @@ trait BinaryOpTrait return 'php::fn::mod(' . $leftExpr . ', ' . $rightExpr . ')'; } + if ($op === '/' and $this->isZeroLiteral($right)) { + return self::VALUE_NAN; + } + return '((' . $leftExpr . ') ' . $op . ' (' . $rightExpr . '))'; } @@ -353,6 +357,20 @@ trait BinaryOpTrait return $this->parseBinaryOp($expr->left, $expr->right, '/'); } + private function isZeroLiteral(NodeAbstract $expr): bool + { + if ($expr instanceof Node\Scalar\Int_) { + return $expr->value == 0; + } + if ($expr instanceof Node\Scalar\Float_) { + return $expr->value == 0.0; + } + if ($expr instanceof Node\Scalar\String_) { + return $expr->value === '0' || $expr->value === '0.0'; + } + return false; + } + protected function parseBinaryOpMinus(Expr\BinaryOp\Minus $expr): string { return $this->parseBinaryOp($expr->left, $expr->right, '-'); diff --git a/tests/aot/float_edge/edge-cases.phpt b/tests/aot/float_edge/edge-cases.phpt index 1afab394..662325c1 100644 --- a/tests/aot/float_edge/edge-cases.phpt +++ b/tests/aot/float_edge/edge-cases.phpt @@ -19,6 +19,7 @@ function main(): void { var_dump(INF + INF); var_dump(INF / INF); var_dump(0.0 / 0.0); + var_dump(1.0 / 0.0); } ?> --EXPECT-- @@ -38,3 +39,4 @@ INF float(INF) float(NAN) float(NAN) +float(NAN) From 7e52d51b5bdde4c427eae76bd90a294e8341b25a Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Thu, 18 Jun 2026 10:22:37 +0800 Subject: [PATCH 5/7] =?UTF-8?q?fix(php):=20=E8=A7=A3=E5=86=B3=E9=99=A4?= =?UTF-8?q?=E9=9B=B6=E8=BF=90=E7=AE=97=E9=94=99=E8=AF=AF=E5=A4=84=E7=90=86?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加了对除法和取模运算中零值检测的统一处理 - 当遇到除零或模零运算时抛出致命错误而不是返回NaN - 移除了旧的零除法返回NaN的逻辑 - 统一了除法和取模运算的零值检查条件 --- src/Php/Parser/BinaryOpTrait.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Php/Parser/BinaryOpTrait.php b/src/Php/Parser/BinaryOpTrait.php index efd214ec..9b3ece94 100644 --- a/src/Php/Parser/BinaryOpTrait.php +++ b/src/Php/Parser/BinaryOpTrait.php @@ -121,12 +121,12 @@ trait BinaryOpTrait $leftExpr = $this->convertExprType($leftExpr, $leftType, self::TYPE_FLOAT); } - if ($op === '%' and !($leftType === self::TYPE_INT and $rightType === self::TYPE_INT)) { - return 'php::fn::mod(' . $leftExpr . ', ' . $rightExpr . ')'; + if (($op === '/' or $op === '%') and $this->isZeroLiteral($right)) { + $this->fatalError($right, 'Cannot divide or modulo by zero'); } - if ($op === '/' and $this->isZeroLiteral($right)) { - return self::VALUE_NAN; + if ($op === '%' and !($leftType === self::TYPE_INT and $rightType === self::TYPE_INT)) { + return 'php::fn::mod(' . $leftExpr . ', ' . $rightExpr . ')'; } return '((' . $leftExpr . ') ' . $op . ' (' . $rightExpr . '))'; From 91675ed3664d2778faa8feb6ac86168fb10031d6 Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Thu, 18 Jun 2026 10:40:58 +0800 Subject: [PATCH 6/7] =?UTF-8?q?feat(php):=20=E6=B7=BB=E5=8A=A0=E9=99=A4?= =?UTF-8?q?=E9=9B=B6=E6=A3=80=E6=9F=A5=E5=92=8C=E6=94=B9=E8=BF=9B=E5=8E=9F?= =?UTF-8?q?=E7=94=9F=E5=B1=9E=E6=80=A7=E8=AE=BF=E9=97=AE=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加对字面量除零、模零、赋值除零和赋值模零操作的编译时检查 - 实现对整数、浮点数和字符串类型的零值检测 - 为 AssignOpTrait 和 BinaryOpTrait 添加 guardLiteralDivisionByZero 方法 - 改进原生属性访问的类名解析,使用完整的类名进行查找 - 支持 static 关键字作为类名解析的一部分 - 添加 isSameClassName 和 isSameOrSubclassOf 辅助方法用于类继承关系判断 - 实现受保护属性访问权限控制的 canAccessProtectedProperty 方法 - 添加多个测试文件验证除零检查和原生属性访问功能 - 创建 NativePropertyTest 和 OperatorTest 测试类验证相关功能 --- phpunit/code/assign-divide-by-zero.php | 7 ++ phpunit/code/assign-modulo-by-zero.php | 7 ++ phpunit/code/divide-by-zero-float.php | 6 ++ phpunit/code/divide-by-zero-int.php | 6 ++ phpunit/code/divide-by-zero-string.php | 6 ++ phpunit/code/modulo-by-zero-int.php | 6 ++ phpunit/code/native-property-full-name.php | 69 +++++++++++++++++++ .../native-property-private-other-class.php | 20 ++++++ ...ive-property-protected-unrelated-class.php | 20 ++++++ phpunit/src/NativePropertyTest.php | 40 +++++++++++ phpunit/src/OperatorTest.php | 34 +++++++++ src/Php/CompilerBase.php | 39 +++++++++-- src/Php/Parser/AssignOpTrait.php | 1 + src/Php/Parser/BinaryOpTrait.php | 21 ++++-- tests/aot/float_edge/edge-cases.phpt | 4 -- 15 files changed, 271 insertions(+), 15 deletions(-) create mode 100644 phpunit/code/assign-divide-by-zero.php create mode 100644 phpunit/code/assign-modulo-by-zero.php create mode 100644 phpunit/code/divide-by-zero-float.php create mode 100644 phpunit/code/divide-by-zero-int.php create mode 100644 phpunit/code/divide-by-zero-string.php create mode 100644 phpunit/code/modulo-by-zero-int.php create mode 100644 phpunit/code/native-property-full-name.php create mode 100644 phpunit/code/native-property-private-other-class.php create mode 100644 phpunit/code/native-property-protected-unrelated-class.php create mode 100644 phpunit/src/NativePropertyTest.php create mode 100644 phpunit/src/OperatorTest.php diff --git a/phpunit/code/assign-divide-by-zero.php b/phpunit/code/assign-divide-by-zero.php new file mode 100644 index 00000000..81a41471 --- /dev/null +++ b/phpunit/code/assign-divide-by-zero.php @@ -0,0 +1,7 @@ +value; + } + + public function readInheritedProtected(Target $target): int + { + return $target->protectedValue; + } + + public static function readSelf(): int + { + return self::$count; + } + + public static function readStatic(): int + { + return static::$count; + } + + public static function readParent(): int + { + return parent::$count; + } + } + + function readObject(): int + { + $target = new Target(); + return $target->value; + } + + function readStaticByUse(): int + { + return Target::$count; + } +} + +namespace { + function main(): void + { + $child = new \NativePropSource\Target\Child(); + var_dump($child->readThis()); + var_dump(\NativePropSource\Target\readObject()); + var_dump(\NativePropSource\Target\readStaticByUse()); + var_dump(\NativePropSource\Target\Child::readSelf()); + var_dump(\NativePropSource\Target\Child::readStatic()); + var_dump(\NativePropSource\Target\Child::readParent()); + } +} diff --git a/phpunit/code/native-property-private-other-class.php b/phpunit/code/native-property-private-other-class.php new file mode 100644 index 00000000..d75474f1 --- /dev/null +++ b/phpunit/code/native-property-private-other-class.php @@ -0,0 +1,20 @@ +value; + } +} + +function main(): void +{ +} diff --git a/phpunit/code/native-property-protected-unrelated-class.php b/phpunit/code/native-property-protected-unrelated-class.php new file mode 100644 index 00000000..93a32149 --- /dev/null +++ b/phpunit/code/native-property-protected-unrelated-class.php @@ -0,0 +1,20 @@ +value; + } +} + +function main(): void +{ +} diff --git a/phpunit/src/NativePropertyTest.php b/phpunit/src/NativePropertyTest.php new file mode 100644 index 00000000..6f32781e --- /dev/null +++ b/phpunit/src/NativePropertyTest.php @@ -0,0 +1,40 @@ +addFiles([$testFile]); + $compiler->prepareFile($testFile); + $compiler->convertFile($testFile); + + $this->addToAssertionCount(1); + } + + public function testFindNativePropertyUsesFullClassNameAcrossBranches(): void + { + try { + $this->compile('native-property-full-name.php'); + } catch (TestError $e) { + $this->fail($e->getMessage()); + } + } + + public function testCannotAccessPrivateNativePropertyFromUnrelatedClass(): void + { + $this->exec('Cannot access private property `value` of class `NativePrivateOwner`', 'native-property-private-other-class.php'); + } + + public function testCannotAccessProtectedNativePropertyFromUnrelatedClass(): void + { + $this->exec('Cannot access protected property `value` of class `NativeProtectedOwner`', 'native-property-protected-unrelated-class.php'); + } +} diff --git a/phpunit/src/OperatorTest.php b/phpunit/src/OperatorTest.php new file mode 100644 index 00000000..7bb2d19d --- /dev/null +++ b/phpunit/src/OperatorTest.php @@ -0,0 +1,34 @@ +exec('Cannot divide or modulo by zero', 'divide-by-zero-int.php'); + } + + public function testLiteralFloatDivideByZeroDoesNotCompile(): void + { + $this->exec('Cannot divide or modulo by zero', 'divide-by-zero-float.php'); + } + + public function testLiteralStringDivideByZeroDoesNotCompile(): void + { + $this->exec('Cannot divide or modulo by zero', 'divide-by-zero-string.php'); + } + + public function testLiteralModuloByZeroDoesNotCompile(): void + { + $this->exec('Cannot divide or modulo by zero', 'modulo-by-zero-int.php'); + } + + public function testLiteralDivideAssignByZeroDoesNotCompile(): void + { + $this->exec('Cannot divide or modulo by zero', 'assign-divide-by-zero.php'); + } + + public function testLiteralModuloAssignByZeroDoesNotCompile(): void + { + $this->exec('Cannot divide or modulo by zero', 'assign-modulo-by-zero.php'); + } +} diff --git a/src/Php/CompilerBase.php b/src/Php/CompilerBase.php index 569da39b..8755a759 100644 --- a/src/Php/CompilerBase.php +++ b/src/Php/CompilerBase.php @@ -3866,7 +3866,7 @@ class CompilerBase extends \PhpAot\Core\Translator if ($this->classDef->trait) { goto _dynamic_attr; } - $nativeProperty = $this->findNativeProperty($expr, $propertyName, $this->class, $this->namespace); + $nativeProperty = $this->findNativeProperty($expr, $propertyName, $this->getFullClassName()); } elseif ($this->isTypedObject($objectName)) { $className = $this->getObjectType($objectName); $nativeProperty = $this->findNativeProperty($expr, $propertyName, $className); @@ -4723,7 +4723,7 @@ class CompilerBase extends \PhpAot\Core\Translator if ($this->isNameExpr($expr->class) and $this->isIdExpr($expr->name)) { $class = $this->parseIdentifier($expr->class); $propertyName = $this->parseIdentifier($expr->name); - if ($class === 'self') { + if ($class === 'self' || $class === 'static') { if ($this->classDef->trait) { return Symbol::getStaticProperty() . '(' . Symbol::getCalledCe() . ', ' . $this->getLiteralString($propertyName) . ')'; } @@ -4745,13 +4745,42 @@ class CompilerBase extends \PhpAot\Core\Translator return null; } + protected function isSameClassName(string $classA, string $classB): bool + { + return strcasecmp(ltrim($classA, '\\'), ltrim($classB, '\\')) === 0; + } + + protected function isSameOrSubclassOf(string $class, string $parent): bool + { + $class = strtolower(ltrim($class, '\\')); + $parent = strtolower(ltrim($parent, '\\')); + while ($class !== '') { + if ($class === $parent) { + return true; + } + $class = $this->classExtends[$class] ?? ''; + } + return false; + } + + protected function canAccessProtectedProperty(string $scope, string $declaringClass): bool + { + if ($scope === '') { + return false; + } + return $this->isSameOrSubclassOf($scope, $declaringClass) + || $this->isSameOrSubclassOf($declaringClass, $scope); + } + /** * @param NodeAbstract $expr 仅用于输出错误日志 + * @param string $class 必须传入带有完整命名空间的类名 */ protected function findNativeProperty(NodeAbstract $expr, string $property, string $class, bool $static = false): ?string { + $class = ltrim($class, '\\'); $findClass = $class; - $scope = $this->class ? $class : ''; + $scope = $this->class ? $this->getFullClassName() : ''; $propertyDef = null; $classDef = null; while (true) { @@ -4769,13 +4798,13 @@ class CompilerBase extends \PhpAot\Core\Translator break; } if ($propertyDef->isProtected()) { - if ($scope) { + if ($this->canAccessProtectedProperty($scope, $findClass)) { break; } $displayClass = ltrim($class, '\\'); $this->fatalError($expr, "Cannot access protected property `{$property}` of class `{$displayClass}`"); } else { - if ($scope === $findClass) { + if ($this->isSameClassName($scope, $findClass)) { break; } $displayClass = ltrim($class, '\\'); diff --git a/src/Php/Parser/AssignOpTrait.php b/src/Php/Parser/AssignOpTrait.php index 4ffae7e7..8286bca2 100644 --- a/src/Php/Parser/AssignOpTrait.php +++ b/src/Php/Parser/AssignOpTrait.php @@ -323,6 +323,7 @@ trait AssignOpTrait $var = $this->parseIdentifier($node->var); $this->context->inAssignExpr = $oriInAssignExpr; $expr = $this->parseIdentifier($node->expr); + $this->guardLiteralDivisionByZero($node->expr, $op); if ($this->isVarExpr($node->var)) { if (!$this->hasVar($var)) { diff --git a/src/Php/Parser/BinaryOpTrait.php b/src/Php/Parser/BinaryOpTrait.php index 9b3ece94..3a776444 100644 --- a/src/Php/Parser/BinaryOpTrait.php +++ b/src/Php/Parser/BinaryOpTrait.php @@ -121,9 +121,7 @@ trait BinaryOpTrait $leftExpr = $this->convertExprType($leftExpr, $leftType, self::TYPE_FLOAT); } - if (($op === '/' or $op === '%') and $this->isZeroLiteral($right)) { - $this->fatalError($right, 'Cannot divide or modulo by zero'); - } + $this->guardLiteralDivisionByZero($right, $op); if ($op === '%' and !($leftType === self::TYPE_INT and $rightType === self::TYPE_INT)) { return 'php::fn::mod(' . $leftExpr . ', ' . $rightExpr . ')'; @@ -357,16 +355,27 @@ trait BinaryOpTrait return $this->parseBinaryOp($expr->left, $expr->right, '/'); } - private function isZeroLiteral(NodeAbstract $expr): bool + protected function guardLiteralDivisionByZero(NodeAbstract $right, string $op): void + { + if (($op === '/' or $op === '%' or $op === '/=' or $op === '%=') and $this->isZeroLiteral($right)) { + $this->fatalError($right, 'Cannot divide or modulo by zero'); + } + } + + protected function isZeroLiteral(NodeAbstract $expr): bool { if ($expr instanceof Node\Scalar\Int_) { - return $expr->value == 0; + return $expr->value === 0; } if ($expr instanceof Node\Scalar\Float_) { return $expr->value == 0.0; } + if ($expr instanceof Expr\UnaryMinus || $expr instanceof Expr\UnaryPlus) { + return $this->isZeroLiteral($expr->expr); + } if ($expr instanceof Node\Scalar\String_) { - return $expr->value === '0' || $expr->value === '0.0'; + $value = trim($expr->value); + return $value !== '' && is_numeric($value) && (float) $value == 0.0; } return false; } diff --git a/tests/aot/float_edge/edge-cases.phpt b/tests/aot/float_edge/edge-cases.phpt index 662325c1..e14b1c1e 100644 --- a/tests/aot/float_edge/edge-cases.phpt +++ b/tests/aot/float_edge/edge-cases.phpt @@ -18,8 +18,6 @@ function main(): void { echo -INF . "\n"; var_dump(INF + INF); var_dump(INF / INF); - var_dump(0.0 / 0.0); - var_dump(1.0 / 0.0); } ?> --EXPECT-- @@ -38,5 +36,3 @@ INF -INF float(INF) float(NAN) -float(NAN) -float(NAN) From cce6967acfcd5f6e509bed627b571ca260a10c1f Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Thu, 18 Jun 2026 10:52:53 +0800 Subject: [PATCH 7/7] =?UTF-8?q?fix(php):=20=E4=BF=AE=E5=A4=8D=E9=9D=99?= =?UTF-8?q?=E6=80=81=E5=B1=9E=E6=80=A7=E6=99=9A=E7=BB=91=E5=AE=9A=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修改 CompilerBase.php 中的静态属性处理逻辑,正确区分 self 和 static 关键字 - 为 native-property-full-name.php 添加 writeStatic 测试方法 - 更新 NativePropertyTest.php 中的编译方法返回值类型和测试用例 - 添加静态属性晚绑定功能的完整测试用例 static-prop-late-static-binding.phpt --- phpunit/code/native-property-full-name.php | 7 ++++ phpunit/src/NativePropertyTest.php | 16 +++++++- src/Php/CompilerBase.php | 5 ++- .../static-prop-late-static-binding.phpt | 39 +++++++++++++++++++ 4 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 tests/aot/static/static-prop-late-static-binding.phpt diff --git a/phpunit/code/native-property-full-name.php b/phpunit/code/native-property-full-name.php index af197a4a..e954ccbb 100644 --- a/phpunit/code/native-property-full-name.php +++ b/phpunit/code/native-property-full-name.php @@ -37,6 +37,12 @@ namespace NativePropSource\Target { return static::$count; } + public static function writeStatic(int $value): int + { + static::$count = $value; + return static::$count; + } + public static function readParent(): int { return parent::$count; @@ -64,6 +70,7 @@ namespace { var_dump(\NativePropSource\Target\readStaticByUse()); var_dump(\NativePropSource\Target\Child::readSelf()); var_dump(\NativePropSource\Target\Child::readStatic()); + var_dump(\NativePropSource\Target\Child::writeStatic(4)); var_dump(\NativePropSource\Target\Child::readParent()); } } diff --git a/phpunit/src/NativePropertyTest.php b/phpunit/src/NativePropertyTest.php index 6f32781e..b01b6b80 100644 --- a/phpunit/src/NativePropertyTest.php +++ b/phpunit/src/NativePropertyTest.php @@ -5,7 +5,7 @@ use PhpAot\Php\Exception\TestError; class NativePropertyTest extends \BaseTest { - private function compile(string $file): void + private function compile(string $file): string { global $translator; @@ -17,6 +17,7 @@ class NativePropertyTest extends \BaseTest $compiler->convertFile($testFile); $this->addToAssertionCount(1); + return ROOT_PATH . '/build/phpunit/code/' . basename($file, '.php') . '.cc'; } public function testFindNativePropertyUsesFullClassNameAcrossBranches(): void @@ -28,6 +29,19 @@ class NativePropertyTest extends \BaseTest } } + public function testStaticStaticPropertyUsesDynamicCalledClassPath(): void + { + try { + $outputFile = $this->compile('native-property-full-name.php'); + } catch (TestError $e) { + $this->fail($e->getMessage()); + } + + $code = file_get_contents($outputFile); + $this->assertStringContainsString('php::getStaticProperty(php_get_called_class(this_), "count")', $code); + $this->assertStringContainsString('php::getStaticProperty(php_get_called_class(this_), "count") = php::toInt(value)', $code); + } + public function testCannotAccessPrivateNativePropertyFromUnrelatedClass(): void { $this->exec('Cannot access private property `value` of class `NativePrivateOwner`', 'native-property-private-other-class.php'); diff --git a/src/Php/CompilerBase.php b/src/Php/CompilerBase.php index 8755a759..3257b200 100644 --- a/src/Php/CompilerBase.php +++ b/src/Php/CompilerBase.php @@ -4723,7 +4723,10 @@ class CompilerBase extends \PhpAot\Core\Translator if ($this->isNameExpr($expr->class) and $this->isIdExpr($expr->name)) { $class = $this->parseIdentifier($expr->class); $propertyName = $this->parseIdentifier($expr->name); - if ($class === 'self' || $class === 'static') { + if ($class === 'static') { + return null; + } + if ($class === 'self') { if ($this->classDef->trait) { return Symbol::getStaticProperty() . '(' . Symbol::getCalledCe() . ', ' . $this->getLiteralString($propertyName) . ')'; } diff --git a/tests/aot/static/static-prop-late-static-binding.phpt b/tests/aot/static/static-prop-late-static-binding.phpt new file mode 100644 index 00000000..573bdfa3 --- /dev/null +++ b/tests/aot/static/static-prop-late-static-binding.phpt @@ -0,0 +1,39 @@ +--TEST-- +Static native properties use late static binding for static::$prop +--FILE-- + +--EXPECT-- +int(1) +int(2) +int(1) +int(9) +int(9)