fix: fix server crash related to parsing incomplete function as a function's parameter

fixes zigtools#567
This commit is contained in:
nullptrdevs 2022-08-25 13:17:38 -07:00
parent 8cf96fe27c
commit 54be6d92c6
6 changed files with 111 additions and 14 deletions

View File

@ -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(

View File

@ -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;
}
}

View File

@ -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 });
}

View File

@ -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(

View File

@ -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);

View File

@ -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(&param_it)) |param| {
const param_comments = if (param.first_doc_comment) |dc|
try analysis.collectDocComments(alloc, tree, dc, .Markdown, false)
else