Commit ddfcf0246e

Matt Chudleigh <matt.chudleigh@gmail.com>
2022-11-24 17:13:22
Bug fix: Prevent uninitialized parse nodes
If a parse node is reserved but never set the node remains uninitialized and can crash tools doing a linear scan of the nodes (like ZLS) when switching on the tag.
1 parent c4cac21
Changed files (1)
lib
std
lib/std/zig/parse.zig
@@ -131,11 +131,23 @@ const Parser = struct {
         return @intCast(Node.Index, i);
     }
 
-    fn reserveNode(p: *Parser) !usize {
+    fn reserveNode(p: *Parser, tag: Ast.Node.Tag) !usize {
         try p.nodes.resize(p.gpa, p.nodes.len + 1);
+        p.nodes.items(.tag)[p.nodes.len - 1] = tag;
         return p.nodes.len - 1;
     }
 
+    fn unreserveNode(p: *Parser, node_index: usize) void {
+        if (p.nodes.len == node_index) {
+            p.nodes.resize(p.gpa, p.nodes.len - 1) catch unreachable;
+        } else {
+            // There is zombie node left in the tree, let's make it as inoffensive as possible
+            // (sadly there's no no-op node)
+            p.nodes.items(.tag)[node_index] = .unreachable_literal;
+            p.nodes.items(.main_token)[node_index] = p.tok_i;
+        }
+    }
+
     fn addExtra(p: *Parser, extra: anytype) Allocator.Error!Node.Index {
         const fields = std.meta.fields(@TypeOf(extra));
         try p.extra_data.ensureUnusedCapacity(p.gpa, fields.len);
@@ -637,13 +649,15 @@ const Parser = struct {
                     return fn_proto;
                 },
                 .l_brace => {
-                    const fn_decl_index = try p.reserveNode();
-                    const body_block = try p.parseBlock();
-                    assert(body_block != 0);
                     if (is_extern) {
                         try p.warnMsg(.{ .tag = .extern_fn_body, .token = extern_export_inline_token });
                         return null_node;
                     }
+                    const fn_decl_index = try p.reserveNode(.fn_decl);
+                    errdefer p.unreserveNode(fn_decl_index);
+
+                    const body_block = try p.parseBlock();
+                    assert(body_block != 0);
                     return p.setNode(fn_decl_index, .{
                         .tag = .fn_decl,
                         .main_token = p.nodes.items(.main_token)[fn_proto],
@@ -724,7 +738,8 @@ const Parser = struct {
         const fn_token = p.eatToken(.keyword_fn) orelse return null_node;
 
         // We want the fn proto node to be before its children in the array.
-        const fn_proto_index = try p.reserveNode();
+        const fn_proto_index = try p.reserveNode(.fn_proto);
+        errdefer p.unreserveNode(fn_proto_index);
 
         _ = p.eatToken(.identifier);
         const params = try p.parseParamDeclList();