diff --git a/docs/UNSUPPORTED_SYNTAX.md b/docs/UNSUPPORTED_SYNTAX.md
index bc411883..df5d84f9 100644
--- a/docs/UNSUPPORTED_SYNTAX.md
+++ b/docs/UNSUPPORTED_SYNTAX.md
@@ -1427,7 +1427,7 @@ var_dump($s); // 输出 "foo bar"
**描述**: 函数参数声明为引用传递同时带有默认值
**示例代码**:
-```php
+```
innerHTML = '
Hello
';
$content = $element->innerHTML;
@@ -1641,6 +1641,568 @@ $body = $doc->body->innerHTML; // 不支持
---
+### 9. 重复的函数名和类名
+
+**状态**: 不支持
+**PHP 版本**: 所有版本
+**描述**: AOT 编译器禁止在同一作用域内定义同名的函数或类,而标准 PHP 允许这种情况(后定义的会覆盖先前的定义)
+
+**示例代码**:
+```
+ 1)跳出或继续多层嵌套循环结构。只支持 `break` 和 `continue`(不带数字参数或参数为 1)。
+
+**示例代码**:
+```
+ 50) {
+ break 2; // ❌ 错误:不支持跳出多层循环
+ }
+ }
+}
+
+// ❌ AOT 编译器不支持:continue 3 继续外层循环
+for ($i = 0; $i < 10; $i++) {
+ for ($j = 0; $j < 10; $j++) {
+ for ($k = 0; $k < 10; $k++) {
+ if ($i + $j + $k > 20) {
+ continue 3; // ❌ 错误:不支持继续多层循环
+ }
+ }
+ }
+}
+
+// ✅ 正确做法:使用 goto
+for ($i = 0; $i < 10; $i++) {
+ for ($j = 0; $j < 10; $j++) {
+ if ($i * $j > 50) {
+ goto end_loop; // ✅ 使用 goto 跳出
+ }
+ }
+}
+end_loop:
+echo "Loop ended";
+
+// ✅ 正确做法:使用标志变量
+$found = false;
+for ($i = 0; $i < 10 && !$found; $i++) {
+ for ($j = 0; $j < 10 && !$found; $j++) {
+ if ($i * $j > 50) {
+ $found = true; // ✅ 设置标志
+ }
+ }
+}
+
+// ✅ 正确做法:提取为函数并使用 return
+function findValue() {
+ for ($i = 0; $i < 10; $i++) {
+ for ($j = 0; $j < 10; $j++) {
+ if ($i * $j > 50) {
+ return [$i, $j]; // ✅ 直接返回
+ }
+ }
+ }
+ return null;
+}
+
+$result = findValue();
+```
+
+**与标准 PHP 的区别**:
+
+在标准 PHP 中:
+```php
+ 50) {
+ break 2; // 跳出两层循环
+ }
+ echo "$i * $j = " . ($i * $j) . "\n";
+ }
+}
+echo "Done";
+```
+
+在 AOT 编译器中:
+```php
+ 50) {
+ break 2; // Compile Error: break/continue with level > 1 is not supported
+ }
+ }
+}
+```
+
+**原因**:
+- 多层 break/continue 需要复杂的控制流分析
+- C++ 后端没有直接对应的语法结构
+- 生成的代码难以优化和维护
+- 容易导致代码逻辑混乱,降低可读性
+- 与现代编程最佳实践不符
+
+**相关测试文件**:
+- 涉及 `break N` 或 `continue N` (N > 1) 的测试文件 (SKIP)
+
+**替代方案**:
+
+1. **使用 goto 语句**
+ ```php
+ 20) {
+ goto exit_loops; // 跳出所有循环
+ }
+ }
+ }
+ }
+ exit_loops:
+ echo "Exited loops at i=$i, j=$j, k=$k";
+ ```
+
+2. **使用标志变量**
+ ```php
+ 50) {
+ $should_break = true;
+ }
+ }
+ }
+ ```
+
+3. **提取为函数并使用 return**
+ ```php
+ $row) {
+ foreach ($row as $j => $value) {
+ if ($value === $target) {
+ return [$i, $j]; // 找到即返回
+ }
+ }
+ }
+ return null;
+ }
+
+ $result = searchMatrix($matrix, 42);
+ ```
+
+4. **使用 try/catch 异常机制**
+ ```php
+ 50) {
+ throw new LoopBreakException("Found at $i, $j");
+ }
+ }
+ }
+ } catch (LoopBreakException $e) {
+ echo $e->getMessage();
+ }
+ ```
+
+5. **重构为单层循环**
+ ```php
+ 50) {
+ break; // 只需普通 break
+ }
+ }
+ ```
+
+**最佳实践建议**:
+
+- ✅ **优先使用函数提取**:将复杂的多层循环逻辑提取为独立函数,使用 return 退出
+- ✅ **使用标志变量**:对于简单的双层循环,使用布尔标志更清晰
+- ⚠️ **谨慎使用 goto**:虽然有效,但过度使用会降低代码可读性
+- ⚠️ **避免深层嵌套**:如果需要使用 break 3 或更高层级,考虑重构代码结构
+- ❌ **不要依赖异常控制流程**:try/catch 方案仅作为最后手段,性能较差
+
+---
+
+### 11. 字符串越界访问行为差异
+
+**状态**: 行为不一致
+**PHP 版本**: 所有版本
+**描述**: AOT 编译器对字符串越界访问的处理与标准 PHP 不同。当访问超出字符串长度的索引时,AOT 编译器会抛出致命错误(Fatal Error),而标准 PHP 只会产生警告(Warning)并返回空字符串。
+
+**示例代码**:
+```
+= 0 && $index < strlen($str)) {
+ $char = $str[$index];
+ echo $char;
+ } else {
+ echo "Index out of bounds";
+ }
+ ```
+
+2. **使用三元运算符**
+ ```php
+ = 0 && $index < strlen($str)) ? $str[$index] : '';
+ echo $char; // 输出空字符串
+ ```
+
+3. **封装安全访问函数**
+ ```php
+ = 0 && $index < strlen($str)) {
+ return $str[$index];
+ }
+ return ''; // 返回空字符串
+ }
+
+ $str = "Hello";
+ echo safeCharAccess($str, 1); // 输出:e
+ echo safeCharAccess($str, 10); // 输出:(空)
+ echo safeCharAccess($str, -1); // 输出:(空)
+ ```
+
+4. **使用 substr 函数**
+ ```php
+ getMessage();
+ // 输出:Caught error: String offset `10` out of range
+ }
+ ```
+
+**最佳实践建议**:
+
+- ✅ **始终检查边界**:在访问字符串索引前验证索引范围
+- ✅ **使用辅助函数**:封装安全的字符访问逻辑,提高代码复用性
+- ✅ **明确错误处理**:对于可能越界的场景,使用 try/catch 或条件判断
+- ⚠️ **注意性能**:频繁的边界检查会影响性能,关键路径可考虑其他方案
+- ❌ **不要依赖警告**:AOT 编译器会将越界访问升级为致命错误
+- ❌ **避免硬编码索引**:使用变量和动态计算时要格外小心
+
+**迁移指南**:
+
+如果你的代码在标准 PHP 中依赖越界访问返回空字符串的行为,需要进行以下修改:
+
+```php
+ 0) ? $str[0] : '';
+
+// 或者
+$firstChar = safeCharAccess($str, 0);
+```
+
+**常见陷阱**:
+
+1. **空字符串访问**
+ ```php
+ 0 ? $str[0] : '';
+ ```
+
+2. **循环中的索引**
+ ```php
+ 0) {
+ echo $input[0];
+ }
+ ```
+
+---
+
### 7. 游离代码(全局可执行表达式)
**状态**: 不支持
@@ -1648,7 +2210,7 @@ $body = $doc->body->innerHTML; // 不支持
**描述**: 在函数或类方法之外执行的可执行表达式
**示例代码**:
-```php
+```
getCreatedAt();
对于不支持的测试文件,需要在 `--FILE--` 之前添加 `--SKIPIF--` 部分:
-```php
+```
--TEST--
测试描述
@@ -1953,18 +2515,26 @@ echo "skip Generator syntax not supported in AOT";
| 类别 | 数量 | 百分比 |
|------|------|--------|
-| 不支持的语法 | 9 | - |
+| 不支持的语法 | 12 | - |
| 计划支持的语法 | 2 | - |
-| 已支持的语法 | 50+ | ~85% |
+| 已支持的语法 | 50+ | ~81% |
**总测试文件数**: 126 个
-**Skip 测试数**: 9 个(根据实际标记数量)
-**正常测试数**: 117 个
+**Skip 测试数**: 12 个(根据实际标记数量)
+**正常测试数**: 114 个
---
## 📝 更新日志
+### 2024-04-07
+- 新增 3 个不支持的语法特性
+- **重复的函数名和类名**: AOT 编译器禁止在同一作用域内定义同名的函数或类,与标准 PHP 不同。PHP 允许后定义覆盖先前的定义,而 AOT 编译器会在编译时报错。
+- **break/continue 跳出多层循环**: AOT 编译器不支持 `break N` 或 `continue N` (N > 1) 跳出或继续多层嵌套循环。需要使用 goto、标志变量、函数返回或 try/catch 代替。
+- **字符串越界访问行为差异**: AOT 编译器对字符串越界访问抛出致命错误(Fatal Error),而标准 PHP 只产生警告(Warning)。需要添加边界检查或使用安全的访问方式。
+- 添加详细的对比说明和替代方案(命名空间、条件定义、goto、标志变量、边界检查等)
+- 更新统计信息和快速参考表
+
### 2024-03-20
- 新增 2 个不支持的语法特性
- **引用参数带有默认值**: 由于编译期内存管理复杂性,不支持 `function foo(array &$ref = [])`
@@ -1994,6 +2564,15 @@ echo "skip Generator syntax not supported in AOT";
### Q: 为什么这些语法不被支持?
A: AOT 编译器采用静态编译方式,某些 PHP 动态特性(如可变变量、生成器)需要在运行时动态解析,与 AOT 的设计理念冲突。
+### Q: AOT 编译器允许重复的函数名或类名吗?
+A: **不允许**。与标准 PHP 不同,AOT 编译器禁止在同一作用域内定义同名的函数或类。PHP 允许后定义覆盖先前的定义(会产生警告),而 AOT 编译器会在编译期直接报错。这是为了保证符号表的唯一性和编译的确定性。建议使用命名空间、不同的名称或条件定义来避免冲突。
+
+### Q: AOT 编译器支持 break 2 或 continue 2 跳出多层循环吗?
+A: **不支持**。AOT 编译器只支持普通的 `break` 和 `continue`(跳出或继续当前层循环),不支持 `break N` 或 `continue N` (N > 1) 跳出或继续多层嵌套循环。替代方案包括:使用 goto 语句、设置标志变量、将逻辑提取为函数并使用 return、或使用 try/catch 异常机制。推荐使用函数提取的方式,代码更清晰易维护。
+
+### Q: AOT 编译器的字符串越界访问行为与 PHP 有什么不同?
+A: **行为不一致**。标准 PHP 在访问超出字符串长度的索引时只会产生警告(Warning)并返回空字符串,程序继续执行。而 AOT 编译器会抛出致命错误(Fatal Error: String offset out of range),导致程序终止。这是因为 AOT 采用更严格的边界检查机制。建议在访问字符串索引前先检查长度,或使用 substr() 函数等安全方式。
+
### Q: 什么时候会支持 Traits?
A: Traits 已在开发计划中,具体支持时间取决于开发进度和社区需求。请查看项目路线图获取最新信息。
@@ -2037,10 +2616,13 @@ A: 游离代码指在函数或方法之外直接执行的可执行表达式(
| 游离代码 | ❌ 不支持 | 将所有代码放入 main() 函数 |
| **引用参数默认值** | ❌ 不支持 | 使用值传递 + 返回值或 null 默认值 |
| **变长引用参数** | ❌ 不支持 | 使用数组参数代替 |
+| **重复函数/类名** | ❌ 不支持 | 使用命名空间、不同名称或条件定义 |
+| **break/continue N** | ❌ 不支持 | 使用 goto、标志变量、函数返回或 try/catch |
+| **字符串越界访问** | ⚠️ 行为不一致 | 添加边界检查或使用 substr()
### 正确的代码结构模板
-```php
+```
func || !ZEND_USER_CODE(frame->func->type))) {
- frame = frame->prev_execute_data;
- }
- return frame;
+ zend_execute_data *frame = EG(current_execute_data);
+ while (frame && (!frame->func || !ZEND_USER_CODE(frame->func->type))) {
+ frame = frame->prev_execute_data;
+ }
+ return frame;
}
php::Scope php_switch_scope(php::Object &this_) {
- php::Scope scope;
- scope.frame = get_frame();
- scope.ce = scope.frame->func->common.scope;
- scope.frame->func->common.scope = php_get_called_ce(this_);
- return scope;
+ php::Scope scope;
+ scope.frame = get_frame();
+ scope.ce = scope.frame->func->common.scope;
+ scope.frame->func->common.scope = php_get_called_ce(this_);
+ return scope;
}
void php_restore_scope(php::Scope &ori_scope) {
- ori_scope.frame->func->common.scope = ori_scope.ce;
+ ori_scope.frame->func->common.scope = ori_scope.ce;
}
void module_shutdown(zend_module_entry *module) {
@@ -82,15 +82,18 @@ int main(int cpp_argc, char **cpp_argv) {
#if PPROF_ON
ProfilerStart("profile.out");
#endif
- try {
- char path_translated[] = "embed";
- SG(request_info).path_translated = path_translated;
- module->request_startup_func(module->type, module->module_number);
- } catch (zend_object *e) {
- rc = EG(exit_status);
- CG(unclean_shutdown) = 1;
- zend_exception_error(e, E_ERROR);
+ zend_first_try {
+ try {
+ char path_translated[] = "embed";
+ SG(request_info).path_translated = path_translated;
+ module->request_startup_func(module->type, module->module_number);
+ } catch (zend_object *e) {
+ rc = EG(exit_status);
+ CG(unclean_shutdown) = 1;
+ zend_exception_error(e, E_ERROR);
+ }
}
+ zend_end_try();
#if PPROF_ON
ProfilerStop();
#endif
diff --git a/tests/core/func/006.phpt b/tests/core/func/006.phpt
index 077b6f87..334e5142 100644
--- a/tests/core/func/006.phpt
+++ b/tests/core/func/006.phpt
@@ -7,19 +7,21 @@ zlib.output_compression=0
zlib.output_handler=
--FILE--
--EXPECT--
string(2) "2B"
diff --git a/tests/core/strings/bug22592.phpt b/tests/core/strings/bug22592.phpt
new file mode 100644
index 00000000..c66e27d8
--- /dev/null
+++ b/tests/core/strings/bug22592.phpt
@@ -0,0 +1,27 @@
+--TEST--
+Bug #22592 (Cascading assignments to strings with curly braces broken)
+--FILE--
+
+--EXPECT--
+string(6) "abcdef"
+string(6) "abcdef"
+string(6) "a*c*e*"
+string(6) "a*c*e*"
diff --git a/tests/core/strings/bug26703.phpt b/tests/core/strings/bug26703.phpt
new file mode 100644
index 00000000..aaa7833f
--- /dev/null
+++ b/tests/core/strings/bug26703.phpt
@@ -0,0 +1,14 @@
+--TEST--
+Bug #26703 (Certain characters inside strings incorrectly treated as keywords)
+--INI--
+highlight.string=#DD0000
+highlight.comment=#FF9900
+highlight.keyword=#007700
+highlight.default=#0000BB
+highlight.html=#000000
+--FILE--
+');
+?>
+--EXPECT--
+<?php echo "foo[] $a \n"; ?>