mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-04 10:48:20 +00:00
fix(ruby-lsp): survive allocation failure in the stdlib seed
The REG_* macros wrote through the return-type vector without checking the allocation, and registered entries whose formatted QN came back NULL. A shared ruby_seed_signature helper now leaves a failed vector out, so the signature types as unknown (the fallback ruby_register_method already uses), and REG_METHOD / REG_SMETHOD skip an entry whose formatted name is NULL, since a NULL QN would reach strcmp in finalize and in every lookup. rubylsp_stdlib_seed_survives_alloc_failure seeds a registry from an arena that cannot allocate. Before: UBSan reported a store to a null pointer and the run crashed with SEGV at ruby_stdlib_data.c:163. After: every registered entry carries a name, the registry finalizes, and the Kernel built-ins with literal names still resolve. Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This commit is contained in:
1 parent
214a9ac843
commit
435baf6bd8
2 files changed
+74
-43
No files matched your search
@@ -40,55 +40,59 @@
|
||||
cbm_registry_add_type(reg, rt); \
|
||||
} while (0)
|
||||
|
||||
/* Register an instance method on receiver type `recv_` returning `ret_`. */
|
||||
#define REG_METHOD(recv_, name_, ret_) \
|
||||
do { \
|
||||
memset(&rf, 0, sizeof(rf)); \
|
||||
rf.min_params = -1; \
|
||||
rf.qualified_name = cbm_arena_sprintf(arena, "%s.%s", (recv_), (name_)); \
|
||||
rf.short_name = (name_); \
|
||||
rf.receiver_type = (recv_); \
|
||||
{ \
|
||||
const CBMType **rets = (const CBMType **)cbm_arena_alloc(arena, 2 * sizeof(*rets)); \
|
||||
rets[0] = (ret_); \
|
||||
rets[1] = NULL; \
|
||||
rf.signature = cbm_type_func(arena, NULL, NULL, rets); \
|
||||
} \
|
||||
cbm_registry_add_func(reg, rf); \
|
||||
/* Signature `() -> ret`. Every seed allocation can fail (arena exhausted):
|
||||
* a NULL return vector is left out, so the signature types as unknown (the
|
||||
* same fallback ruby_register_method uses) instead of being written through. */
|
||||
static const CBMType *ruby_seed_signature(CBMArena *arena, const CBMType *ret) {
|
||||
const CBMType **rets = (const CBMType **)cbm_arena_alloc(arena, 2 * sizeof(*rets));
|
||||
if (rets) {
|
||||
rets[0] = ret;
|
||||
rets[1] = NULL;
|
||||
}
|
||||
return cbm_type_func(arena, NULL, NULL, rets);
|
||||
}
|
||||
|
||||
/* Register an instance method on receiver type `recv_` returning `ret_`.
|
||||
* An entry whose formatted QN came back NULL is not registered: a NULL QN
|
||||
* would reach strcmp in finalize and in every later lookup. */
|
||||
#define REG_METHOD(recv_, name_, ret_) \
|
||||
do { \
|
||||
memset(&rf, 0, sizeof(rf)); \
|
||||
rf.min_params = -1; \
|
||||
rf.qualified_name = cbm_arena_sprintf(arena, "%s.%s", (recv_), (name_)); \
|
||||
if (!rf.qualified_name) \
|
||||
break; \
|
||||
rf.short_name = (name_); \
|
||||
rf.receiver_type = (recv_); \
|
||||
rf.signature = ruby_seed_signature(arena, (ret_)); \
|
||||
cbm_registry_add_func(reg, rf); \
|
||||
} while (0)
|
||||
|
||||
/* Register a singleton (class-side) method: receiver key "<qn>.self"
|
||||
* matches ruby_lookup_singleton_method's keying convention. */
|
||||
#define REG_SMETHOD(recv_, name_, ret_) \
|
||||
do { \
|
||||
memset(&rf, 0, sizeof(rf)); \
|
||||
rf.min_params = -1; \
|
||||
rf.qualified_name = cbm_arena_sprintf(arena, "%s.%s", (recv_), (name_)); \
|
||||
rf.short_name = (name_); \
|
||||
rf.receiver_type = cbm_arena_sprintf(arena, "%s.self", (recv_)); \
|
||||
{ \
|
||||
const CBMType **rets = (const CBMType **)cbm_arena_alloc(arena, 2 * sizeof(*rets)); \
|
||||
rets[0] = (ret_); \
|
||||
rets[1] = NULL; \
|
||||
rf.signature = cbm_type_func(arena, NULL, NULL, rets); \
|
||||
} \
|
||||
cbm_registry_add_func(reg, rf); \
|
||||
* matches ruby_lookup_singleton_method's keying convention. Skipped when
|
||||
* either formatted name came back NULL (see REG_METHOD). */
|
||||
#define REG_SMETHOD(recv_, name_, ret_) \
|
||||
do { \
|
||||
memset(&rf, 0, sizeof(rf)); \
|
||||
rf.min_params = -1; \
|
||||
rf.qualified_name = cbm_arena_sprintf(arena, "%s.%s", (recv_), (name_)); \
|
||||
rf.receiver_type = cbm_arena_sprintf(arena, "%s.self", (recv_)); \
|
||||
if (!rf.qualified_name || !rf.receiver_type) \
|
||||
break; \
|
||||
rf.short_name = (name_); \
|
||||
rf.signature = ruby_seed_signature(arena, (ret_)); \
|
||||
cbm_registry_add_func(reg, rf); \
|
||||
} while (0)
|
||||
|
||||
/* Register a global (Kernel) built-in function returning `ret_`. */
|
||||
#define REG_BUILTIN(name_, ret_) \
|
||||
do { \
|
||||
memset(&rf, 0, sizeof(rf)); \
|
||||
rf.min_params = -1; \
|
||||
rf.qualified_name = (name_); \
|
||||
rf.short_name = (name_); \
|
||||
{ \
|
||||
const CBMType **rets = (const CBMType **)cbm_arena_alloc(arena, 2 * sizeof(*rets)); \
|
||||
rets[0] = (ret_); \
|
||||
rets[1] = NULL; \
|
||||
rf.signature = cbm_type_func(arena, NULL, NULL, rets); \
|
||||
} \
|
||||
cbm_registry_add_func(reg, rf); \
|
||||
#define REG_BUILTIN(name_, ret_) \
|
||||
do { \
|
||||
memset(&rf, 0, sizeof(rf)); \
|
||||
rf.min_params = -1; \
|
||||
rf.qualified_name = (name_); \
|
||||
rf.short_name = (name_); \
|
||||
rf.signature = ruby_seed_signature(arena, (ret_)); \
|
||||
cbm_registry_add_func(reg, rf); \
|
||||
} while (0)
|
||||
|
||||
void cbm_ruby_stdlib_register(CBMTypeRegistry *reg, CBMArena *arena) {
|
||||
|
||||
@@ -1498,6 +1498,32 @@ TEST(rubylsp_const_path_is_linear) {
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* Review ask (c): the stdlib seed must survive allocation failure. A
|
||||
* zero-initialised arena has no blocks and no grow size, so every
|
||||
* allocation from it returns NULL. The seed must not write through a NULL
|
||||
* return-type vector, and must not register an entry whose formatted name
|
||||
* came back NULL (a NULL QN would reach strcmp in finalize and in every later
|
||||
* lookup). */
|
||||
TEST(rubylsp_stdlib_seed_survives_alloc_failure) {
|
||||
CBMArena reg_arena;
|
||||
cbm_arena_init(®_arena);
|
||||
CBMTypeRegistry reg;
|
||||
cbm_registry_init(®, ®_arena);
|
||||
CBMArena dead;
|
||||
memset(&dead, 0, sizeof(dead));
|
||||
cbm_ruby_stdlib_register(®, &dead);
|
||||
ASSERT(reg.type_count > 0);
|
||||
ASSERT(reg.func_count > 0); /* Kernel built-ins carry literal names */
|
||||
for (int i = 0; i < reg.func_count; i++) {
|
||||
ASSERT(reg.funcs[i].qualified_name != NULL);
|
||||
ASSERT(reg.funcs[i].short_name != NULL);
|
||||
}
|
||||
cbm_registry_finalize(®);
|
||||
ASSERT(cbm_registry_lookup_func(®, "puts") != NULL);
|
||||
cbm_arena_destroy(®_arena);
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* ── suite ──────────────────────────────────────────────────────── */
|
||||
|
||||
void suite_ruby_lsp(void) {
|
||||
@@ -1536,4 +1562,5 @@ void suite_ruby_lsp(void) {
|
||||
RUN_TEST(rubylsp_self_rebinding_blocks_no_edge);
|
||||
RUN_TEST(rubylsp_mixin_walk_visits_each_module_once);
|
||||
RUN_TEST(rubylsp_const_path_is_linear);
|
||||
RUN_TEST(rubylsp_stdlib_seed_survives_alloc_failure);
|
||||
}
|
||||
Reference in new issue
Block a user