|
20 | 20 | */ |
21 | 21 | #include "test_framework.h" |
22 | 22 | #include "cbm.h" |
| 23 | +#include "lsp/c_lsp.h" |
| 24 | +#include "lsp/py_lsp.h" |
| 25 | +#include "lsp/cs_lsp.h" |
| 26 | +#include "lsp/ts_lsp.h" |
| 27 | +#include "lsp/go_lsp.h" |
| 28 | +#include "lsp/type_registry.h" |
| 29 | +#include "arena.h" |
23 | 30 | #include <stdio.h> |
24 | 31 | #include <stdlib.h> |
25 | 32 |
|
@@ -15182,9 +15189,159 @@ TEST(clsp_easy_win_sfinaeconditional_return) { |
15182 | 15189 | PASS(); |
15183 | 15190 | } |
15184 | 15191 |
|
| 15192 | +/* Reproduce-first guard for the Linux-kernel full-index O(n^2) hang. |
| 15193 | + * |
| 15194 | + * In `full` mode the pipeline builds ONE project-wide C cross-registry |
| 15195 | + * (cbm_c_build_cross_registry), FINALIZES it (O(1) hash lookups), then shares it |
| 15196 | + * READ-ONLY across the parallel resolve workers (ctx.registry_shared = true). |
| 15197 | + * cbm_run_c_lsp_cross_with_registry must therefore NOT mutate it. |
| 15198 | + * |
| 15199 | + * Bug: c_lsp.c:4323 (and 4628/4201/4426) ignore registry_shared and add_func into |
| 15200 | + * the shared, already-finalized registry. Each post-finalize add lands in a tail |
| 15201 | + * the hash index does not cover, so lookup_func_self/lookup_method_self linear-scan |
| 15202 | + * that ever-growing tail on every lookup (type_registry.c:280-286, 186-195). Across |
| 15203 | + * 89k kernel files doing millions of lookups => O(files * defs) (the >6-min hang), |
| 15204 | + * plus an 11-thread heap race (c_lsp.c:4146-4152 warns of the SIGSEGV). |
| 15205 | + * |
| 15206 | + * INVARIANT (green <=> fixed): resolve leaves the finalized shared registry's |
| 15207 | + * func_count/type_count unchanged. RED on the unguarded code; GREEN once every |
| 15208 | + * mutation site honors !registry_shared. */ |
| 15209 | +TEST(clsp_tier2_shared_registry_readonly_c) { |
| 15210 | + CBMArena arena; |
| 15211 | + cbm_arena_init(&arena); |
| 15212 | + /* stdlib-only project registry, finalized inside the builder */ |
| 15213 | + CBMTypeRegistry *reg = cbm_c_build_cross_registry(&arena, NULL, 0); |
| 15214 | + ASSERT_NOT_NULL(reg); |
| 15215 | + int funcs_before = reg->func_count; |
| 15216 | + int types_before = reg->type_count; |
| 15217 | + /* A C translation unit defining functions absent from the shared registry; a |
| 15218 | + * correct read-only resolve must not register them into it. */ |
| 15219 | + const char *src = "struct Node { int v; };\n" |
| 15220 | + "struct Node *make_node(int v);\n" |
| 15221 | + "int helper(int x) { return x + 1; }\n" |
| 15222 | + "int caller(void) { return helper(make_node(7)->v); }\n"; |
| 15223 | + CBMResolvedCallArray out = {0}; |
| 15224 | + cbm_run_c_lsp_cross_with_registry(&arena, src, (int)strlen(src), "test.mod", |
| 15225 | + /*cpp_mode=*/false, reg, /*include_paths=*/NULL, |
| 15226 | + /*include_ns_qns=*/NULL, /*include_count=*/0, |
| 15227 | + /*cached_tree=*/NULL, &out); |
| 15228 | + ASSERT_EQ(reg->func_count, funcs_before); |
| 15229 | + ASSERT_EQ(reg->type_count, types_before); |
| 15230 | + cbm_arena_destroy(&arena); |
| 15231 | + PASS(); |
| 15232 | +} |
| 15233 | + |
15185 | 15234 | /* ── Suite ─────────────────────────────────────────────────────── */ |
15186 | 15235 |
|
| 15236 | +/* C++ sibling guard: the same shared-registry read-only invariant for the |
| 15237 | + * C++-only mutation sites — method registration (c_lsp.c:4760), template |
| 15238 | + * type-param scan (4558), default-arg min_params scan (4333). RED while those |
| 15239 | + * sites ignore registry_shared; GREEN once guarded. (Latent O(n^2)+race for large |
| 15240 | + * C++ codebases like LLVM/bitcoin — not the kernel, which is C.) */ |
| 15241 | +TEST(clsp_tier2_shared_registry_readonly_cpp) { |
| 15242 | + CBMArena arena; |
| 15243 | + cbm_arena_init(&arena); |
| 15244 | + CBMTypeRegistry *reg = cbm_c_build_cross_registry(&arena, NULL, 0); |
| 15245 | + ASSERT_NOT_NULL(reg); |
| 15246 | + int funcs_before = reg->func_count; |
| 15247 | + int types_before = reg->type_count; |
| 15248 | + const char *src = "template <typename T> struct Holder { T get(); };\n" |
| 15249 | + "struct Box { int unwrap(); };\n" |
| 15250 | + "int Box::unwrap() { return 0; }\n" |
| 15251 | + "int with_default(int a, int b = 2) { return a + b; }\n" |
| 15252 | + "int caller(Box *b) { return b->unwrap() + with_default(1); }\n"; |
| 15253 | + CBMResolvedCallArray out = {0}; |
| 15254 | + cbm_run_c_lsp_cross_with_registry(&arena, src, (int)strlen(src), "test.mod", |
| 15255 | + /*cpp_mode=*/true, reg, /*include_paths=*/NULL, |
| 15256 | + /*include_ns_qns=*/NULL, /*include_count=*/0, |
| 15257 | + /*cached_tree=*/NULL, &out); |
| 15258 | + ASSERT_EQ(reg->func_count, funcs_before); |
| 15259 | + ASSERT_EQ(reg->type_count, types_before); |
| 15260 | + cbm_arena_destroy(&arena); |
| 15261 | + PASS(); |
| 15262 | +} |
| 15263 | + |
| 15264 | +/* Cross-language check of the same Tier-2 shared-registry read-only invariant for |
| 15265 | + * the other languages that build a shared cross-registry (py, c#, ts, go). The |
| 15266 | + * registry-level seal (type_registry.c: add_func/_type no-op when reg->read_only) |
| 15267 | + * guards all of them at one chokepoint. Each test: build+finalize the shared |
| 15268 | + * registry, resolve a source, assert func_count/type_count are unchanged. (TS |
| 15269 | + * deliberately mutates a per-file OVERLAY chained to the base — the base must |
| 15270 | + * still be untouched.) */ |
| 15271 | +TEST(seal_py_shared_registry_readonly) { |
| 15272 | + CBMArena arena; |
| 15273 | + cbm_arena_init(&arena); |
| 15274 | + CBMTypeRegistry *reg = cbm_py_build_cross_registry(&arena, NULL, 0); |
| 15275 | + ASSERT_NOT_NULL(reg); |
| 15276 | + int fb = reg->func_count, tb = reg->type_count; |
| 15277 | + const char *src = "def helper(x):\n return x + 1\n\ndef caller():\n return helper(1)\n"; |
| 15278 | + CBMResolvedCallArray out = {0}; |
| 15279 | + cbm_run_py_lsp_cross_with_registry(&arena, src, (int)strlen(src), "test.mod", reg, NULL, NULL, |
| 15280 | + 0, NULL, &out); |
| 15281 | + ASSERT_EQ(reg->func_count, fb); |
| 15282 | + ASSERT_EQ(reg->type_count, tb); |
| 15283 | + cbm_arena_destroy(&arena); |
| 15284 | + PASS(); |
| 15285 | +} |
| 15286 | + |
| 15287 | +TEST(seal_cs_shared_registry_readonly) { |
| 15288 | + CBMArena arena; |
| 15289 | + cbm_arena_init(&arena); |
| 15290 | + CBMTypeRegistry *reg = cbm_cs_build_cross_registry(&arena, NULL, 0); |
| 15291 | + ASSERT_NOT_NULL(reg); |
| 15292 | + int fb = reg->func_count, tb = reg->type_count; |
| 15293 | + const char *src = "namespace N { class Box { int Unwrap() { return 0; }\n" |
| 15294 | + " int Caller() { return Unwrap(); } } }\n"; |
| 15295 | + CBMResolvedCallArray out = {0}; |
| 15296 | + cbm_run_cs_lsp_cross_with_registry(&arena, src, (int)strlen(src), "test.mod", reg, NULL, 0, |
| 15297 | + NULL, &out); |
| 15298 | + ASSERT_EQ(reg->func_count, fb); |
| 15299 | + ASSERT_EQ(reg->type_count, tb); |
| 15300 | + cbm_arena_destroy(&arena); |
| 15301 | + PASS(); |
| 15302 | +} |
| 15303 | + |
| 15304 | +TEST(seal_ts_shared_registry_readonly) { |
| 15305 | + CBMArena arena; |
| 15306 | + cbm_arena_init(&arena); |
| 15307 | + CBMTypeRegistry *reg = cbm_ts_build_cross_registry(&arena, NULL, 0); |
| 15308 | + ASSERT_NOT_NULL(reg); |
| 15309 | + int fb = reg->func_count, tb = reg->type_count; |
| 15310 | + const char *src = "class Box { unwrap(): number { return 0; } }\n" |
| 15311 | + "function caller(b: Box): number { return b.unwrap(); }\n"; |
| 15312 | + CBMResolvedCallArray out = {0}; |
| 15313 | + cbm_run_ts_lsp_cross_with_registry(&arena, src, (int)strlen(src), "test.mod", false, false, |
| 15314 | + false, reg, NULL, 0, NULL, NULL, 0, NULL, &out); |
| 15315 | + ASSERT_EQ(reg->func_count, fb); |
| 15316 | + ASSERT_EQ(reg->type_count, tb); |
| 15317 | + cbm_arena_destroy(&arena); |
| 15318 | + PASS(); |
| 15319 | +} |
| 15320 | + |
| 15321 | +TEST(seal_go_shared_registry_readonly) { |
| 15322 | + CBMArena arena; |
| 15323 | + cbm_arena_init(&arena); |
| 15324 | + CBMTypeRegistry *reg = cbm_go_build_cross_registry(&arena, NULL, 0); |
| 15325 | + ASSERT_NOT_NULL(reg); |
| 15326 | + int fb = reg->func_count, tb = reg->type_count; |
| 15327 | + const char *src = "package main\nfunc helper(x int) int { return x + 1 }\n" |
| 15328 | + "func caller() int { return helper(1) }\n"; |
| 15329 | + CBMResolvedCallArray out = {0}; |
| 15330 | + cbm_run_go_lsp_cross_with_registry(&arena, src, (int)strlen(src), "test.mod", reg, NULL, NULL, |
| 15331 | + 0, NULL, &out); |
| 15332 | + ASSERT_EQ(reg->func_count, fb); |
| 15333 | + ASSERT_EQ(reg->type_count, tb); |
| 15334 | + cbm_arena_destroy(&arena); |
| 15335 | + PASS(); |
| 15336 | +} |
| 15337 | + |
15187 | 15338 | SUITE(c_lsp) { |
| 15339 | + RUN_TEST(clsp_tier2_shared_registry_readonly_c); |
| 15340 | + RUN_TEST(clsp_tier2_shared_registry_readonly_cpp); |
| 15341 | + RUN_TEST(seal_py_shared_registry_readonly); |
| 15342 | + RUN_TEST(seal_cs_shared_registry_readonly); |
| 15343 | + RUN_TEST(seal_ts_shared_registry_readonly); |
| 15344 | + RUN_TEST(seal_go_shared_registry_readonly); |
15188 | 15345 | RUN_TEST(clsp_simple_var_decl); |
15189 | 15346 | RUN_TEST(clsp_pointer_arrow); |
15190 | 15347 | RUN_TEST(clsp_dot_access); |
|
0 commit comments