commit 492ed18b7e86639918d2bc2fc599fd93d00eff78 (tree)
parent 93720cb68b37f60e3c5c17dc2e1910db2d028445
Author: Motiejus Jakštys <motiejus@jakstys.lt>
Date: Sat, 14 Feb 2026 16:08:46 +0000
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>
Diffstat:
2 files changed, 54 insertions(+), 25 deletions(-)
diff --git a/stage0/astgen.c b/stage0/astgen.c
@@ -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);
}
}
diff --git a/stage0/astgen_test.zig b/stage0/astgen_test.zig
@@ -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;
};
}