Skip to content
128 changes: 125 additions & 3 deletions src/lgc.c
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,10 @@ static uint32_t trace_heaps = 0;
static int local_collection(lua_State *L, int type);
static int global_trace(lua_State *L);
static void unblock_mutators(lua_State *L);
static void validate_heap_objects(lua_State *L, const char *where);
static int is_bad_gc_ptr(uintptr_t p);
static void tailq_abort(lua_State *L, const char *where, const char *what,
GCheader *o, GCheader *prev, int count);

static INLINE int is_black(lua_State *L, GCheader *obj)
{
Expand Down Expand Up @@ -1488,6 +1492,11 @@ static GCheader *new_obj(lua_State *L, enum lua_obj_type tt,
* before it becomes visible to the GC via the grey stack. */
block_collector(L, pt);
TAILQ_INSERT_HEAD(&L->heap->objects, o, allocd);
if (o->allocd.tqe_next != NULL &&
!is_bad_gc_ptr((uintptr_t)o->allocd.tqe_next) &&
o->allocd.tqe_next->allocd.tqe_prev != &o->allocd.tqe_next) {
tailq_abort(L, "new_obj", "insert_backlink_broken", o, NULL, -1);
}
make_grey(L, o);
unblock_collector(L, pt);

Expand Down Expand Up @@ -1656,6 +1665,8 @@ void luaC_inherit_thread(lua_State *L, lua_State *th)
ck_sequence_write_end(&th->memlock);
luaE_flush_stringtable(th);

validate_heap_objects(L, "inherit_thread:before_transfer");

TAILQ_FOREACH_SAFE(steal, &th->heap->objects, allocd, tmp) {
/* Update owner before removing from source heap. This ensures that
* if any concurrent reader sees this object, it will either:
Expand All @@ -1667,9 +1678,17 @@ void luaC_inherit_thread(lua_State *L, lua_State *th)

TAILQ_REMOVE(&th->heap->objects, steal, allocd);
TAILQ_INSERT_HEAD(&L->heap->objects, steal, allocd);
if (steal->allocd.tqe_next != NULL &&
!is_bad_gc_ptr((uintptr_t)steal->allocd.tqe_next) &&
steal->allocd.tqe_next->allocd.tqe_prev != &steal->allocd.tqe_next) {
tailq_abort(L, "inherit_thread", "insert_backlink_broken", steal, NULL, -1);
}

make_grey(L, steal);
}

validate_heap_objects(L, "inherit_thread:after_transfer");

TAILQ_REMOVE(&G(L)->all_heaps, th->heap, heaps);
unlock_all_threads();

Expand Down Expand Up @@ -1954,6 +1973,96 @@ static void sanity_check_mark_status(lua_State *L)
}
}

static int is_bad_gc_ptr(uintptr_t p)
{
return p == 0x5a5a5a5a5a5a5a5aULL || p == 0 || (p & 0x7) != 0;
}

static void *safe_deref_owner_L(void *owner_ptr)
{
if (!owner_ptr || is_bad_gc_ptr((uintptr_t)owner_ptr))
return NULL;
return (void*)((GCheap *)owner_ptr)->owner;
}

static void tailq_abort(lua_State *L, const char *where, const char *what,
GCheader *o, GCheader *prev, int count)
{
void *o_next = NULL, *o_prev = NULL, *o_owner = NULL, *o_owner_L = NULL;
void *p_next = NULL, *p_owner = NULL, *p_owner_L = NULL;
void *nn_next = NULL, *nn_prev = NULL, *nn_owner = NULL, *nn_owner_L = NULL;
int tt = -1, nn_tt = -1;
unsigned marked = 0, xref = 0, ref = 0;

if (o) {
o_next = (void*)o->allocd.tqe_next;
o_prev = (void*)o->allocd.tqe_prev;
o_owner = (void*)o->owner;
o_owner_L = safe_deref_owner_L(o_owner);
tt = o->tt;
marked = o->marked;
xref = o->xref;
ref = o->ref;
if (o->allocd.tqe_next && !is_bad_gc_ptr((uintptr_t)o->allocd.tqe_next)) {
GCheader *nn = o->allocd.tqe_next;
nn_next = (void*)nn->allocd.tqe_next;
nn_prev = (void*)nn->allocd.tqe_prev;
nn_owner = (void*)nn->owner;
nn_owner_L = safe_deref_owner_L(nn_owner);
nn_tt = nn->tt;
}
}
if (prev) {
p_next = (void*)prev->allocd.tqe_next;
p_owner = (void*)prev->owner;
p_owner_L = safe_deref_owner_L(p_owner);
}
thrlua_log(L, DCRITICAL,
"thrlua HEAP CORRUPT [%s] %s L=%p heap=%p count=%d"
" node=%p node.next=%p node.prev=%p"
" prev=%p prev.next=%p prev.owner=%p prev.owner->L=%p"
" node.owner=%p node.owner->L=%p"
" tt=%d marked=0x%x xref=%u ref=%u"
" nn=%p nn.next=%p nn.prev=%p nn.owner=%p nn.owner->L=%p nn.tt=%d\n",
where, what, (void*)L, (void*)L->heap, count,
(void*)o, o_next, o_prev,
(void*)prev, p_next, p_owner, p_owner_L,
o_owner, o_owner_L,
tt, marked, xref, ref,
o_next, nn_next, nn_prev, nn_owner, nn_owner_L, nn_tt);
abort();
}

static void validate_heap_objects(lua_State *L, const char *where)
{
GCheader *o;
GCheader *prev = NULL;
int count = 0;

TAILQ_FOREACH(o, &L->heap->objects, allocd) {
if (is_bad_gc_ptr((uintptr_t)o) ||
o->tt < LUA_TSTRING || o->tt > LUA_TGLOBAL ||
(o->marked & FREEDBIT)) {
tailq_abort(L, where, is_bad_gc_ptr((uintptr_t)o) ? "bad_ptr" : "bad_tt_or_freed",
is_bad_gc_ptr((uintptr_t)o) ? NULL : o, prev, count);
}
if (o->owner != L->heap) {
tailq_abort(L, where, "wrong_owner", o, prev, count);
}
if (is_bad_gc_ptr((uintptr_t)o->allocd.tqe_prev) ||
*(o->allocd.tqe_prev) != o) {
tailq_abort(L, where, "tqe_prev_broken", o, prev, count);
}
if (o->allocd.tqe_next != NULL &&
!is_bad_gc_ptr((uintptr_t)o->allocd.tqe_next) &&
o->allocd.tqe_next->allocd.tqe_prev != &o->allocd.tqe_next) {
tailq_abort(L, where, "tqe_next_backlink_broken", o, prev, count);
}
prev = o;
count++;
}
}

static int local_collection(lua_State *L, int type)
{
int reclaimed;
Expand All @@ -1974,6 +2083,8 @@ static int local_collection(lua_State *L, int type)
* while we are in this function and manipulating our string tables or heap */
block_collector(L, pt);

validate_heap_objects(L, "local_collection:entry");

/* prune out excess string table entries.
* We don't want to be too aggressive, as we'd like to see some benefit
* from string interning. We remove the head of each chain and repeat
Expand Down Expand Up @@ -2020,6 +2131,8 @@ static int local_collection(lua_State *L, int type)
check_references(L);
}

validate_heap_objects(L, "local_collection:after_mark");

/* run any finalizers; may turn some objects grey again */
run_finalize(L);

Expand All @@ -2035,26 +2148,34 @@ static int local_collection(lua_State *L, int type)
/* remove collected weak values from weak tables */
fixup_weak_refs(L);

validate_heap_objects(L, "local_collection:before_reclaim");

/* and now we can free whatever is left in White. Note that we're still
* blocked here so we are pulling white out of the heap and placing them
* in another list that will free them when we unblock the collector. */
reclaimed = reclaim_white(L, 0);

validate_heap_objects(L, "local_collection:after_reclaim");

/* White is the new Black */
L->black = !L->black;

sanity_check_mark_status(L);

/* Finalize deferred objects */
finalize_deferred(L);

validate_heap_objects(L, "local_collection:after_finalize");

/* Now we can un-block the global collector, as we are done with our string
* tables and our heap. */
unblock_collector(L, pt);

/* Finalize deferred objects */
finalize_deferred(L);

/* Free any objects that were white */
free_deferred_white(L);

validate_heap_objects(L, "local_collection:after_free");

/* Free any deferred stringtable nodes */
while (tofree) {
n = tofree;
Expand Down Expand Up @@ -2119,6 +2240,7 @@ static void trace_heap(GCheap *h)
GCheader *o;

ck_pr_store_32(&h->owner->xref_count, 0);
validate_heap_objects(h->owner, "trace_heap");
TAILQ_FOREACH(o, &h->objects, allocd) {
global_trace_obj(h->owner, &h->owner->gch, o);
}
Expand Down