Merge pull request '修复一些问题' (#2) from fix-20260617 into master

Reviewed-on: #2
pull/3/head
韩天峰 2 months ago
commit 6543dfccce
  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. 76
      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. 54
      phpunit/src/NativePropertyTest.php
  11. 34
      phpunit/src/OperatorTest.php
  12. 58
      src/Php/CompilerBase.php
  13. 5
      src/Php/Generator/Utils.php
  14. 1
      src/Php/Parser/AssignOpTrait.php
  15. 27
      src/Php/Parser/BinaryOpTrait.php
  16. 0
      tests/aot/basic/object-arg.phpt
  17. 15
      tests/aot/const/const-same-name.phpt
  18. 15
      tests/aot/const/const-var-same-name.phpt
  19. 2
      tests/aot/float_edge/edge-cases.phpt
  20. 52
      tests/aot/namespace/ns-same-as-class.phpt
  21. 39
      tests/aot/static/static-prop-late-static-binding.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,76 @@
<?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 writeStatic(int $value): int
{
static::$count = $value;
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::writeStatic(4));
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,54 @@
<?php
use PhpAot\Php\CompilerTest;
use PhpAot\Php\Exception\TestError;
class NativePropertyTest extends \BaseTest
{
private function compile(string $file): string
{
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);
return ROOT_PATH . '/build/phpunit/code/' . basename($file, '.php') . '.cc';
}
public function testFindNativePropertyUsesFullClassNameAcrossBranches(): void
{
try {
$this->compile('native-property-full-name.php');
} catch (TestError $e) {
$this->fail($e->getMessage());
}
}
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');
}
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');
}
}

@ -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';
@ -3865,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);
@ -4722,6 +4723,9 @@ 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 === 'static') {
return null;
}
if ($class === 'self') {
if ($this->classDef->trait) {
return Symbol::getStaticProperty() . '(' . Symbol::getCalledCe() . ', ' . $this->getLiteralString($propertyName) . ')';
@ -4732,8 +4736,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;
@ -4742,16 +4748,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, string $namespace = '', bool $static = false): ?string
protected function findNativeProperty(NodeAbstract $expr, string $property, string $class, bool $static = false): ?string
{
$class = ltrim($class, '\\');
$findClass = $class;
if ($namespace) {
$findClass = $namespace . '\\' . $class;
}
$scope = $this->class ? ltrim($namespace . '\\' . $class, '\\') : '';
$scope = $this->class ? $this->getFullClassName() : '';
$propertyDef = null;
$classDef = null;
while (true) {
@ -4769,13 +4801,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, '\\');
@ -5022,22 +5054,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

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

@ -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,6 +121,8 @@ trait BinaryOpTrait
$leftExpr = $this->convertExprType($leftExpr, $leftType, self::TYPE_FLOAT);
}
$this->guardLiteralDivisionByZero($right, $op);
if ($op === '%' and !($leftType === self::TYPE_INT and $rightType === self::TYPE_INT)) {
return 'php::fn::mod(' . $leftExpr . ', ' . $rightExpr . ')';
}
@ -353,6 +355,31 @@ trait BinaryOpTrait
return $this->parseBinaryOp($expr->left, $expr->right, '/');
}
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;
}
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_) {
$value = trim($expr->value);
return $value !== '' && is_numeric($value) && (float) $value == 0.0;
}
return false;
}
protected function parseBinaryOpMinus(Expr\BinaryOp\Minus $expr): string
{
return $this->parseBinaryOp($expr->left, $expr->right, '-');

@ -0,0 +1,15 @@
--TEST--
Constant with the same name (case-insensitive) resolve independently
--FILE--
<?php
const a = 123;
const A = 456;
function main(): void
{
var_dump(a, A);
}
?>
--EXPECT--
int(123)
int(456)

@ -0,0 +1,15 @@
--TEST--
Constant and variable with the same name (case-insensitive) resolve independently
--FILE--
<?php
const a = 123;
function main(): void
{
$a = 456;
var_dump(a, $a);
}
?>
--EXPECT--
int(123)
int(456)

@ -18,7 +18,6 @@ function main(): void {
echo -INF . "\n";
var_dump(INF + INF);
var_dump(INF / INF);
var_dump(0.0 / 0.0);
}
?>
--EXPECT--
@ -37,4 +36,3 @@ INF
-INF
float(INF)
float(NAN)
float(NAN)

@ -0,0 +1,52 @@
--TEST--
use class A\B (without alias) inside namespace A\B, call B::$v
--FILE--
<?php
namespace A {
class B
{
public static $v = 123;
}
}
namespace A\B {
use A\B;
function testProp(): int {
return B::$v;
}
class C extends B {
public static function testSelf(): int
{
return self::$v;
}
public static function testStatic(): int
{
return static::$v;
}
public static function testParent(): int
{
return parent::$v;
}
}
}
namespace {
function main() {
var_dump(\A\B\testProp());
var_dump(\A\B\C::testSelf());
var_dump(\A\B\C::testStatic());
var_dump(\A\B\C::testParent());
}
}
?>
--EXPECT--
int(123)
int(123)
int(123)
int(123)

@ -0,0 +1,39 @@
--TEST--
Static native properties use late static binding for static::$prop
--FILE--
<?php
use native_types;
class StaticPropBase {
public static int $v = 1;
public static function get(): int {
return static::$v;
}
public static function set(int $v): void {
static::$v = $v;
}
}
class StaticPropChild extends StaticPropBase {
public static int $v = 2;
}
function main(): void {
var_dump(StaticPropBase::get());
var_dump(StaticPropChild::get());
StaticPropChild::set(9);
var_dump(StaticPropBase::$v);
var_dump(StaticPropChild::$v);
var_dump(StaticPropChild::get());
}
?>
--EXPECT--
int(1)
int(2)
int(1)
int(9)
int(9)
Loading…
Cancel
Save