From 54be6d92c6045c0ee21c98a810643c5d1873deba Mon Sep 17 00:00:00 2001 From: nullptrdevs <16590917+nullptrdevs@users.noreply.github.com> Date: Thu, 25 Aug 2022 13:17:38 -0700 Subject: [PATCH] fix: fix server crash related to parsing incomplete function as a function's parameter fixes zigtools#567 --- src/analysis.zig | 15 ++++--- src/ast.zig | 96 +++++++++++++++++++++++++++++++++++++++++ src/inlay_hints.zig | 6 +-- src/references.zig | 4 +- src/semantic_tokens.zig | 2 +- src/signature_help.zig | 2 +- 6 files changed, 111 insertions(+), 14 deletions(-) diff --git a/src/analysis.zig b/src/analysis.zig index 01d2932..92849e3 100644 --- a/src/analysis.zig +++ b/src/analysis.zig @@ -135,7 +135,7 @@ pub fn getFunctionSnippet(allocator: std.mem.Allocator, tree: Ast, func: Ast.ful var it = func.iterate(&tree); var i: usize = 0; - while (it.next()) |param| : (i += 1) { + while (ast.nextFnParam(&it)) |param| : (i += 1) { if (skip_self_param and i == 0) continue; if (i != @boolToInt(skip_self_param)) try buf_stream.writeAll(", ${") @@ -172,7 +172,7 @@ pub fn getFunctionSnippet(allocator: std.mem.Allocator, tree: Ast, func: Ast.ful try buf_stream.print("{}", .{fmtSnippetPlaceholder(tree.tokenSlice(curr_token))}); if (is_comma or tag == .keyword_const) try buf_stream.writeByte(' '); } - } else unreachable; + } // else Incomplete and that's ok :) try buf_stream.writeByte('}'); } @@ -188,7 +188,7 @@ pub fn hasSelfParam(arena: *std.heap.ArenaAllocator, document_store: *DocumentSt const tree = handle.tree; var it = func.iterate(&tree); - const param = it.next().?; + const param = ast.nextFnParam(&it).?; if (param.type_expr == 0) return false; const token_starts = tree.tokens.items(.start); @@ -245,7 +245,7 @@ pub fn isTypeFunction(tree: Ast, func: Ast.full.FnProto) bool { pub fn isGenericFunction(tree: Ast, func: Ast.full.FnProto) bool { var it = func.iterate(&tree); - while (it.next()) |param| { + while (ast.nextFnParam(&it)) |param| { if (param.anytype_ellipsis3 != null or param.comptime_noalias != null) { return true; } @@ -714,7 +714,7 @@ pub fn resolveTypeOfNodeInternal(store: *DocumentStore, arena: *std.heap.ArenaAl var it = fn_decl.iterate(&decl.handle.tree); if (token_tags[call.ast.lparen - 2] == .period) { if (try hasSelfParam(arena, store, decl.handle, fn_decl)) { - _ = it.next(); + _ = ast.nextFnParam(&it); expected_params -= 1; } } @@ -722,7 +722,7 @@ pub fn resolveTypeOfNodeInternal(store: *DocumentStore, arena: *std.heap.ArenaAl // Bind type params to the arguments passed in the call. const param_len = std.math.min(call.ast.params.len, expected_params); var i: usize = 0; - while (it.next()) |decl_param| : (i += 1) { + while (ast.nextFnParam(&it)) |decl_param| : (i += 1) { if (i >= param_len) break; if (!isMetaType(decl.handle.tree, decl_param.type_expr)) continue; @@ -808,6 +808,7 @@ pub fn resolveTypeOfNodeInternal(store: *DocumentStore, arena: *std.heap.ArenaAl }, .field_access => { if (datas[node].rhs == 0) return null; + if (node >= tree.nodes.len - 1) return null; // #boundsCheck const rhs_str = tree.tokenSlice(datas[node].rhs); // If we are accessing a pointer type, remove one pointerness level :) const left_type = try resolveFieldAccessLhsType( @@ -2605,7 +2606,7 @@ fn makeScopeInternal(allocator: std.mem.Allocator, context: ScopeContext, node_i const scope_idx = scopes.items.len - 1; var it = func.iterate(&tree); - while (it.next()) |param| { + while (ast.nextFnParam(&it)) |param| { // Add parameter decls if (param.name_token) |name_token| { if (try scopes.items[scope_idx].decls.fetchPut( diff --git a/src/ast.zig b/src/ast.zig index c895196..86761e2 100644 --- a/src/ast.zig +++ b/src/ast.zig @@ -1119,3 +1119,99 @@ pub fn blockStatements(tree: Ast, node: Ast.Node.Index, buf: *[2]Ast.Node.Index) else => return null, }; } + +/// Iterates over FnProto Params w/ added bounds check to support incomplete ast nodes +pub fn nextFnParam(it: *Ast.full.FnProto.Iterator) ?Ast.full.FnProto.Param { + const token_tags = it.tree.tokens.items(.tag); + while (true) { + var first_doc_comment: ?Ast.TokenIndex = null; + var comptime_noalias: ?Ast.TokenIndex = null; + var name_token: ?Ast.TokenIndex = null; + if (!it.tok_flag) { + if (it.param_i >= it.fn_proto.ast.params.len) { + return null; + } + const param_type = it.fn_proto.ast.params[it.param_i]; + var tok_i = it.tree.firstToken(param_type) - 1; + while (true) : (tok_i -= 1) switch (token_tags[tok_i]) { + .colon => continue, + .identifier => name_token = tok_i, + .doc_comment => first_doc_comment = tok_i, + .keyword_comptime, .keyword_noalias => comptime_noalias = tok_i, + else => break + }; + it.param_i += 1; + it.tok_i = it.tree.lastToken(param_type) + 1; + + // #boundsCheck + // https://github.com/zigtools/zls/issues/567 + if (it.tree.lastToken(param_type) >= it.tree.tokens.len - 1) + return Ast.full.FnProto.Param{ + .first_doc_comment = first_doc_comment, + .comptime_noalias = comptime_noalias, + .name_token = name_token, + .anytype_ellipsis3 = null, + .type_expr = 0, + }; + + // Look for anytype and ... params afterwards. + if (token_tags[it.tok_i] == .comma) { + it.tok_i += 1; + } + it.tok_flag = true; + return Ast.full.FnProto.Param{ + .first_doc_comment = first_doc_comment, + .comptime_noalias = comptime_noalias, + .name_token = name_token, + .anytype_ellipsis3 = null, + .type_expr = param_type, + }; + } + if (token_tags[it.tok_i] == .comma) { + it.tok_i += 1; + } + if (token_tags[it.tok_i] == .r_paren) { + return null; + } + if (token_tags[it.tok_i] == .doc_comment) { + first_doc_comment = it.tok_i; + while (token_tags[it.tok_i] == .doc_comment) { + it.tok_i += 1; + } + } + switch (token_tags[it.tok_i]) { + .ellipsis3 => { + it.tok_flag = false; // Next iteration should return null. + return Ast.full.FnProto.Param{ + .first_doc_comment = first_doc_comment, + .comptime_noalias = null, + .name_token = null, + .anytype_ellipsis3 = it.tok_i, + .type_expr = 0, + }; + }, + .keyword_noalias, .keyword_comptime => { + comptime_noalias = it.tok_i; + it.tok_i += 1; + }, + else => {}, + } + if (token_tags[it.tok_i] == .identifier and + token_tags[it.tok_i + 1] == .colon) + { + name_token = it.tok_i; + it.tok_i += 2; + } + if (token_tags[it.tok_i] == .keyword_anytype) { + it.tok_i += 1; + return Ast.full.FnProto.Param{ + .first_doc_comment = first_doc_comment, + .comptime_noalias = comptime_noalias, + .name_token = name_token, + .anytype_ellipsis3 = it.tok_i - 1, + .type_expr = 0, + }; + } + it.tok_flag = false; + } +} diff --git a/src/inlay_hints.zig b/src/inlay_hints.zig index 8e34196..403e1fa 100644 --- a/src/inlay_hints.zig +++ b/src/inlay_hints.zig @@ -106,10 +106,10 @@ fn writeCallHint(builder: *Builder, arena: *std.heap.ArenaAllocator, store: *Doc var it = fn_proto.iterate(&decl_tree); if (try analysis.hasSelfParam(arena, store, decl_handle.handle, fn_proto)) { - _ = it.next(); + _ = ast.nextFnParam(&it); } - while (it.next()) |param| : (i += 1) { + while (ast.nextFnParam(&it)) |param| : (i += 1) { if (param.name_token == null) continue; if (i >= call.ast.params.len) break; @@ -577,7 +577,7 @@ fn writeNodeInlayHint(builder: *Builder, arena: *std.heap.ArenaAllocator, store: const fn_proto: Ast.full.FnProto = ast.fnProto(tree, node, &buffer).?; var it = fn_proto.iterate(&tree); - while (it.next()) |param_decl| { + while (ast.nextFnParam(&it)) |param_decl| { try await @asyncCall(child_frame, {}, writeNodeInlayHint, .{ builder, arena, store, param_decl.type_expr, range }); } diff --git a/src/references.zig b/src/references.zig index 050a8fe..368f735 100644 --- a/src/references.zig +++ b/src/references.zig @@ -136,7 +136,7 @@ fn symbolReferencesInternal(arena: *std.heap.ArenaAllocator, store: *DocumentSto var buf: [1]Ast.Node.Index = undefined; const fn_proto = ast.fnProto(tree, node, &buf).?; var it = fn_proto.iterate(&tree); - while (it.next()) |param| { + while (ast.nextFnParam(&it)) |param| { if (param.type_expr != 0) try symbolReferencesInternal(arena, store, .{ .node = param.type_expr, .handle = handle }, decl, encoding, context, handler); } @@ -527,7 +527,7 @@ pub fn symbolReferences(arena: *std.heap.ArenaAllocator, store: *DocumentStore, var buf: [1]Ast.Node.Index = undefined; const fn_proto = ast.fnProto(curr_handle.tree, proto, &buf).?; var it = fn_proto.iterate(&curr_handle.tree); - while (it.next()) |candidate| { + while (ast.nextFnParam(&it)) |candidate| { if (std.meta.eql(candidate, param)) { if (curr_handle.tree.nodes.items(.tag)[proto] == .fn_decl) { try symbolReferencesInternal( diff --git a/src/semantic_tokens.zig b/src/semantic_tokens.zig index d35e38c..d8af3d5 100644 --- a/src/semantic_tokens.zig +++ b/src/semantic_tokens.zig @@ -462,7 +462,7 @@ fn writeNodeTokens(builder: *Builder, arena: *std.heap.ArenaAllocator, store: *D try writeTokenMod(builder, fn_proto.name_token, func_name_tok_type, tok_mod); var it = fn_proto.iterate(&tree); - while (it.next()) |param_decl| { + while (ast.nextFnParam(&it)) |param_decl| { if (param_decl.first_doc_comment) |docs| try writeDocComments(builder, tree, docs); try writeToken(builder, param_decl.comptime_noalias, .keyword); diff --git a/src/signature_help.zig b/src/signature_help.zig index cb9e08b..8609e63 100644 --- a/src/signature_help.zig +++ b/src/signature_help.zig @@ -24,7 +24,7 @@ fn fnProtoToSignatureInfo(document_store: *DocumentStore, arena: *std.heap.Arena var params = std.ArrayListUnmanaged(ParameterInformation){}; var param_it = proto.iterate(&tree); - while (param_it.next()) |param| { + while (ast.nextFnParam(¶m_it)) |param| { const param_comments = if (param.first_doc_comment) |dc| try analysis.collectDocComments(alloc, tree, dc, .Markdown, false) else