Skip to content

Commit fd5369c

Browse files
committed
Reviewing and updating the require errdefer deinit rule
1 parent 680f8d4 commit fd5369c

File tree

1 file changed

+104
-51
lines changed

1 file changed

+104
-51
lines changed

src/rules/require_errdefer_dealloc.zig

Lines changed: 104 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ pub fn buildRule(options: zlinter.rules.RuleOptions) zlinter.rules.LintRule {
6363
fn run(
6464
rule: zlinter.rules.LintRule,
6565
doc: zlinter.session.LintDocument,
66-
allocator: std.mem.Allocator,
66+
gpa: std.mem.Allocator,
6767
options: zlinter.rules.RunOptions,
6868
) error{OutOfMemory}!?zlinter.results.LintResult {
6969
const config = options.getConfig(Config);
@@ -72,41 +72,60 @@ fn run(
7272
const tree = doc.handle.tree;
7373

7474
var problem_nodes = shims.ArrayList(Ast.Node.Index).empty;
75-
defer problem_nodes.deinit(allocator);
75+
defer problem_nodes.deinit(gpa);
7676

7777
const root: NodeIndexShim = .root;
78-
var it = try doc.nodeLineageIterator(root, allocator);
78+
var it = try doc.nodeLineageIterator(
79+
root,
80+
gpa,
81+
);
7982
defer it.deinit();
8083

84+
var arena: std.heap.ArenaAllocator = .init(gpa);
85+
defer arena.deinit();
86+
8187
nodes: while (try it.next()) |tuple| {
82-
const node, _ = tuple;
88+
const node = tuple[0].toNodeIndex();
89+
90+
var buffer: [1]Ast.Node.Index = undefined;
91+
const fn_decl = zlinter.ast.fnDecl(
92+
tree,
93+
node,
94+
&buffer,
95+
) orelse continue :nodes;
8396

84-
var fn_proto_buffer: [1]Ast.Node.Index = undefined;
85-
const fn_decl = zlinter.ast.fnDecl(tree, node.toNodeIndex(), &fn_proto_buffer) orelse continue :nodes;
97+
if (!zlinter.ast.fnProtoReturnsError(tree, fn_decl.proto))
98+
continue :nodes;
8699

87-
if (!zlinter.ast.fnProtoReturnsError(tree, fn_decl.proto)) continue :nodes;
100+
try processBlock(
101+
doc,
102+
fn_decl.block,
103+
&problem_nodes,
104+
gpa,
105+
arena.allocator(),
106+
);
88107

89-
try processBlock(doc, fn_decl.block, &problem_nodes, allocator);
108+
_ = arena.reset(.retain_capacity);
90109
}
91110

92111
var lint_problems: shims.ArrayList(zlinter.results.LintProblem) = .empty;
93-
defer lint_problems.deinit(allocator);
112+
defer lint_problems.deinit(gpa);
94113

95114
for (problem_nodes.items) |node| {
96-
try lint_problems.append(allocator, .{
115+
try lint_problems.append(gpa, .{
97116
.rule_id = rule.rule_id,
98117
.severity = config.severity,
99118
.start = .startOfNode(tree, node),
100119
.end = .endOfNode(tree, node),
101-
.message = try std.fmt.allocPrint(allocator, "Missing `errdefer` cleanup", .{}),
120+
.message = try std.fmt.allocPrint(gpa, "Missing `errdefer` cleanup", .{}),
102121
});
103122
}
104123

105124
return if (lint_problems.items.len > 0)
106125
try zlinter.results.LintResult.init(
107-
allocator,
126+
gpa,
108127
doc.path,
109-
try lint_problems.toOwnedSlice(allocator),
128+
try lint_problems.toOwnedSlice(gpa),
110129
)
111130
else
112131
null;
@@ -117,40 +136,56 @@ fn processBlock(
117136
block_node: Ast.Node.Index,
118137
problems: *shims.ArrayList(Ast.Node.Index),
119138
gpa: std.mem.Allocator,
139+
arena: std.mem.Allocator,
120140
) !void {
121141
const tree = doc.handle.tree;
122142

123-
var cleanup_symbols: std.StringHashMap(Ast.Node.Index) = .init(gpa);
124-
defer {
125-
var it = cleanup_symbols.keyIterator();
126-
while (it.next()) |k| gpa.free(k.*);
127-
cleanup_symbols.deinit();
128-
}
143+
// Populated with declarations that look like they should be cleaned up.
144+
var cleanup_symbols: std.StringHashMap(Ast.Node.Index) = .init(arena);
129145

130146
var call_buffer: [1]Ast.Node.Index = undefined;
131147
for (doc.lineage.items(.children)[NodeIndexShim.init(block_node).index] orelse &.{}) |child_node| {
132148
if (try declRef(doc, child_node)) |decl_ref| {
133-
if (decl_ref.hasDeinit())
134-
try cleanup_symbols.put(try gpa.dupe(u8, decl_ref.var_name), child_node);
135-
} else if (try zlinter.ast.deferBlock(doc, child_node, gpa)) |defer_block| {
136-
defer defer_block.deinit(gpa);
137-
149+
// Track declarations that look like they need to be cleaned up.
150+
if (!decl_ref.requiresCleanup()) continue;
151+
152+
try cleanup_symbols.put(
153+
try arena.dupe(u8, decl_ref.var_name),
154+
child_node,
155+
);
156+
} else if (try zlinter.ast.deferBlock(
157+
doc,
158+
child_node,
159+
arena,
160+
)) |defer_block| {
161+
// Remove any tracked declarations that are cleaned up within defer/errdefer
138162
for (defer_block.children) |defer_block_child| {
139-
const cleanup_call = callWithName(doc, defer_block_child, &call_buffer, &.{"deinit"}) orelse continue;
140-
switch (cleanup_call.kind) {
163+
const call = callWithName(
164+
doc,
165+
defer_block_child,
166+
&call_buffer,
167+
&.{"deinit"},
168+
) orelse continue;
169+
switch (call.kind) {
141170
.single_field => |info| {
142-
if (cleanup_symbols.fetchRemove(tree.tokenSlice(info.field_main_token))) |e| gpa.free(e.key);
171+
_ = cleanup_symbols.remove(tree.tokenSlice(info.field_main_token));
143172
},
144173
.enum_literal, .other => {},
145174
}
146175
}
147176
} else if (zlinter.ast.isBlock(tree, child_node)) {
148-
try processBlock(doc, child_node, problems, gpa);
177+
try processBlock(
178+
doc,
179+
child_node,
180+
problems,
181+
gpa,
182+
arena,
183+
);
149184
}
150185
}
151186

152-
var leftover_it = cleanup_symbols.valueIterator();
153-
while (leftover_it.next()) |node| {
187+
var remaining_it = cleanup_symbols.valueIterator();
188+
while (remaining_it.next()) |node| {
154189
try problems.append(gpa, node.*);
155190
}
156191
}
@@ -270,6 +305,8 @@ const DeclRef = struct {
270305
name: []const u8,
271306
uri: []const u8,
272307

308+
// TODO: This really need a lot of work, it's just a quick hack to get
309+
// something going to see how useful such a rule is.
273310
const deinit_references = std.StaticStringMap([]const u8).initComptime(.{
274311
.{ "ArrayHashMap", "std/array_hash_map.zig" },
275312
.{ "ArrayHashMapUnmanaged", "std/array_hash_map.zig" },
@@ -299,7 +336,7 @@ const DeclRef = struct {
299336
.{ "StringHashMapUnmanaged", "std/hash_map.zig" },
300337
});
301338

302-
fn hasDeinit(self: DeclRef) bool {
339+
fn requiresCleanup(self: DeclRef) bool {
303340
if (deinit_references.get(self.name)) |uri_suffix| {
304341
return std.mem.endsWith(u8, self.uri, uri_suffix);
305342
}
@@ -308,21 +345,37 @@ const DeclRef = struct {
308345
};
309346

310347
fn declRef(doc: zlinter.session.LintDocument, var_decl_node: Ast.Node.Index) !?DeclRef {
311-
const var_decl = doc.handle.tree.fullVarDecl(var_decl_node) orelse return null;
312-
313-
const init_node = NodeIndexShim.initOptional(var_decl.ast.init_node) orelse return null;
348+
const tree = doc.handle.tree;
349+
const var_decl = tree.fullVarDecl(var_decl_node) orelse return null;
350+
const init_node = (NodeIndexShim.initOptional(var_decl.ast.init_node) orelse return null).toNodeIndex();
314351

315352
var call_buffer: [1]Ast.Node.Index = undefined;
316-
if (!switch (shims.nodeTag(doc.handle.tree, init_node.toNodeIndex())) {
317-
.field_access => zlinter.ast.isFieldVarAccess(doc.handle.tree, init_node.toNodeIndex(), &.{"empty"}),
318-
.enum_literal => zlinter.ast.isEnumLiteral(doc.handle.tree, init_node.toNodeIndex(), &.{"empty"}),
353+
if (!switch (shims.nodeTag(tree, init_node)) {
354+
// e.g., `ArrayList(u8).empty`
355+
.field_access => zlinter.ast.isFieldVarAccess(
356+
tree,
357+
init_node,
358+
&.{"empty"},
359+
),
360+
// e.g., `.empty`
361+
.enum_literal => zlinter.ast.isEnumLiteral(
362+
tree,
363+
init_node,
364+
&.{"empty"},
365+
),
319366
else =>
320367
// This will also handle optional and array accesses, which shouldn't occur
321368
// but shouldn't be a problem to us anyway as we do strict checks on the
322369
// type anyway.
323370
//
324-
// e.g., `array[0].init()` and `optional.?.init()`
325-
if (callWithName(doc, init_node.toNodeIndex(), &call_buffer, &.{"init"})) |call|
371+
// e.g., `array[0].init(gpa)` and `optional.?.init(allocator)`
372+
if (callWithName(
373+
doc,
374+
init_node,
375+
&call_buffer,
376+
&.{"init"},
377+
)) |call|
378+
// TODO: This is fine for managed but what about unmanaged, which is now the standard?
326379
!hasArenaParam(doc, call.params)
327380
else
328381
false,
@@ -345,42 +398,42 @@ fn declRef(doc: zlinter.session.LintDocument, var_decl_node: Ast.Node.Index) !?D
345398
.container_decl_two,
346399
.container_decl_two_trailing,
347400
=> {
348-
const tree = scope_handle.handle.tree;
349-
const first_token = tree.firstToken(node);
401+
const scope_tree = scope_handle.handle.tree;
402+
const first_token = scope_tree.firstToken(node);
350403

351404
// `Foo = struct`
352-
if (first_token > 1 and tree.tokens.items(.tag)[first_token - 2] == .identifier and tree.tokens.items(.tag)[first_token - 1] == .equal) {
405+
if (first_token > 1 and scope_tree.tokens.items(.tag)[first_token - 2] == .identifier and scope_tree.tokens.items(.tag)[first_token - 1] == .equal) {
353406
var str_token = first_token - 2;
354407
// `Foo: type = struct`
355-
if (first_token > 3 and tree.tokens.items(.tag)[first_token - 4] == .identifier and tree.tokens.items(.tag)[first_token - 3] == .colon) {
408+
if (first_token > 3 and scope_tree.tokens.items(.tag)[first_token - 4] == .identifier and scope_tree.tokens.items(.tag)[first_token - 3] == .colon) {
356409
str_token = first_token - 4;
357410
}
358411
return .{
359-
.var_name = doc.handle.tree.tokenSlice(var_decl.ast.mut_token + 1),
360-
.name = tree.tokenSlice(str_token),
412+
.var_name = tree.tokenSlice(var_decl.ast.mut_token + 1),
413+
.name = scope_tree.tokenSlice(str_token),
361414
.uri = scope_handle.handle.uri,
362415
};
363-
} else if (first_token > 0 and tree.tokens.items(.tag)[first_token - 1] == .keyword_return) {
416+
} else if (first_token > 0 and scope_tree.tokens.items(.tag)[first_token - 1] == .keyword_return) {
364417
const doc_scope = try scope_handle.handle.getDocumentScope();
365418

366419
const function_scope = switch (zlinter.version.zig) {
367420
.@"0.14" => zlinter.zls.Analyser.innermostFunctionScopeAtIndex(
368421
doc_scope,
369-
tree.tokens.items(.start)[first_token - 1],
422+
scope_tree.tokens.items(.start)[first_token - 1],
370423
).unwrap(),
371424
.@"0.15", .@"0.16" => zlinter.zls.Analyser.innermostScopeAtIndexWithTag(
372425
doc_scope,
373-
tree.tokenStart(first_token - 1),
426+
scope_tree.tokenStart(first_token - 1),
374427
.initOne(.function),
375428
).unwrap(),
376429
} orelse return null;
377430

378431
var fn_proto_buffer: [1]Ast.Node.Index = undefined;
379-
const func = tree.fullFnProto(&fn_proto_buffer, doc_scope.getScopeAstNode(function_scope).?).?;
432+
const func = scope_tree.fullFnProto(&fn_proto_buffer, doc_scope.getScopeAstNode(function_scope).?).?;
380433

381434
return .{
382-
.var_name = doc.handle.tree.tokenSlice(var_decl.ast.mut_token + 1),
383-
.name = tree.tokenSlice(func.name_token orelse return null),
435+
.var_name = tree.tokenSlice(var_decl.ast.mut_token + 1),
436+
.name = scope_tree.tokenSlice(func.name_token orelse return null),
384437
.uri = scope_handle.handle.uri,
385438
};
386439
}

0 commit comments

Comments
 (0)