Skip to content

Commit 8407825

Browse files
gh-19: Fix race condition caused by env_get_entry.
1 parent c8dd49f commit 8407825

File tree

3 files changed

+142
-45
lines changed

3 files changed

+142
-45
lines changed

src/env.c

Lines changed: 77 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,67 @@
2222
#define strdup _strdup
2323
#endif
2424

25+
/* ================================================================== */
26+
/* Thread-local snapshots for env_get_entry */
27+
/* ================================================================== */
28+
29+
// env_get_entry historically returned a raw pointer into Env storage.
30+
// With the namespace buffer active, that pointer could be invalidated
31+
// immediately after returning (e.g. via realloc/value_free on the
32+
// prepare thread), causing use-after-free crashes.
33+
//
34+
// Fix: return a per-thread snapshot copy of the entry.
35+
//
36+
// NOTE: The returned pointer is valid until the calling thread makes
37+
// enough subsequent env_get_entry calls to wrap this ring buffer.
38+
39+
#ifndef ENV_ENTRY_SNAPSHOT_RING
40+
#define ENV_ENTRY_SNAPSHOT_RING 32
41+
#endif
42+
43+
static _Thread_local EnvEntry g_entry_snaps[ENV_ENTRY_SNAPSHOT_RING];
44+
static _Thread_local size_t g_entry_snap_idx = 0;
45+
46+
static void env_entry_snap_clear(EnvEntry* e) {
47+
if (!e) return;
48+
if (e->name) {
49+
free(e->name);
50+
e->name = NULL;
51+
}
52+
if (e->alias_target) {
53+
free(e->alias_target);
54+
e->alias_target = NULL;
55+
}
56+
value_free(e->value);
57+
e->value = value_null();
58+
e->decl_type = TYPE_UNKNOWN;
59+
e->initialized = false;
60+
e->frozen = false;
61+
e->permafrozen = false;
62+
}
63+
64+
static EnvEntry* env_entry_snap_alloc(void) {
65+
EnvEntry* slot = &g_entry_snaps[g_entry_snap_idx++ % ENV_ENTRY_SNAPSHOT_RING];
66+
env_entry_snap_clear(slot);
67+
return slot;
68+
}
69+
70+
static void env_entry_snap_from_raw(EnvEntry* dst, const EnvEntry* src) {
71+
if (!dst) return;
72+
if (!src) {
73+
// leave dst cleared
74+
return;
75+
}
76+
77+
dst->name = src->name ? strdup(src->name) : NULL;
78+
dst->decl_type = src->decl_type;
79+
dst->initialized = src->initialized;
80+
dst->frozen = src->frozen;
81+
dst->permafrozen = src->permafrozen;
82+
dst->alias_target = src->alias_target ? strdup(src->alias_target) : NULL;
83+
dst->value = value_copy(src->value);
84+
}
85+
2586
/* ================================================================== */
2687
/* Helpers */
2788
/* ================================================================== */
@@ -345,13 +406,27 @@ int env_permafreeze(Env* env, const char* name) {
345406
/* ================================================================== */
346407

347408
EnvEntry* env_get_entry(Env* env, const char* name) {
409+
EnvEntry* snap = env_entry_snap_alloc();
348410
if (ns_buffer_active()) {
349411
ns_buffer_read_lock(name);
350412
EnvEntry* entry = env_get_entry_raw(env, name);
413+
env_entry_snap_from_raw(snap, entry);
351414
ns_buffer_read_unlock();
352-
return entry;
415+
if (!entry) {
416+
env_entry_snap_clear(snap);
417+
return NULL;
418+
}
419+
return snap;
420+
}
421+
422+
EnvEntry* entry = env_get_entry_raw(env, name);
423+
env_entry_snap_from_raw(snap, entry);
424+
if (!entry) {
425+
// Clear to a canonical empty state and return NULL for not-found.
426+
env_entry_snap_clear(snap);
427+
return NULL;
353428
}
354-
return env_get_entry_raw(env, name);
429+
return snap;
355430
}
356431

357432
bool env_get(Env* env, const char* name, Value* out_value,

src/env.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,11 @@ bool env_assign(Env* env, const char* name, Value value, DeclType type, bool dec
2929
bool env_get(Env* env, const char* name, Value* out_value, DeclType* out_type, bool* out_initialized);
3030
bool env_delete(Env* env, const char* name);
3131
bool env_exists(Env* env, const char* name);
32-
// Return pointer to the EnvEntry for the given name, searching parents.
33-
// Caller must NOT free the returned pointer. Returns NULL if not found.
32+
// Return a per-thread snapshot of the EnvEntry for the given name, searching parents.
33+
// The snapshot is safe to read after the function returns even when the
34+
// namespace buffer is active (it does not point into Env storage).
35+
// Caller must NOT free the returned pointer.
36+
// Returns NULL if not found.
3437
EnvEntry* env_get_entry(Env* env, const char* name);
3538

3639
// Create or update an alias (pointer) binding: `name` will become an alias to `target_name`.

src/interpreter.c

Lines changed: 60 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1452,21 +1452,34 @@ ExecResult assign_index_chain(Interpreter* interp, Env* env, Expr* idx_expr, Val
14521452
return make_error("Indexed assignment base must be an identifier", stmt_line, stmt_col);
14531453
}
14541454

1455-
EnvEntry* entry = env_get_entry(env, walker->as.ident);
1456-
if (!entry) {
1455+
const char* base_name = walker->as.ident;
1456+
Value base_val = value_null();
1457+
DeclType base_type = TYPE_UNKNOWN;
1458+
bool base_initialized = false;
1459+
1460+
if (!env_get(env, base_name, &base_val, &base_type, &base_initialized)) {
14571461
char buf[256];
1458-
snprintf(buf, sizeof(buf), "Cannot assign to undeclared identifier '%s'", walker->as.ident);
1462+
snprintf(buf, sizeof(buf), "Cannot assign to undeclared identifier '%s'", base_name);
14591463
return make_error(buf, stmt_line, stmt_col);
14601464
}
14611465

1462-
// If uninitialized, default to map (matches previous indexed-assignment behavior).
1463-
if (!entry->initialized) {
1464-
entry->value = value_map_new();
1465-
entry->initialized = true;
1466+
// If uninitialized (or NULL), default to MAP (matches previous behaviour),
1467+
// and persist that into the environment via env_assign.
1468+
if (!base_initialized || base_val.type == VAL_NULL) {
1469+
Value nm = value_map_new();
1470+
if (!env_assign(env, base_name, nm, TYPE_UNKNOWN, false)) {
1471+
value_free(nm);
1472+
value_free(base_val);
1473+
return make_error("Cannot assign to identifier (frozen?)", stmt_line, stmt_col);
1474+
}
1475+
value_free(base_val);
1476+
base_val = nm;
1477+
base_initialized = true;
14661478
}
14671479

14681480
Expr** nodes = malloc(sizeof(Expr*) * (chain_len ? chain_len : 1));
14691481
if (!nodes) {
1482+
value_free(base_val);
14701483
return make_error("Out of memory", stmt_line, stmt_col);
14711484
}
14721485

@@ -1476,15 +1489,16 @@ ExecResult assign_index_chain(Interpreter* interp, Env* env, Expr* idx_expr, Val
14761489
walker = walker->as.index.target;
14771490
}
14781491

1479-
Value* cur = &entry->value;
1492+
ExecResult out;
1493+
Value* cur = &base_val;
14801494

14811495
// Process from innermost -> outermost
14821496
for (int ni = (int)chain_len - 1; ni >= 0; ni--) {
14831497
Expr* node = nodes[ni];
14841498
ExprList* indices = &node->as.index.indices;
14851499
if (indices->count == 0) {
1486-
free(nodes);
1487-
return make_error("Empty index list", node->line, node->column);
1500+
out = make_error("Empty index list", node->line, node->column);
1501+
goto cleanup;
14881502
}
14891503

14901504
// Auto-promote NULL to MAP when assigning through indexes.
@@ -1496,37 +1510,37 @@ ExecResult assign_index_chain(Interpreter* interp, Env* env, Expr* idx_expr, Val
14961510
Tensor* t = cur->as.tns;
14971511
// Lvalue assignment only supports full integer indexing (no ranges/wildcards).
14981512
if (indices->count != t->ndim) {
1499-
free(nodes);
1500-
return make_error("Cannot assign through tensor slice", node->line, node->column);
1513+
out = make_error("Cannot assign through tensor slice", node->line, node->column);
1514+
goto cleanup;
15011515
}
15021516

15031517
size_t* idxs0 = malloc(sizeof(size_t) * indices->count);
15041518
if (!idxs0) {
1505-
free(nodes);
1506-
return make_error("Out of memory", stmt_line, stmt_col);
1519+
out = make_error("Out of memory", stmt_line, stmt_col);
1520+
goto cleanup;
15071521
}
15081522

15091523
for (size_t i = 0; i < indices->count; i++) {
15101524
Expr* it = indices->items[i];
15111525
if (it->type == EXPR_WILDCARD || it->type == EXPR_RANGE) {
15121526
free(idxs0);
1513-
free(nodes);
1514-
return make_error("Cannot assign using ranges or wildcards", it->line, it->column);
1527+
out = make_error("Cannot assign using ranges or wildcards", it->line, it->column);
1528+
goto cleanup;
15151529
}
15161530

15171531
Value vi = eval_expr(interp, it, env);
15181532
if (interp->error) {
15191533
ExecResult err = make_error(interp->error, interp->error_line, interp->error_col);
15201534
clear_error(interp);
15211535
free(idxs0);
1522-
free(nodes);
1523-
return err;
1536+
out = err;
1537+
goto cleanup;
15241538
}
15251539
if (vi.type != VAL_INT) {
15261540
value_free(vi);
15271541
free(idxs0);
1528-
free(nodes);
1529-
return make_error("Index expression must evaluate to INT", it->line, it->column);
1542+
out = make_error("Index expression must evaluate to INT", it->line, it->column);
1543+
goto cleanup;
15301544
}
15311545

15321546
int64_t v = vi.as.i;
@@ -1535,8 +1549,8 @@ ExecResult assign_index_chain(Interpreter* interp, Env* env, Expr* idx_expr, Val
15351549
if (v < 1 || v > dim) {
15361550
value_free(vi);
15371551
free(idxs0);
1538-
free(nodes);
1539-
return make_error("Index out of range", it->line, it->column);
1552+
out = make_error("Index out of range", it->line, it->column);
1553+
goto cleanup;
15401554
}
15411555
idxs0[i] = (size_t)(v - 1);
15421556
value_free(vi);
@@ -1545,8 +1559,8 @@ ExecResult assign_index_chain(Interpreter* interp, Env* env, Expr* idx_expr, Val
15451559
Value* elem = value_tns_get_ptr(*cur, idxs0, indices->count);
15461560
free(idxs0);
15471561
if (!elem) {
1548-
free(nodes);
1549-
return make_error("Index out of range", node->line, node->column);
1562+
out = make_error("Index out of range", node->line, node->column);
1563+
goto cleanup;
15501564
}
15511565
cur = elem;
15521566
continue;
@@ -1559,13 +1573,13 @@ ExecResult assign_index_chain(Interpreter* interp, Env* env, Expr* idx_expr, Val
15591573
if (interp->error) {
15601574
ExecResult err = make_error(interp->error, interp->error_line, interp->error_col);
15611575
clear_error(interp);
1562-
free(nodes);
1563-
return err;
1576+
out = err;
1577+
goto cleanup;
15641578
}
15651579
if (!(key.type == VAL_INT || key.type == VAL_STR || key.type == VAL_FLT)) {
15661580
value_free(key);
1567-
free(nodes);
1568-
return make_error("Map index must be INT, FLT or STR", it->line, it->column);
1581+
out = make_error("Map index must be INT, FLT or STR", it->line, it->column);
1582+
goto cleanup;
15691583
}
15701584

15711585
bool last_key_in_node = (i + 1 == indices->count);
@@ -1576,25 +1590,25 @@ ExecResult assign_index_chain(Interpreter* interp, Env* env, Expr* idx_expr, Val
15761590
Value* slot = value_map_get_ptr(cur, key, true);
15771591
value_free(key);
15781592
if (!slot) {
1579-
free(nodes);
1580-
return make_error("Internal error assigning to map", stmt_line, stmt_col);
1593+
out = make_error("Internal error assigning to map", stmt_line, stmt_col);
1594+
goto cleanup;
15811595
}
15821596
if (slot->type != VAL_NULL && value_type_to_decl(slot->type) != value_type_to_decl(rhs.type)) {
1583-
free(nodes);
1584-
return make_error("Map entry type mismatch", stmt_line, stmt_col);
1597+
out = make_error("Map entry type mismatch", stmt_line, stmt_col);
1598+
goto cleanup;
15851599
}
15861600
value_free(*slot);
15871601
*slot = value_copy(rhs);
1588-
free(nodes);
1589-
return make_ok(value_null());
1602+
out = make_ok(value_null());
1603+
goto cleanup;
15901604
}
15911605

15921606
// Descend into slot.
15931607
Value* slot = value_map_get_ptr(cur, key, true);
15941608
value_free(key);
15951609
if (!slot) {
1596-
free(nodes);
1597-
return make_error("Internal error indexing map", stmt_line, stmt_col);
1610+
out = make_error("Internal error indexing map", stmt_line, stmt_col);
1611+
goto cleanup;
15981612
}
15991613

16001614
if (slot->type == VAL_NULL) {
@@ -1608,21 +1622,26 @@ ExecResult assign_index_chain(Interpreter* interp, Env* env, Expr* idx_expr, Val
16081622
}
16091623

16101624
// Unsupported type for indexed assignment
1611-
free(nodes);
1612-
return make_error("Indexing assignment is supported only on tensors and maps", node->line, node->column);
1625+
out = make_error("Indexing assignment is supported only on tensors and maps", node->line, node->column);
1626+
goto cleanup;
16131627
}
16141628

16151629
// If we get here, the chain ended after resolving to a tensor element (e.g. a<1> = rhs)
16161630
if (cur->type != VAL_NULL && value_type_to_decl(cur->type) != value_type_to_decl(rhs.type)) {
1617-
free(nodes);
1618-
return make_error("Element type mismatch", stmt_line, stmt_col);
1631+
out = make_error("Element type mismatch", stmt_line, stmt_col);
1632+
goto cleanup;
16191633
}
16201634
mtx_lock(&g_tns_lock);
16211635
value_free(*cur);
16221636
*cur = value_copy(rhs);
16231637
mtx_unlock(&g_tns_lock);
1638+
1639+
out = make_ok(value_null());
1640+
1641+
cleanup:
16241642
free(nodes);
1625-
return make_ok(value_null());
1643+
value_free(base_val);
1644+
return out;
16261645
}
16271646

16281647
static ExecResult exec_stmt(Interpreter* interp, Stmt* stmt, Env* env, LabelMap* labels) {

0 commit comments

Comments
 (0)