-
Notifications
You must be signed in to change notification settings - Fork 17
Expand file tree
/
Copy path_gc_lint_test.v
More file actions
135 lines (127 loc) · 5.14 KB
/
Copy path_gc_lint_test.v
File metadata and controls
135 lines (127 loc) · 5.14 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
module gui
// Static source scan: flags .clear() calls that are not in the
// allowlist. Forces developers to explicitly declare new .clear()
// usage as GC-safe. See CLAUDE.md "GC / Boehm False-Retention Rules".
import os
// Allowlist entry: file path suffix + context snippet on same line.
// Keyed on content rather than line numbers so it survives refactors.
struct ClearAllowEntry {
file_suffix string // e.g. 'scratch_pools.v' or 'svg/animation.v'
context string // substring that must appear on the .clear() line
}
// Every .clear() in the codebase must match one of these entries
// or the test fails. To add a new .clear(), append an entry here
// with a comment explaining why it is GC-safe.
const clear_allowlist = [
// --- Value-only arrays (no pointers in backing memory) ---
// []f32
ClearAllowEntry{'scratch_pools.v', 'scratch.clear()'},
ClearAllowEntry{'scratch_pools.v', 'out.clear()'},
ClearAllowEntry{'render_svg.v', 'out.clear()'},
ClearAllowEntry{'svg/animation.v', 'out.clear()'},
// []int
ClearAllowEntry{'layout_sizing.v', 'fill_indices.clear()'},
ClearAllowEntry{'layout_sizing.v', 'fixed_indices.clear()'},
ClearAllowEntry{'layout_sizing.v', 'scratch.candidates.clear()'},
ClearAllowEntry{'layout_sizing.v', 'scratch.fixed_indices.clear()'},
// []GradientStop (value struct: f32 + Color)
ClearAllowEntry{'render_gradient.v', 'normalized.clear()'},
ClearAllowEntry{'render_gradient.v', 'sampled.clear()'},
// []FilterVertex (value struct: floats + u8s)
ClearAllowEntry{'render_filters.v', 'scratch_vertices.clear()'},
// []rune field (string builder, value type)
ClearAllowEntry{'view_text_xtra.v', 'field.clear()'},
// --- Map clears (map.clear() zeroes bucket metadata) ---
// map[K]V — all safe: maps use internal hash tables
ClearAllowEntry{'bounded_map.v', 'm.data.clear()'},
ClearAllowEntry{'bounded_map.v', 'm.access_time.clear()'},
ClearAllowEntry{'state_registry.v', 'r.maps.clear()'},
ClearAllowEntry{'state_registry.v', 'r.meta.clear()'},
ClearAllowEntry{'state_registry.v', 'r.orders.clear()'},
ClearAllowEntry{'render_svg.v', 'svg_group_matrices.clear()'},
ClearAllowEntry{'render_svg.v', 'svg_group_opacities.clear()'},
ClearAllowEntry{'view_image_xtra.v', 'm.data.clear()'},
ClearAllowEntry{'markdown_mermaid.v', 'm.data.clear()'},
ClearAllowEntry{'markdown_mermaid.v', 'm.order.clear()'},
ClearAllowEntry{'layout_sizing.v', 'parent_total_child_widths.clear()'},
ClearAllowEntry{'layout_sizing.v', 'parent_total_child_heights.clear()'},
ClearAllowEntry{'layout_query.v', 'focus_seen.clear()'},
// --- BoundedMap/BoundedStack .clear() methods ---
// These are the clear() method bodies themselves on the
// wrapper types — internal map/array clears, not raw arrays.
ClearAllowEntry{'bounded_stack.v', 's.elements.clear()'},
// --- BoundedMap-backed state_map .clear() calls ---
// state_map returns &BoundedMap whose .clear() method is safe
ClearAllowEntry{'window_event.v', 'ss.clear()'},
ClearAllowEntry{'window_event.v', 'cs.clear()'},
ClearAllowEntry{'view_select.v', 'ss.clear()'},
ClearAllowEntry{'view_theme_toggle.v', 'ss.clear()'},
ClearAllowEntry{'view_overflow_panel.v', 'ss.clear()'},
ClearAllowEntry{'view_input_date.v', 'ids.clear()'},
ClearAllowEntry{'view_table.v', 'tc.clear()'},
ClearAllowEntry{'view_state.v', 'diagram_cache.clear()'},
ClearAllowEntry{'view_state.v', 'svg_cache.clear()'},
ClearAllowEntry{'view_state.v', 'markdown_cache.clear()'},
ClearAllowEntry{'view_state.v', 'registry.clear()'},
ClearAllowEntry{'window_api.v', 'markdown_cache.clear()'},
ClearAllowEntry{'window_api.v', 'diagram_cache.clear()'},
ClearAllowEntry{'svg_load.v', 'svg_cache.clear()'},
// --- FormIssue arrays (value struct: two strings, no ptrs) ---
ClearAllowEntry{'view_form.v', 'sync_errors.clear()'},
ClearAllowEntry{'view_form.v', 'async_errors.clear()'},
]
fn test_no_unallowlisted_clear_calls() {
gui_root := os.dir(@FILE)
mut violations := []string{}
scan_dir(gui_root, '', mut violations)
scan_dir(os.join_path(gui_root, 'svg'), 'svg/', mut violations)
if violations.len > 0 {
mut msg := '\n.clear() calls not in allowlist '
msg += '(use array_clear for pointer arrays):\n'
for v in violations {
msg += ' ${v}\n'
}
msg += '\nAdd to clear_allowlist in _gc_lint_test.v '
msg += 'if verified GC-safe.'
assert false, msg
}
}
fn scan_dir(dir string, prefix string, mut violations []string) {
if !os.is_dir(dir) {
return
}
entries := os.ls(dir) or { return }
for fname in entries {
if !fname.ends_with('.v') {
continue
}
// Skip test files — they don't ship in the library
if fname.ends_with('_test.v') {
continue
}
rel := prefix + fname
fpath := os.join_path(dir, fname)
lines := os.read_lines(fpath) or { continue }
for i, line in lines {
trimmed := line.trim_space()
if !trimmed.contains('.clear()') {
continue
}
if trimmed.starts_with('//') {
continue
}
if is_clear_allowed(rel, trimmed) {
continue
}
violations << '${rel}:${i + 1}: ${trimmed}'
}
}
}
fn is_clear_allowed(rel_path string, line string) bool {
for entry in clear_allowlist {
if rel_path == entry.file_suffix && line.contains(entry.context) {
return true
}
}
return false
}