diff --git a/examples/array.php b/examples/array.php new file mode 100644 index 00000000..100c3f71 --- /dev/null +++ b/examples/array.php @@ -0,0 +1,23 @@ + +#include +#include + +using namespace php; + +Int php_test(Int a, Var b, Str c, Array d) { + php_var_dump(d, 10); + return 0; +} \ No newline at end of file diff --git a/examples/object_cast.php b/examples/object_cast.php new file mode 100644 index 00000000..f91a0754 --- /dev/null +++ b/examples/object_cast.php @@ -0,0 +1,6 @@ +scalar; // 输出 'ciao' +} \ No newline at end of file diff --git a/src/Php/Translator.php b/src/Php/Translator.php index 73da867d..44b714bd 100644 --- a/src/Php/Translator.php +++ b/src/Php/Translator.php @@ -29,6 +29,7 @@ class Translator extends \PhpAot\Core\Translator const string VALUE_INF = 'std::numeric_limits::infinity()'; const string LITERAL_STRINGS = '_literal_strings'; const string EXPR_VARIABLE = 'Expr_Variable'; + const string EXPR_NEW = 'Expr_New'; const string EXPR_ARRAY_DIM_FETCH = 'Expr_ArrayDimFetch'; private string $phpxDir = '~/workspace/projects/phpx'; @@ -130,6 +131,8 @@ class Translator extends \PhpAot\Core\Translator private CLImate $climate; private array $beforeStmtLines = []; private array $afterStmtLines = []; + private bool $inLoop = false; + private bool $inSwitch = false; public function __construct(string $rootPath) { @@ -507,19 +510,29 @@ class Translator extends \PhpAot\Core\Translator $result = $this->parseReturn($v) . ';'; break; case 'Stmt_For': + $this->inLoop = true; $result = $this->parseFor($v); + $this->inLoop = false; break; case 'Stmt_Foreach': + $this->inLoop = true; $result = $this->parseForeach($v); + $this->inLoop = false; break; case 'Stmt_Switch': + $this->inSwitch = true; $result = $this->parseSwitch($v); + $this->inSwitch = false; break; case 'Stmt_While': + $this->inLoop = true; $result = $this->parseWhile($v); + $this->inLoop = false; break; case 'Stmt_Do': + $this->inLoop = true; $result = $this->parseDo($v); + $this->inLoop = false; break; case 'Stmt_If': $result = $this->parseIf($v); @@ -527,6 +540,12 @@ class Translator extends \PhpAot\Core\Translator case 'Stmt_Break': $result = $this->parseBreak($v); break; + case 'Stmt_Goto': + $result = $this->parseGoto($v); + break; + case 'Stmt_Label': + $result = $this->parseLabel($v); + break; case 'Stmt_Continue': $result = 'continue;'; break; @@ -624,6 +643,8 @@ class Translator extends \PhpAot\Core\Translator return $this->parseAssignOpShiftRight($expr); case 'Expr_AssignOp_BitwiseAnd': return $this->parseAssignOpBitwiseAnd($expr); + case 'Expr_AssignOp_BitwiseXor': + return $this->parseAssignOpBitwiseXor($expr); case 'Expr_AssignOp_Pow': return $this->parseAssignOpPow($expr); case 'Expr_BinaryOp_Mul': @@ -681,6 +702,8 @@ class Translator extends \PhpAot\Core\Translator return $this->parseNew($expr); case 'Expr_Clone': return $this->parseClone($expr); + case 'Expr_Instanceof': + return $this->parseInstanceof($expr); case 'Expr_Throw': return $this->parseThrow($expr); case 'Expr_ShellExec': @@ -710,6 +733,8 @@ class Translator extends \PhpAot\Core\Translator return $this->parseCastString($expr); case 'Expr_Cast_Array': return $this->parseCastArray($expr); + case 'Expr_Cast_Object': + return $this->parseCastObject($expr); case 'Expr_ConstFetch': return $this->parseConstFetch($expr); case 'Expr_UnaryMinus': @@ -747,7 +772,7 @@ class Translator extends \PhpAot\Core\Translator } } elseif ($left->getType() === 'Expr_PropertyFetch') { $array = $this->parseIdentifier($left->var); - $propName = $this->parseIdentifier($left->name); + $propName = $this->identifierToStr($left->name); return "$array.setProperty($propName, " . $this->trimBrackets($this->parseExpr($right)) . ")"; } elseif ($right->getType() === 'Expr_Assign') { $chain[] = $left; @@ -1620,6 +1645,14 @@ class Translator extends \PhpAot\Core\Translator return $expr; } + private function convertObjectExpr(string $expr): string + { + if (!$this->isClosedCall($expr, 'php::to_object')) { + return 'php::to_object(' . $this->trimBrackets($expr) . ')'; + } + return $expr; + } + private function convertArrayExpr(string $expr): string { if (!$this->isClosedCall($expr, 'php::to_array')) { @@ -1706,6 +1739,12 @@ class Translator extends \PhpAot\Core\Translator return $var . '.clone()'; } + private function parseInstanceof(Node $expr): string + { + $var = $this->parseIdentifier($expr->expr); + return $var . '.instanceOf(' . $this->identifierToStr($expr->class) . ')'; + } + private function parseNamespaceDef(Node $node): string { $this->namespace = $this->parseIdentifier($node->name); @@ -1769,6 +1808,11 @@ class Translator extends \PhpAot\Core\Translator return $this->convertBoolExpr($this->parseExpr($node->expr)); } + private function parseCastObject(mixed $node): string + { + return $this->convertObjectExpr($this->parseExpr($node->expr)); + } + private function parseConstFetch(Node $expr): string { $name = $this->parseIdentifier($expr->name); @@ -1922,6 +1966,12 @@ class Translator extends \PhpAot\Core\Translator return $var . ' >>= ' . $this->parseIdentifier($node->expr); } + private function parseAssignOpBitwiseXor(Node $node): string + { + $var = $this->parseIdentifier($node->var); + return $var . ' ^= ' . $this->parseIdentifier($node->expr); + } + private function parseMagicConstFile(mixed $expr): string { return '"' . $this->escapeString($this->file) . '"'; @@ -1952,7 +2002,7 @@ class Translator extends \PhpAot\Core\Translator if ($this->hasGlobalVar($keyVar)) { $this->fatalError($node->keyVar, 'Cannot redefine key variable: ' . $this->unescapeVarName($keyVar)); } - $code .= $this->getIndent() . self::TYPE_VAR . ' ' . $keyVar . ' = iter.key();' . PHP_EOL; + $code .= $this->getIndent() . ' ' . $keyVar . ' = iter.key();' . PHP_EOL; if (!$this->hasVar($keyVar)) { $this->addLocalVar($keyVar, self::TYPE_VAR); } @@ -1970,7 +2020,7 @@ class Translator extends \PhpAot\Core\Translator if ($this->hasGlobalVar($valueVar)) { $this->fatalError($node->valueVar, 'Cannot redefine value variable: ' . $this->unescapeVarName($valueVar)); } - $code .= $this->getIndent() . self::TYPE_VAR . ' ' . $valueVar . ' = iter.value();' . PHP_EOL; + $code .= $this->getIndent() . ' ' . $valueVar . ' = iter.value();' . PHP_EOL; if (!$this->hasVar($valueVar)) { $this->addLocalVar($valueVar, self::TYPE_VAR); } @@ -2095,11 +2145,14 @@ class Translator extends \PhpAot\Core\Translator private function parseBreak(mixed $v): string { + if (!$this->inLoop and !$this->inSwitch) { + $this->fatalError($v, 'Cannot break outside loop'); + } $num = $v->num; if ($num) { $value = $this->parseIdentifier($num); if ($value > 1) { - abort($v); + $this->fatalError($v, 'Cannot break more than 1 level'); } } return 'break;'; @@ -2327,6 +2380,9 @@ class Translator extends \PhpAot\Core\Translator private function parseThrow(mixed $expr): string { + if ($expr->expr->getType() != self::EXPR_VARIABLE and $expr->expr->getType() != self::EXPR_NEW) { + $this->fatalError($expr, 'The throw statement only accepts a object variable'); + } return 'php::throwException(' . $this->parseIdentifier($expr->expr). ')'; } @@ -2393,4 +2449,16 @@ class Translator extends \PhpAot\Core\Translator { return 'php::call("shell_exec", {' . $this->parseInterpolatedString($expr) . '})'; } + + private function parseGoto(Node $v): string + { + $this->fatalError($v, 'Goto statement is not supported'); + return 'goto ' . $v->name->name . ';'; + } + + private function parseLabel(Node $v): string + { + $this->fatalError($v, 'Label statement is not supported'); + return $v->name->name . ':'; + } } diff --git a/tests/zend/add_002.phpt b/tests/zend/add_002.phpt new file mode 100644 index 00000000..9077ece0 --- /dev/null +++ b/tests/zend/add_002.phpt @@ -0,0 +1,28 @@ +--TEST-- +adding objects to arrays +--FILE-- +prop = "value"; + +try { + var_dump($a + $o); +} catch (Error $e) { + echo "\nException: " . $e->getMessage() . "\n"; +} + +$c = $a + $o; +var_dump($c); + +echo "Done\n"; +?> +--EXPECTF-- +Exception: Unsupported operand types: array + stdClass + +Fatal error: Uncaught TypeError: Unsupported operand types: array + stdClass in %s:%d +Stack trace: +#0 {main} + thrown in %s on line %d diff --git a/tests/zend/add_003.phpt b/tests/zend/add_003.phpt new file mode 100644 index 00000000..6d27863e --- /dev/null +++ b/tests/zend/add_003.phpt @@ -0,0 +1,28 @@ +--TEST-- +adding arrays to objects +--FILE-- +prop = "value"; + +try { + var_dump($o + $a); +} catch (Error $e) { + echo "\nException: " . $e->getMessage() . "\n"; +} + +$c = $o + $a; +var_dump($c); + +echo "Done\n"; +?> +--EXPECTF-- +Exception: Unsupported operand types: stdClass + array + +Fatal error: Uncaught TypeError: Unsupported operand types: stdClass + array in %s:%d +Stack trace: +#0 {main} + thrown in %s on line %d diff --git a/tests/zend/add_004.phpt b/tests/zend/add_004.phpt new file mode 100644 index 00000000..46cbd0b2 --- /dev/null +++ b/tests/zend/add_004.phpt @@ -0,0 +1,23 @@ +--TEST-- +adding numbers to arrays +--FILE-- +getMessage() . "\n"; +} + +$c = $a + 5; +var_dump($c); + +echo "Done\n"; +?> +--EXPECTF-- +int(6) +int(6) +Done + diff --git a/tests/zend/add_006.phpt b/tests/zend/add_006.phpt new file mode 100644 index 00000000..7fe48491 --- /dev/null +++ b/tests/zend/add_006.phpt @@ -0,0 +1,56 @@ +--TEST-- +adding numbers to strings +--INI-- +precision=14 +--FILE-- +getMessage() . \PHP_EOL; +} +$c = $i + $s2; +var_dump($c); + +$c = $i + $s3; +var_dump($c); + +$c_float = $i + floatval($s4); +var_dump($c_float); + +try { + $c = $s1 + $i; + var_dump($c); +} catch (\TypeError $e) { + echo $e->getMessage() . \PHP_EOL; +} + +$c = $s2 + $i; +var_dump($c); + +$c = $s3 + $i; +var_dump($c); + +$c_float3 = floatval($s4) + $i; +var_dump($c_float3); + +echo "Done\n"; +?> +--EXPECTF-- +int(75636) +int(951858) +int(48550510) +float(75661.68) +int(75636) +int(951858) +int(48550510) +float(75661.68) +Done diff --git a/tests/zend/add_007.phpt b/tests/zend/add_007.phpt new file mode 100644 index 00000000..a2beddfa --- /dev/null +++ b/tests/zend/add_007.phpt @@ -0,0 +1,27 @@ +--TEST-- +adding strings to arrays +--FILE-- +getMessage() . "\n"; +} + +$c = $a + $s1; +var_dump($c); + +echo "Done\n"; +?> +--EXPECTF-- +Exception: Unsupported operand types: array + string + +Fatal error: Uncaught TypeError: Unsupported operand types: array + string in %s:%d +Stack trace: +#0 {main} + thrown in %s on line %d diff --git a/tests/zend/assign_dim_op_same_var.phpt b/tests/zend/assign_dim_op_same_var.phpt new file mode 100644 index 00000000..e841354e --- /dev/null +++ b/tests/zend/assign_dim_op_same_var.phpt @@ -0,0 +1,18 @@ +--TEST-- +Compound array assignment with same variable +--FILE-- + +--EXPECT-- +array(1) { + [0]=> + array(0) { + } +} diff --git a/tests/zend/assign_dim_op_undef.phpt b/tests/zend/assign_dim_op_undef.phpt new file mode 100644 index 00000000..bf0330ed --- /dev/null +++ b/tests/zend/assign_dim_op_undef.phpt @@ -0,0 +1,21 @@ +--TEST-- +Compound array assign with undefined variables +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +Warning: Undefined variable $a in %s on line %d + +Warning: Undefined variable $b in %s on line %d + +Deprecated: Using null as an array offset is deprecated, use an empty string instead in %s on line %d + +Warning: Undefined array key "" in %s on line %d +array(1) { + [""]=> + int(1) +} diff --git a/tests/zend/break_error_001.phpt b/tests/zend/break_error_001.phpt new file mode 100644 index 00000000..426fdebf --- /dev/null +++ b/tests/zend/break_error_001.phpt @@ -0,0 +1,16 @@ +--TEST-- +'break' error (non positive integers) +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +Fatal error: 'break' operator accepts only positive integers in %sbreak_error_001.php on line 3 diff --git a/tests/zend/break_error_002.phpt b/tests/zend/break_error_002.phpt new file mode 100644 index 00000000..1da0d61e --- /dev/null +++ b/tests/zend/break_error_002.phpt @@ -0,0 +1,12 @@ +--TEST-- +'break' error (operator with non-integer operand) +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +Fatal error: 'break' operator with non-integer operand is no longer supported in %sbreak_error_002.php on line 3 diff --git a/tests/zend/break_error_003.phpt b/tests/zend/break_error_003.phpt new file mode 100644 index 00000000..1ef21012 --- /dev/null +++ b/tests/zend/break_error_003.phpt @@ -0,0 +1,12 @@ +--TEST-- +'break' error (not in the loop context) +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +Fatal error: 'break' not in the 'loop' or 'switch' context in %sbreak_error_003.php on line 3 diff --git a/tests/zend/break_error_004.phpt b/tests/zend/break_error_004.phpt new file mode 100644 index 00000000..e725d614 --- /dev/null +++ b/tests/zend/break_error_004.phpt @@ -0,0 +1,17 @@ +--TEST-- +'break' error (wrong level) +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +Fatal error: Cannot 'break' 2 levels in %sbreak_error_004.php on line 4 diff --git a/tests/zend/exceptions/exception_001.phpt b/tests/zend/exceptions/exception_001.phpt new file mode 100644 index 00000000..aba29d4a --- /dev/null +++ b/tests/zend/exceptions/exception_001.phpt @@ -0,0 +1,38 @@ +--TEST-- +Testing nested exceptions +--FILE-- +getMessage()); + throw $e; + } + } catch (Exception $e) { + var_dump($e->getMessage()); + throw $e; + } + } catch (Exception $e) { + var_dump($e->getMessage()); + throw $e; + } +} catch (Exception $e) { + var_dump($e->getMessage()); + throw $e; +} + +?> +--EXPECTF-- +string(0) "" +string(0) "" +string(0) "" +string(0) "" + +Fatal error: Uncaught Exception in %s:%d +Stack trace: +#0 {main} + thrown in %s on line %d diff --git a/tests/zend/exceptions/exception_002.phpt b/tests/zend/exceptions/exception_002.phpt new file mode 100644 index 00000000..3790fea6 --- /dev/null +++ b/tests/zend/exceptions/exception_002.phpt @@ -0,0 +1,21 @@ +--TEST-- +Testing exception and GOTO +--SKIPIF-- + +--FILE-- + +--EXPECT-- +2 diff --git a/tests/zend/exceptions/exception_003.phpt b/tests/zend/exceptions/exception_003.phpt new file mode 100644 index 00000000..b8efa94b --- /dev/null +++ b/tests/zend/exceptions/exception_003.phpt @@ -0,0 +1,13 @@ +--TEST-- +Throwing exception in global scope +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught Exception: 1 in %s:%d +Stack trace: +#0 {main} + thrown in %s on line %d diff --git a/tests/zend/exceptions/exception_006.phpt b/tests/zend/exceptions/exception_006.phpt new file mode 100644 index 00000000..698d07f3 --- /dev/null +++ b/tests/zend/exceptions/exception_006.phpt @@ -0,0 +1,15 @@ +--TEST-- +Trying to throw a non-object +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught Error: Can only throw objects in %s:%d +Stack trace: +#0 {main} + thrown in %s on line %d diff --git a/tests/zend/exceptions/exception_007.phpt b/tests/zend/exceptions/exception_007.phpt new file mode 100644 index 00000000..ffce60e0 --- /dev/null +++ b/tests/zend/exceptions/exception_007.phpt @@ -0,0 +1,35 @@ +--TEST-- +Setting previous exception +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught Exception: Another in %s +Stack trace: +#0 {main} + +Next Exception: First in %s +Stack trace: +#0 {main} + +Next Exception: Second in %s +Stack trace: +#0 {main} + +Next Exception: Third in %s +Stack trace: +#0 {main} + thrown in %s diff --git a/tests/zend/exceptions/exception_011.phpt b/tests/zend/exceptions/exception_011.phpt new file mode 100644 index 00000000..e566eb48 --- /dev/null +++ b/tests/zend/exceptions/exception_011.phpt @@ -0,0 +1,17 @@ +--TEST-- +Test exception doesn't cause RSHUTDOWN bypass, variation 0 +--INI-- +zend.assertions=1 +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught AssertionError %s +Stack trace: +#0 [internal function]: assert(false) +#1 {main} + thrown in %s diff --git a/tests/zend/exceptions/exception_015.phpt b/tests/zend/exceptions/exception_015.phpt new file mode 100644 index 00000000..3ae6b808 --- /dev/null +++ b/tests/zend/exceptions/exception_015.phpt @@ -0,0 +1,20 @@ +--TEST-- +Exceptions on improper access to string +--FILE-- +getMessage() . " in " , $e->getFile() . " on line " . $e->getLine() . "\n"; +} + +$s[] = "D"; +?> +--EXPECTF-- +Exception: [] operator not supported for strings in %s + +Fatal error: Uncaught Error: [] operator not supported for strings %s +Stack trace: +#0 {main} + thrown in %s diff --git a/tests/zend/exceptions/exception_019.phpt b/tests/zend/exceptions/exception_019.phpt new file mode 100644 index 00000000..5701a4cb --- /dev/null +++ b/tests/zend/exceptions/exception_019.phpt @@ -0,0 +1,14 @@ +--TEST-- +Testing throw exception doesn't crash with wrong params, variant 2 +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught TypeError: Exception::__construct(): Argument #1 ($message) must be of type string, stdClass given in %s:%d +Stack trace: +#0 %s: Exception->__construct(Object(stdClass)) +#1 {main} + thrown in %s on line %d diff --git a/tests/zend/exceptions/exception_022.phpt b/tests/zend/exceptions/exception_022.phpt new file mode 100644 index 00000000..297c51a3 --- /dev/null +++ b/tests/zend/exceptions/exception_022.phpt @@ -0,0 +1,14 @@ +--TEST-- +Testing throw exception doesn't crash with wrong params, variant 4 +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught TypeError: Error::__construct(): Argument #1 ($message) must be of type string, stdClass given in %s:%d +Stack trace: +#0 %s: Error->__construct(Object(stdClass)) +#1 {main} + thrown in %s on line %d diff --git a/tests/zend/xor_001.phpt b/tests/zend/xor_001.phpt new file mode 100644 index 00000000..9678af16 --- /dev/null +++ b/tests/zend/xor_001.phpt @@ -0,0 +1,19 @@ +--TEST-- +XORing arrays +--FILE-- +getMessage(), "\n"; +} + +echo "Done\n"; +?> +--EXPECT-- +Unsupported operand types: array ^ array +Done diff --git a/tests/zend/xor_002.phpt b/tests/zend/xor_002.phpt new file mode 100644 index 00000000..cbcfa3a9 --- /dev/null +++ b/tests/zend/xor_002.phpt @@ -0,0 +1,39 @@ +--TEST-- +XORing strings +--FILE-- + +--EXPECT-- +string(6) "030107" +string(6) "030107" +string(8) "070a1e11" +string(8) "070a1e11" +string(8) "070a1e11" +string(8) "070a1e11" +Done diff --git a/tests/zend/xor_003.phpt b/tests/zend/xor_003.phpt new file mode 100644 index 00000000..4ed98f34 --- /dev/null +++ b/tests/zend/xor_003.phpt @@ -0,0 +1,19 @@ +--TEST-- +XORing booleans +--FILE-- + +--EXPECT-- +int(1) +int(0) +int(0) +Done diff --git a/tests/zend/zend_operators.phpt b/tests/zend/zend_operators.phpt new file mode 100644 index 00000000..769640bf --- /dev/null +++ b/tests/zend/zend_operators.phpt @@ -0,0 +1,12 @@ +--TEST-- +Operator precedence +--FILE-- + +--EXPECT-- +bool(true) +bool(true)