astgen: fix OPT() sentinel crashes in rlExpr and exprRl

The C parser uses OPT() macro which stores UINT32_MAX as the "none"
sentinel for optional AST node indices in extra_data. The rlExpr
(AstRlAnnotate) and exprRl functions were checking `!= 0` for these
fields, treating UINT32_MAX as a valid node index and causing segfaults.

Fixed optional field checks for fn_proto_one, fn_proto extra data
(param, align, addrspace, section, callconv), while cont_expr,
global_var_decl type_node, and slice_sentinel end_node.

Also added behavior test corpus files and FAIL: diagnostic to
the corpus test runner.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-14 16:08:46 +00:00
parent 23beb0aad2
commit e5f71beb02
2 changed files with 54 additions and 25 deletions

View File

@@ -5866,6 +5866,8 @@ static uint32_t exprRl(GenZir* gz, Scope* scope, ResultLoc rl, uint32_t node) {
const Ast* stree = ag->tree;
uint32_t start_node = stree->extra_data.arr[nd.rhs];
uint32_t end_node = stree->extra_data.arr[nd.rhs + 1];
if (end_node == UINT32_MAX)
end_node = 0; // OPT() sentinel
uint32_t sentinel_node = stree->extra_data.arr[nd.rhs + 2];
// slice_length optimization (AstGen.zig:887-906).
if (end_node != 0 && stree->nodes.tags[nd.lhs] == AST_NODE_SLICE_OPEN
@@ -14258,6 +14260,8 @@ static bool rlExpr(
} else if (tag == AST_NODE_LOCAL_VAR_DECL
|| tag == AST_NODE_GLOBAL_VAR_DECL) {
type_node = tree->extra_data.arr[nd.lhs];
if (type_node == UINT32_MAX)
type_node = 0; // OPT() sentinel
init_node = nd.rhs;
} else { // ALIGNED_VAR_DECL
init_node = nd.rhs;
@@ -14498,6 +14502,8 @@ static bool rlExpr(
body_node = tree->extra_data.arr[nd.rhs + 1];
} else {
cont_node = tree->extra_data.arr[nd.rhs];
if (cont_node == UINT32_MAX)
cont_node = 0; // OPT() sentinel
body_node = tree->extra_data.arr[nd.rhs + 1];
else_node = tree->extra_data.arr[nd.rhs + 2];
}
@@ -14635,7 +14641,7 @@ static bool rlExpr(
ss.end = tree->extra_data.arr[nd.rhs + 1];
ss.sentinel = tree->extra_data.arr[nd.rhs + 2];
(void)rlExpr(ag, ss.start, block, RL_RI_TYPE_ONLY);
if (ss.end != 0)
if (ss.end != UINT32_MAX)
(void)rlExpr(ag, ss.end, block, RL_RI_TYPE_ONLY);
(void)rlExpr(ag, ss.sentinel, block, RL_RI_NONE);
return false;
@@ -15047,16 +15053,16 @@ static bool rlExpr(
fp.addrspace_expr = tree->extra_data.arr[pnd.lhs + 2];
fp.section_expr = tree->extra_data.arr[pnd.lhs + 3];
fp.callconv_expr = tree->extra_data.arr[pnd.lhs + 4];
if (fp.param != 0)
if (fp.param != UINT32_MAX)
(void)rlExpr(ag, fp.param, block, RL_RI_TYPE_ONLY);
if (fp.align_expr != 0)
if (fp.align_expr != UINT32_MAX)
(void)rlExpr(ag, fp.align_expr, block, RL_RI_TYPE_ONLY);
if (fp.addrspace_expr != 0)
if (fp.addrspace_expr != UINT32_MAX)
(void)rlExpr(
ag, fp.addrspace_expr, block, RL_RI_TYPE_ONLY);
if (fp.section_expr != 0)
if (fp.section_expr != UINT32_MAX)
(void)rlExpr(ag, fp.section_expr, block, RL_RI_TYPE_ONLY);
if (fp.callconv_expr != 0)
if (fp.callconv_expr != UINT32_MAX)
(void)rlExpr(ag, fp.callconv_expr, block, RL_RI_TYPE_ONLY);
} else if (ptag == AST_NODE_FN_PROTO) {
return_type = pnd.rhs;
@@ -15070,14 +15076,14 @@ static bool rlExpr(
for (uint32_t i = fp.params_start; i < fp.params_end; i++)
(void)rlExpr(
ag, tree->extra_data.arr[i], block, RL_RI_TYPE_ONLY);
if (fp.align_expr != 0)
if (fp.align_expr != UINT32_MAX)
(void)rlExpr(ag, fp.align_expr, block, RL_RI_TYPE_ONLY);
if (fp.addrspace_expr != 0)
if (fp.addrspace_expr != UINT32_MAX)
(void)rlExpr(
ag, fp.addrspace_expr, block, RL_RI_TYPE_ONLY);
if (fp.section_expr != 0)
if (fp.section_expr != UINT32_MAX)
(void)rlExpr(ag, fp.section_expr, block, RL_RI_TYPE_ONLY);
if (fp.callconv_expr != 0)
if (fp.callconv_expr != UINT32_MAX)
(void)rlExpr(ag, fp.callconv_expr, block, RL_RI_TYPE_ONLY);
}
} else {
@@ -15101,16 +15107,16 @@ static bool rlExpr(
fp.addrspace_expr = tree->extra_data.arr[nd.lhs + 2];
fp.section_expr = tree->extra_data.arr[nd.lhs + 3];
fp.callconv_expr = tree->extra_data.arr[nd.lhs + 4];
if (fp.param != 0)
if (fp.param != UINT32_MAX)
(void)rlExpr(ag, fp.param, block, RL_RI_TYPE_ONLY);
if (fp.align_expr != 0)
if (fp.align_expr != UINT32_MAX)
(void)rlExpr(ag, fp.align_expr, block, RL_RI_TYPE_ONLY);
if (fp.addrspace_expr != 0)
if (fp.addrspace_expr != UINT32_MAX)
(void)rlExpr(
ag, fp.addrspace_expr, block, RL_RI_TYPE_ONLY);
if (fp.section_expr != 0)
if (fp.section_expr != UINT32_MAX)
(void)rlExpr(ag, fp.section_expr, block, RL_RI_TYPE_ONLY);
if (fp.callconv_expr != 0)
if (fp.callconv_expr != UINT32_MAX)
(void)rlExpr(ag, fp.callconv_expr, block, RL_RI_TYPE_ONLY);
} else if (tag == AST_NODE_FN_PROTO) {
return_type = nd.rhs;
@@ -15124,14 +15130,14 @@ static bool rlExpr(
for (uint32_t i = fp.params_start; i < fp.params_end; i++)
(void)rlExpr(
ag, tree->extra_data.arr[i], block, RL_RI_TYPE_ONLY);
if (fp.align_expr != 0)
if (fp.align_expr != UINT32_MAX)
(void)rlExpr(ag, fp.align_expr, block, RL_RI_TYPE_ONLY);
if (fp.addrspace_expr != 0)
if (fp.addrspace_expr != UINT32_MAX)
(void)rlExpr(
ag, fp.addrspace_expr, block, RL_RI_TYPE_ONLY);
if (fp.section_expr != 0)
if (fp.section_expr != UINT32_MAX)
(void)rlExpr(ag, fp.section_expr, block, RL_RI_TYPE_ONLY);
if (fp.callconv_expr != 0)
if (fp.callconv_expr != UINT32_MAX)
(void)rlExpr(ag, fp.callconv_expr, block, RL_RI_TYPE_ONLY);
}
}

View File

@@ -286,7 +286,7 @@ fn expectEqualZir(gpa: Allocator, ref: Zir, got: c.Zir) !void {
for (0..265) |t| {
if (ref_counts[t] != got_counts[t])
std.debug.print("tag {d}: ref={d} got={d} (diff={d})\n", .{
t, ref_counts[t], got_counts[t],
t, ref_counts[t], got_counts[t],
@as(i32, @intCast(got_counts[t])) - @as(i32, @intCast(ref_counts[t])),
});
}
@@ -787,6 +787,27 @@ const corpus_files = .{
.{ "parser_test.zig", @embedFile("parser_test.zig") },
.{ "test_all.zig", @embedFile("test_all.zig") },
.{ "tokenizer_test.zig", @embedFile("tokenizer_test.zig") },
.{ "optional.zig", @embedFile("../test/behavior/optional.zig") },
.{ "call.zig", @embedFile("../test/behavior/call.zig") },
.{ "pointers.zig", @embedFile("../test/behavior/pointers.zig") },
.{ "type.zig", @embedFile("../test/behavior/type.zig") },
.{ "enum.zig", @embedFile("../test/behavior/enum.zig") },
.{ "switch_on_captured_error.zig", @embedFile("../test/behavior/switch_on_captured_error.zig") },
.{ "error.zig", @embedFile("../test/behavior/error.zig") },
.{ "switch.zig", @embedFile("../test/behavior/switch.zig") },
.{ "array.zig", @embedFile("../test/behavior/array.zig") },
.{ "slice.zig", @embedFile("../test/behavior/slice.zig") },
.{ "basic.zig", @embedFile("../test/behavior/basic.zig") },
.{ "packed-struct.zig", @embedFile("../test/behavior/packed-struct.zig") },
.{ "eval.zig", @embedFile("../test/behavior/eval.zig") },
.{ "field_parent_ptr.zig", @embedFile("../test/behavior/field_parent_ptr.zig") },
.{ "struct.zig", @embedFile("../test/behavior/struct.zig") },
.{ "floatop.zig", @embedFile("../test/behavior/floatop.zig") },
.{ "union.zig", @embedFile("../test/behavior/union.zig") },
.{ "math.zig", @embedFile("../test/behavior/math.zig") },
.{ "vector.zig", @embedFile("../test/behavior/vector.zig") },
.{ "cast.zig", @embedFile("../test/behavior/cast.zig") },
};
fn corpusCheck(gpa: Allocator, source: [:0]const u8) !void {
@@ -941,11 +962,12 @@ test "astgen: corpus multi_array_list.zig" {
try corpusCheck(gpa, @embedFile("../lib/std/multi_array_list.zig"));
}
test "astgen: corpus Sema.zig" {
if (true) return error.SkipZigTest; // TODO: too large, work on smaller files first
const gpa = std.testing.allocator;
try corpusCheck(gpa, @embedFile("../src/Sema.zig"));
}
// Later much later
//test "astgen: corpus Sema.zig" {
// if (true) return error.SkipZigTest; // TODO: too large, work on smaller files first
// const gpa = std.testing.allocator;
// try corpusCheck(gpa, @embedFile("../src/Sema.zig"));
//}
test "astgen: enum decl" {
const gpa = std.testing.allocator;
@@ -981,6 +1003,7 @@ test "astgen: corpus" {
var any_fail = false;
inline for (corpus_files) |entry| {
corpusCheck(gpa, entry[1]) catch {
std.debug.print("FAIL: {s}\n", .{entry[0]});
any_fail = true;
};
}