feat(php): 添加除零检查和改进原生属性访问功能

- 添加对字面量除零、模零、赋值除零和赋值模零操作的编译时检查
- 实现对整数、浮点数和字符串类型的零值检测
- 为 AssignOpTrait 和 BinaryOpTrait 添加 guardLiteralDivisionByZero 方法
- 改进原生属性访问的类名解析,使用完整的类名进行查找
- 支持 static 关键字作为类名解析的一部分
- 添加 isSameClassName 和 isSameOrSubclassOf 辅助方法用于类继承关系判断
- 实现受保护属性访问权限控制的 canAccessProtectedProperty 方法
- 添加多个测试文件验证除零检查和原生属性访问功能
- 创建 NativePropertyTest 和 OperatorTest 测试类验证相关功能
pull/2/head
韩天峰 2 months ago
parent 7e52d51b5b
commit 91675ed366
  1. 7
      phpunit/code/assign-divide-by-zero.php
  2. 7
      phpunit/code/assign-modulo-by-zero.php
  3. 6
      phpunit/code/divide-by-zero-float.php
  4. 6
      phpunit/code/divide-by-zero-int.php
  5. 6
      phpunit/code/divide-by-zero-string.php
  6. 6
      phpunit/code/modulo-by-zero-int.php
  7. 69
      phpunit/code/native-property-full-name.php
  8. 20
      phpunit/code/native-property-private-other-class.php
  9. 20
      phpunit/code/native-property-protected-unrelated-class.php
  10. 40
      phpunit/src/NativePropertyTest.php
  11. 34
      phpunit/src/OperatorTest.php
  12. 39
      src/Php/CompilerBase.php
  13. 1
      src/Php/Parser/AssignOpTrait.php
  14. 21
      src/Php/Parser/BinaryOpTrait.php
  15. 4
      tests/aot/float_edge/edge-cases.phpt

@ -0,0 +1,7 @@
<?php
function main(): void
{
$value = 10;
$value /= 0;
}

@ -0,0 +1,7 @@
<?php
function main(): void
{
$value = 10;
$value %= 0;
}

@ -0,0 +1,6 @@
<?php
function main(): void
{
var_dump(1.0 / 0.0);
}

@ -0,0 +1,6 @@
<?php
function main(): void
{
var_dump(10 / 0);
}

@ -0,0 +1,6 @@
<?php
function main(): void
{
var_dump(10 / '0.00');
}

@ -0,0 +1,6 @@
<?php
function main(): void
{
var_dump(10 % 0);
}

@ -0,0 +1,69 @@
<?php
namespace NativePropSource {
use native_types;
class Target
{
public static int $count = 1;
public int $value = 2;
protected int $protectedValue = 3;
}
}
namespace NativePropSource\Target {
use native_types;
use NativePropSource\Target;
class Child extends Target
{
public function readThis(): int
{
return $this->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());
}
}

@ -0,0 +1,20 @@
<?php
use native_types;
class NativePrivateOwner
{
private int $value = 1;
}
class NativePrivateReader
{
public function read(NativePrivateOwner $owner): int
{
return $owner->value;
}
}
function main(): void
{
}

@ -0,0 +1,20 @@
<?php
use native_types;
class NativeProtectedOwner
{
protected int $value = 1;
}
class NativeProtectedReader
{
public function read(NativeProtectedOwner $owner): int
{
return $owner->value;
}
}
function main(): void
{
}

@ -0,0 +1,40 @@
<?php
use PhpAot\Php\CompilerTest;
use PhpAot\Php\Exception\TestError;
class NativePropertyTest extends \BaseTest
{
private function compile(string $file): void
{
global $translator;
$compiler = CompilerTest::create(ROOT_PATH);
$translator = $compiler;
$testFile = __DIR__ . '/../code/' . $file;
$compiler->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');
}
}

@ -0,0 +1,34 @@
<?php
class OperatorTest extends \BaseTest
{
public function testLiteralIntDivideByZeroDoesNotCompile(): void
{
$this->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');
}
}

@ -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, '\\');

@ -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)) {

@ -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;
}

@ -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)

Loading…
Cancel
Save