authorgravatar for mail@isaacfreund.comIsaac Freund <mail@isaacfreund.com> 2026-07-04 13:05:28+02:00
committergravatar for mail@isaacfreund.comIsaac Freund <mail@isaacfreund.com> 2026-07-08 11:49:01+02:00
log203e8279539220db84823bb79671c5c6c7d1065f
tree371be758619ecfb8fc1bc733d199d6fbfdfcd43b
parente5230c5e8aa95c03e62554ba31bd2f2c7ff86ed9
signaturelock-open Commit is signed but in an unrecognized format.

parser: check binary op whitespace before parsing rhs


3 files changed, 23 insertions(+), 4 deletions(-)

lib/std/zig/Parse.zig+13-4
...@@ -1637,13 +1637,17 @@ fn parseExprPrecedence(p: *Parse, min_prec: i32) Error!?Node.Index {...@@ -1637,13 +1637,17 @@ fn parseExprPrecedence(p: *Parse, min_prec: i32) Error!?Node.Index {
1637 if (tok_tag == .keyword_catch) {1637 if (tok_tag == .keyword_catch) {
1638 _ = try p.parsePayload();1638 _ = try p.parsePayload();
1639 }1639 }
1640 const rhs = try p.parseExprPrecedence(info.prec + 1) orelse {
1641 try p.warn(.expected_expr);
1642 return node;
1643 };
16441640
1641 // Check for whitespace error before parsing the rhs to facilitate fuzzing.
1642 // Consider the case where there is a mismatched whitespace error but the rhs
1643 // expression triggers stack overflow when parsing is attempted. In this case
1644 // we want to return with an error rather than crashing on stack overflow.
1645 {1645 {
1646 const tok_len = tok_tag.lexeme().?.len;1646 const tok_len = tok_tag.lexeme().?.len;
1647 if (p.tokenStart(oper_token) + tok_len >= p.source.len) {
1648 try p.warn(.expected_expr);
1649 return node;
1650 }
1647 const char_before = p.source[p.tokenStart(oper_token) - 1];1651 const char_before = p.source[p.tokenStart(oper_token) - 1];
1648 const char_after = p.source[p.tokenStart(oper_token) + tok_len];1652 const char_after = p.source[p.tokenStart(oper_token) + tok_len];
1649 if (tok_tag == .ampersand and char_after == '&') {1653 if (tok_tag == .ampersand and char_after == '&') {
...@@ -1655,6 +1659,11 @@ fn parseExprPrecedence(p: *Parse, min_prec: i32) Error!?Node.Index {...@@ -1655,6 +1659,11 @@ fn parseExprPrecedence(p: *Parse, min_prec: i32) Error!?Node.Index {
1655 }1659 }
1656 }1660 }
16571661
1662 const rhs = try p.parseExprPrecedence(info.prec + 1) orelse {
1663 try p.warn(.expected_expr);
1664 return node;
1665 };
1666
1658 node = try p.addNode(.{1667 node = try p.addNode(.{
1659 .tag = info.tag,1668 .tag = info.tag,
1660 .main_token = oper_token,1669 .main_token = oper_token,
lib/std/zig/parser_fuzz.zig+9
...@@ -131,6 +131,15 @@ test "nosuspend multi assign" {...@@ -131,6 +131,15 @@ test "nosuspend multi assign" {
131 try checkAgainstOracle("test{nosuspend*0,var _=0;}");131 try checkAgainstOracle("test{nosuspend*0,var _=0;}");
132}132}
133133
134// Found using AFL++
135test "bin op at end of file" {
136 try checkAgainstOracle(
137 \\test {
138 \\ _ = 00
139 \\|
140 );
141}
142
134fn checkAgainstOracle(source: [:0]const u8) !void {143fn checkAgainstOracle(source: [:0]const u8) !void {
135 var fba_buf: [1 << 18]u8 = undefined;144 var fba_buf: [1 << 18]u8 = undefined;
136 var fba: std.heap.FixedBufferAllocator = .init(&fba_buf);145 var fba: std.heap.FixedBufferAllocator = .init(&fba_buf);
lib/std/zig/parser_test.zig+1
...@@ -6976,6 +6976,7 @@ test "recovery: missing comma" {...@@ -6976,6 +6976,7 @@ test "recovery: missing comma" {
6976 , &[_]Error{6976 , &[_]Error{
6977 .expected_comma_after_switch_prong,6977 .expected_comma_after_switch_prong,
6978 .expected_comma_after_switch_prong,6978 .expected_comma_after_switch_prong,
6979 .mismatched_binary_op_whitespace,
6979 .expected_expr,6980 .expected_expr,
6980 });6981 });
6981}6982}