a2ui-project / a2ui-project/a2ui

[BUG]: Expression parser's recursion depth guard is unreachable (Dart and web_core)

Đang mở
#2,490 2 bình luận 0 reaction 1 người được giao Được @Varun-S10 nhận Xem trên GitHub
P2 status: first-line-handled status: needs review type: bug
Ngôn ngữ chính
TypeScript
Star
16.4k
Fork
1.3k
Merge trung bình
2 ngày 13 giờ
Pull request đã merge (30 ngày)
134

Mô tả

- [x] I have searched the existing issues to make sure this bug has not already been reported.

## Describe the Bug

`ExpressionParser` declares a maximum recursion depth of 10, but nothing enforces it. The check lives in `parse()`, which is only ever entered at depth 0, while both recursive paths bypass it:

- **Nested interpolations** (`${${...}}`) recurse through `parseExpression()`, which accepts a `depth` but never compares it against the limit.
- **Function-call arguments** (`f(a: g(b: ...))`) recurse through `parseExpressionInternal()` with `depth` passed through unchanged, so that nesting is never counted at all.

Recursion on parse is therefore unbounded. Both implementations share the shape and the defect:

| | limit declared | reachable | deeply nested template |
| --- | --- | --- | --- |
| `dart/a2ui_core` | [`maxDepth = 10`](https://github.com/a2ui-project/a2ui/blob/676a8999936b17070195230d396c18d14a22d64d/dart/a2ui_core/lib/src/processing/expressions.dart#L20) | no | `StackOverflowError` |
| `renderers/web_core` | [`MAX_DEPTH = 10`](https://github.com/a2ui-project/a2ui/blob/676a8999936b17070195230d396c18d14a22d64d/renderers/web_core/src/v0_9/basic_catalog/expressions/expression_parser.ts#L29) | no | `RangeError: Maximum call stack size exceeded` |

The relevant lines in Dart are [L25](https://github.com/a2ui-project/a2ui/blob/676a8999936b17070195230d396c18d14a22d64d/dart/a2ui_core/lib/src/processing/expressions.dart#L25) (the only check), [L101](https://github.com/a2ui-project/a2ui/blob/676a8999936b17070195230d396c18d14a22d64d/dart/a2ui_core/lib/src/processing/expressions.dart#L101) (`parseExpression`, no check) and [L180](https://github.com/a2ui-project/a2ui/blob/676a8999936b17070195230d396c18d14a22d64d/dart/a2ui_core/lib/src/processing/expressions.dart#L180) (arguments, depth not incremented); in web_core they are [L36](https://github.com/a2ui-project/a2ui/blob/676a8999936b17070195230d396c18d14a22d64d/renderers/web_core/src/v0_9/basic_catalog/expressions/expression_parser.ts#L36), [L115](https://github.com/a2ui-project/a2ui/blob/676a8999936b17070195230d396c18d14a22d64d/renderers/web_core/src/v0_9/basic_catalog/expressions/expression_parser.ts#L115) and [L198](https://github.com/a2ui-project/a2ui/blob/676a8999936b17070195230d396c18d14a22d64d/renderers/web_core/src/v0_9/basic_catalog/expressions/expression_parser.ts#L198).

Nesting depth is agent-controlled: `formatString` hands its `value` argument straight to the parser, and that argument is an ordinary string in the message payload.

## Steps to Reproduce

Dart, against `dart/a2ui_core` at main (676a899):

```dart
final parser = ExpressionParser();
String nestedCalls(int n) => '\${${'f(a: ' * n}1${')' * n}}';

parser.parse(nestedCalls(1000)); // parses fine; the guard never fires
parser.parse(nestedCalls(5000)); // StackOverflowError
```

web_core, against the published `@a2ui/web_core@0.10.7`:

```js
const p = new ExpressionParser();
const nested = (n) => '${'.repeat(n) + 'x' + '}'.repeat(n);

p.parse(nested(1000)); // parses fine
p.parse(nested(5000)); // RangeError: Maximum call stack size exceeded
```

Measured for both nesting forms (`${f(a: f(a: ...))}` and `${${...}}`) in both languages:

| nesting depth | Dart | web_core |
| --- | --- | --- |
| 10 | parsed, no error | parsed, no error |
| 100 | parsed, no error | parsed, no error |
| 1000 | parsed, no error | parsed, no error |
| 5000 | `StackOverflowError` | `RangeError` |

## Expected Behavior

A template nested past the declared limit is rejected with `A2uiExpressionError`, which is what the guard was written to throw and what the rest of the expression layer raises. Today the caller instead gets a `StackOverflowError` / `RangeError`, outside the `A2uiError` hierarchy consumers handle — and in Dart, `avoid_catching_errors` (enabled repo-wide) discourages catching it at all.

## Environment Details

- **OS**: macOS
- **Browser/Platform**: Dart 3.12.2, Node.js 25.2.1
- **SDK/Package Name & Version**: `a2ui_core` at main (676a899), `@a2ui/web_core` 0.10.7
- **Protocol Version**: v0.9

## Additional Context

The fix has the same shape in both languages: move the depth check into `parseExpressionInternal()` — the single point both recursive paths pass through — and pass `depth + 1` when parsing function-call arguments.

I have the Dart side ready with tests and will open a PR for it shortly.

I am deliberately not sending the web_core change yet: #2433 is currently editing that same file to add template length and parts caps. Those caps are orthogonal to this one — they bound how large an input is, not how deeply it nests, so neither stops the recursion described here — and the fix is best applied on top of that PR rather than conflicting with it. Happy to send it as a follow-up once it lands.

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Hướng nghiên cứu

The bug is in the expression parsers in dart/a2ui_core/lib/src/processing/expressions.dart and renderers/web_core/src/v0_9/basic_catalog/expressions/expression_parser.ts. Start by reading the parseExpressionInternal method in both files to understand the recursion paths. The fix is to move the depth check into parseExpressionInternal and ensure depth is incremented when parsing function-call arguments. Verify the fix by running the provided reproduction code and ensuring an A2uiExpressionError is thrown for nesting >10.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Đánh giá

Công nghệ
dart, typescript
Lĩnh vực
compilers
Loại issue
Lỗi
Độ khó
3/5
Thời gian dự kiến
1-2 ngày
Mức độ hoạt động
Sôi nổi
Độ rõ ràng
Đặc tả rõ ràng
Mức phù hợp với người mới
65/100

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.