- 添加 __call 方法异常处理测试 - 添加 __set 和 __get 魔术方法功能测试 - 添加魔术方法签名检查测试 - 添加抽象类和抽象方法调用测试 - 添加对象转数组键名转换测试 - 添加赋值操作符属性测试 - 添加数据损坏相关测试 fix(compiler): 禁止在 echo 中使用赋值表达式 - 在 parseEcho 方法中检测赋值表达式并报错 - 修复属性赋值操作符解析逻辑 refactor(stub): 优化全局变量默认值处理 - 统一数组类型默认值处理方式 - 移除特殊空数组处理逻辑 feat(magic): 添加魔术方法参数数量检测机制 - 实现 MagicMethodDetector trait - 集成到 Translator 类中使用pull/1/head
parent
66249916f5
commit
43b9797df8
16 changed files with 481 additions and 18 deletions
@ -0,0 +1,21 @@ |
||||
<?php |
||||
|
||||
namespace PhpAot\Php; |
||||
|
||||
use PhpParser\NodeAbstract; |
||||
|
||||
trait MagicMethodDetector |
||||
{ |
||||
function checkRequiredArgNum(string $name, MethodDef $methodDef, NodeAbstract $v): void |
||||
{ |
||||
if ($name == '__call' or $name == '__callStatic' or $name == '__set') { |
||||
if (count($methodDef->functionDef->argInfoList) != 2) { |
||||
$this->fatalError($v, 'Method ' . $this->class . "::$name() must take exactly 2 arguments"); |
||||
} |
||||
} elseif ($name == '__get') { |
||||
if (count($methodDef->functionDef->argInfoList) != 1) { |
||||
$this->fatalError($v, 'Method ' . $this->class . "::$name() must take exactly 1 argument"); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,79 @@ |
||||
--TEST-- |
||||
Ensure exceptions are handled properly when thrown in __call. |
||||
--SKIPIF-- |
||||
<?php die('skip'); ?> |
||||
--FILE-- |
||||
<?php |
||||
class A { |
||||
function __call($strMethod, $arrArgs) { |
||||
var_dump($this); |
||||
throw new Exception; |
||||
echo "You should not see this"; |
||||
} |
||||
function test() { |
||||
A::unknownCalledWithSRO(1,2,3); |
||||
} |
||||
} |
||||
|
||||
class B extends A { |
||||
function test() { |
||||
B::unknownCalledWithSROFromChild(1,2,3); |
||||
} |
||||
} |
||||
|
||||
function main() { |
||||
$a = new A(); |
||||
|
||||
echo "---> Invoke __call via simple method call.\n"; |
||||
try { |
||||
$a->unknown(); |
||||
} catch (Exception $e) { |
||||
echo "Exception caught OK; continuing.\n"; |
||||
} |
||||
|
||||
echo "\n\n---> Invoke __call via scope resolution operator within instance.\n"; |
||||
try { |
||||
$a->test(); |
||||
} catch (Exception $e) { |
||||
echo "Exception caught OK; continuing.\n"; |
||||
} |
||||
|
||||
echo "\n\n---> Invoke __call via scope resolution operator within child instance.\n"; |
||||
$b = new B(); |
||||
try { |
||||
$b->test(); |
||||
} catch (Exception $e) { |
||||
echo "Exception caught OK; continuing.\n"; |
||||
} |
||||
|
||||
echo "\n\n---> Invoke __call via callback.\n"; |
||||
try { |
||||
call_user_func(array($b, 'unknownCallback'), 1,2,3); |
||||
} catch (Exception $e) { |
||||
echo "Exception caught OK; continuing.\n"; |
||||
} |
||||
} |
||||
?> |
||||
--EXPECTF-- |
||||
---> Invoke __call via simple method call. |
||||
object(A)#%d (0) { |
||||
} |
||||
Exception caught OK; continuing. |
||||
|
||||
|
||||
---> Invoke __call via scope resolution operator within instance. |
||||
object(A)#%d (0) { |
||||
} |
||||
Exception caught OK; continuing. |
||||
|
||||
|
||||
---> Invoke __call via scope resolution operator within child instance. |
||||
object(B)#%d (0) { |
||||
} |
||||
Exception caught OK; continuing. |
||||
|
||||
|
||||
---> Invoke __call via callback. |
||||
object(B)#%d (0) { |
||||
} |
||||
Exception caught OK; continuing. |
||||
@ -0,0 +1,71 @@ |
||||
--TEST-- |
||||
ZE2 __set() and __get() |
||||
--FILE-- |
||||
<?php |
||||
class setter { |
||||
public $n; |
||||
public $x = array('a' => 1, 'b' => 2, 'c' => 3); |
||||
|
||||
function __get($nm) { |
||||
echo "Getting [$nm]\n"; |
||||
|
||||
if (isset($this->x[$nm])) { |
||||
$r = $this->x[$nm]; |
||||
echo "Returning: $r\n"; |
||||
return $r; |
||||
} |
||||
else { |
||||
echo "Nothing!\n"; |
||||
} |
||||
} |
||||
|
||||
function __set($nm, $val) { |
||||
echo "Setting [$nm] to $val\n"; |
||||
|
||||
if (isset($this->x[$nm])) { |
||||
$this->x[$nm] = $val; |
||||
echo "OK!\n"; |
||||
} |
||||
else { |
||||
echo "Not OK!\n"; |
||||
} |
||||
} |
||||
} |
||||
|
||||
function main() { |
||||
$foo = new Setter(); |
||||
|
||||
// this doesn't go through __set()... should it? |
||||
$foo->n = 1; |
||||
|
||||
// the rest are fine... |
||||
$foo->a = 100; |
||||
$foo->a++; |
||||
$foo->z++; |
||||
var_dump($foo); |
||||
} |
||||
?> |
||||
--EXPECTF-- |
||||
Setting [a] to 100 |
||||
OK! |
||||
Getting [a] |
||||
Returning: 100 |
||||
Setting [a] to 101 |
||||
OK! |
||||
Getting [z] |
||||
Nothing! |
||||
Setting [z] to 1 |
||||
Not OK! |
||||
object(setter)#%d (2) { |
||||
["n"]=> |
||||
int(1) |
||||
["x"]=> |
||||
array(3) { |
||||
["a"]=> |
||||
int(101) |
||||
["b"]=> |
||||
int(2) |
||||
["c"]=> |
||||
int(3) |
||||
} |
||||
} |
||||
@ -0,0 +1,16 @@ |
||||
--TEST-- |
||||
ZE2 __get() signature check |
||||
--SKIPIF-- |
||||
<?php die('skip, failed at compile time'); ?> |
||||
--FILE-- |
||||
<?php |
||||
class Test { |
||||
function __get($x,$y) { |
||||
} |
||||
} |
||||
function main() { |
||||
|
||||
} |
||||
?> |
||||
--EXPECTF-- |
||||
Fatal error: Method Test::__get() must take exactly 1 argument in %s__set__get_002.php on line %d |
||||
@ -0,0 +1,16 @@ |
||||
--TEST-- |
||||
ZE2 __set() signature check |
||||
--SKIPIF-- |
||||
<?php die('skip, failed at compile time'); ?> |
||||
--FILE-- |
||||
<?php |
||||
class Test { |
||||
function __set() { |
||||
} |
||||
} |
||||
function main() { |
||||
|
||||
} |
||||
?> |
||||
--EXPECTF-- |
||||
Fatal error: Method Test::__set() must take exactly 2 arguments in %s__set__get_003.php on line %d |
||||
@ -0,0 +1,33 @@ |
||||
--TEST-- |
||||
ZE2 __set() and __get() |
||||
--FILE-- |
||||
<?php |
||||
class Test { |
||||
protected $x = []; |
||||
|
||||
function __get($name) { |
||||
if (isset($this->x[$name])) { |
||||
return $this->x[$name]; |
||||
} else { |
||||
return NULL; |
||||
} |
||||
} |
||||
|
||||
function __set($name, $val) { |
||||
$this->x[$name] = $val; |
||||
} |
||||
} |
||||
function main() { |
||||
$foo = new Test(); |
||||
$bar = new Test(); |
||||
$bar->baz = "Check"; |
||||
|
||||
$foo->bar = $bar; |
||||
|
||||
var_dump($bar->baz); |
||||
var_dump($foo->bar->baz); |
||||
} |
||||
?> |
||||
--EXPECT-- |
||||
string(5) "Check" |
||||
string(5) "Check" |
||||
@ -0,0 +1,62 @@ |
||||
--TEST-- |
||||
ZE2 __set() and __get() |
||||
--FILE-- |
||||
<?php |
||||
class Test |
||||
{ |
||||
protected $x = []; |
||||
|
||||
function __get($name) { |
||||
echo __METHOD__ . "\n"; |
||||
if (isset($this->x[$name])) { |
||||
return $this->x[$name]; |
||||
} else { |
||||
return NULL; |
||||
} |
||||
} |
||||
|
||||
function __set($name, $val) { |
||||
echo __METHOD__ . "\n"; |
||||
$this->x[$name] = $val; |
||||
} |
||||
} |
||||
|
||||
class AutoGen |
||||
{ |
||||
protected $x = []; |
||||
|
||||
function __get($name) { |
||||
echo __METHOD__ . "\n"; |
||||
if (!isset($this->x[$name])) { |
||||
$this->x[$name] = new Test(); |
||||
} |
||||
return $this->x[$name]; |
||||
} |
||||
|
||||
function __set($name, $val) { |
||||
echo __METHOD__ . "\n"; |
||||
$this->x[$name] = $val; |
||||
} |
||||
} |
||||
function main() { |
||||
$foo = new AutoGen(); |
||||
$foo->bar->baz = "Check"; |
||||
|
||||
var_dump($foo->bar); |
||||
var_dump($foo->bar->baz); |
||||
} |
||||
?> |
||||
--EXPECTF-- |
||||
AutoGen::__get |
||||
Test::__set |
||||
AutoGen::__get |
||||
object(Test)#%d (1) { |
||||
["x":protected]=> |
||||
array(1) { |
||||
["baz"]=> |
||||
string(5) "Check" |
||||
} |
||||
} |
||||
AutoGen::__get |
||||
Test::__get |
||||
string(5) "Check" |
||||
@ -0,0 +1,30 @@ |
||||
--TEST-- |
||||
ZE2 Data corruption in __set |
||||
--FILE-- |
||||
<?php |
||||
class foo { |
||||
const foobar=1; |
||||
public $pp = array('t'=>null); |
||||
|
||||
function bar() { |
||||
$this->t = 'f'; |
||||
echo $this->t; |
||||
} |
||||
function __get($prop) |
||||
{ |
||||
return $this->pp[$prop]; |
||||
} |
||||
function __set($prop, $val) |
||||
{ |
||||
echo "__set"; |
||||
$this->pp[$prop] = '__test'; |
||||
} |
||||
} |
||||
|
||||
function main() { |
||||
$f = new foo; |
||||
$f->bar(); |
||||
} |
||||
?> |
||||
--EXPECT-- |
||||
__set__test |
||||
@ -0,0 +1,35 @@ |
||||
--TEST-- |
||||
ZE2 An abstract method may not be called |
||||
--FILE-- |
||||
<?php |
||||
|
||||
abstract class fail { |
||||
abstract function show(); |
||||
} |
||||
|
||||
class pass extends fail { |
||||
function show() { |
||||
echo "Call to function show()\n"; |
||||
} |
||||
function error() { |
||||
parent::show(); |
||||
} |
||||
} |
||||
|
||||
function main() { |
||||
$t = new pass(); |
||||
$t->show(); |
||||
$t->error(); |
||||
|
||||
echo "Done\n"; // shouldn't be displayed |
||||
} |
||||
?> |
||||
--EXPECTF-- |
||||
Call to function show() |
||||
|
||||
Fatal error: Uncaught Error: Cannot call abstract method fail::show() in %s:%d |
||||
Stack trace: |
||||
#0 [internal function]: pass->error() |
||||
#1 Unknown(0) : %s |
||||
#2 {main} |
||||
thrown in %s on line %d |
||||
@ -0,0 +1,32 @@ |
||||
--TEST-- |
||||
ZE2 An abstract class cannot be instantiated |
||||
--FILE-- |
||||
<?php |
||||
|
||||
abstract class fail { |
||||
abstract function show(); |
||||
} |
||||
|
||||
class pass extends fail { |
||||
function show() { |
||||
echo "Call to function show()\n"; |
||||
} |
||||
} |
||||
function main() { |
||||
$t2 = new pass(); |
||||
$t2->show(); |
||||
|
||||
$t = new fail(); |
||||
$t->show(); |
||||
|
||||
echo "Done\n"; // shouldn't be displayed |
||||
} |
||||
?> |
||||
--EXPECTF-- |
||||
Call to function show() |
||||
|
||||
Fatal error: Uncaught Error: Cannot instantiate abstract class fail in %s:%d |
||||
Stack trace: |
||||
#0 Unknown(0) : %s |
||||
#1 {main} |
||||
thrown in %s on line %d |
||||
@ -0,0 +1,16 @@ |
||||
--TEST-- |
||||
ZE2 A final method cannot be abstract |
||||
--SKIPIF-- |
||||
<?php die('skip, failed at compile time'); ?> |
||||
--FILE-- |
||||
<?php |
||||
|
||||
class fail { |
||||
abstract final function show(); |
||||
} |
||||
function main() { |
||||
echo "Done\n"; // Shouldn't be displayed |
||||
} |
||||
?> |
||||
--EXPECTF-- |
||||
Fatal error: Cannot use the final modifier on an abstract method in %s on line %d |
||||
@ -0,0 +1,20 @@ |
||||
--TEST-- |
||||
Verifies the correct conversion of objects to arrays |
||||
--FILE-- |
||||
<?php |
||||
class foo |
||||
{ |
||||
private $private = 'private'; |
||||
protected $protected = 'protected'; |
||||
public $public = 'public'; |
||||
} |
||||
function main() { |
||||
var_export((array) new foo); |
||||
} |
||||
?> |
||||
--EXPECT-- |
||||
array ( |
||||
'' . "\0" . 'foo' . "\0" . 'private' => 'private', |
||||
'' . "\0" . '*' . "\0" . 'protected' => 'protected', |
||||
'public' => 'public', |
||||
) |
||||
@ -0,0 +1,32 @@ |
||||
--TEST-- |
||||
ZE2 assign_op property of overloaded object |
||||
--FILE-- |
||||
<?php |
||||
|
||||
class Test { |
||||
private $real_a = 2; |
||||
|
||||
function __set($property, $value) { |
||||
if ($property == "a") { |
||||
$this->real_a = $value; |
||||
} |
||||
} |
||||
|
||||
function __get($property) { |
||||
if ($property == "a") { |
||||
return $this->real_a; |
||||
} |
||||
} |
||||
} |
||||
function main() { |
||||
$obj = new Test; |
||||
var_dump($obj->a); |
||||
$obj->a += 2; |
||||
var_dump($obj->a); |
||||
echo "---Done---\n"; |
||||
} |
||||
?> |
||||
--EXPECT-- |
||||
int(2) |
||||
int(4) |
||||
---Done--- |
||||
Loading…
Reference in new issue