mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-22 05:59:31 +08:00
scr_union_release buffered every union box left alive by a release as a cycle-collector candidate, including boxes whose arm_trace is NULL (scalar arms, and ref arms whose type the compiler proved acyclic, such as TreeNode | null once TreeNode is constructor-only). Such a box's trace visits nothing, so it can never be on a collectable cycle: skip the candidate buffer for it. Deaths check the header's buffered bit inline before calling scr_cyc_on_dead, because typed-ref cache commits swap arm contents between boxes of one union type, so a box can have been buffered under an earlier arm. test_closure.c checks that string- and f64-armed boxes are never buffered, that closure-armed boxes are, and that a closure -> box -> union -> closure cycle is still collected under the RC audit.
135 lines
5.6 KiB
C
135 lines
5.6 KiB
C
/* Unit tests for boxes and closures: RC cascades (closure → box → string),
|
|
* scalar and ref slots, set_ref release of old contents, immortal closures.
|
|
* Built with -DSCR_RC_AUDIT; exits nonzero on any failure or leak.
|
|
*/
|
|
#include "../src/scr_runtime.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
extern long scr_str_live_count(void);
|
|
extern long scr_box_live_count(void);
|
|
extern long scr_closure_live_count(void);
|
|
extern long scr_union_live_count(void);
|
|
|
|
static int failures = 0;
|
|
#define CHECK(cond) \
|
|
do { \
|
|
if (!(cond)) { \
|
|
failures++; \
|
|
fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \
|
|
} \
|
|
} while (0)
|
|
|
|
static double dummy_fn(ScrClosure *env, double x) {
|
|
(void)env;
|
|
return x * 2;
|
|
}
|
|
|
|
int main(void) {
|
|
/* scalar box */
|
|
ScrBox *nb = scr_box_new(SCR_BOX_F64);
|
|
CHECK(scr_box_get_f64(nb) == 0);
|
|
scr_box_set_f64(nb, 3.5);
|
|
CHECK(scr_box_get_f64(nb) == 3.5);
|
|
|
|
/* string box: set_ref releases the old value; get_ref returns +1 */
|
|
ScrBox *sb = scr_box_new(SCR_BOX_STR);
|
|
scr_box_set_ref(sb, scr_str_new("first", 5));
|
|
scr_box_set_ref(sb, scr_str_new("second", 6)); /* releases "first" */
|
|
CHECK(scr_str_live_count() == 1);
|
|
ScrStr *out = (ScrStr *)scr_box_get_ref(sb);
|
|
CHECK(out->rc == 2 && strcmp(out->data, "second") == 0);
|
|
scr_str_release(out);
|
|
|
|
/* closure holding boxes: release cascades */
|
|
ScrClosure *c = scr_closure_new((void *)&dummy_fn, 2);
|
|
c->caps[0] = scr_box_retain(nb);
|
|
c->caps[1] = scr_box_retain(sb);
|
|
CHECK(((double (*)(ScrClosure *, double))c->fn)(c, 21) == 42);
|
|
scr_box_release(nb);
|
|
scr_box_release(sb);
|
|
CHECK(scr_box_live_count() == 2); /* kept alive by the closure */
|
|
scr_closure_release(c);
|
|
CHECK(scr_box_live_count() == 0);
|
|
CHECK(scr_str_live_count() == 0);
|
|
CHECK(scr_closure_live_count() == 0);
|
|
|
|
/* box holding a closure: cascade through SCR_BOX_FUNC */
|
|
ScrBox *fb = scr_box_new(SCR_BOX_FUNC);
|
|
scr_box_set_ref(fb, scr_closure_new((void *)&dummy_fn, 0));
|
|
CHECK(scr_closure_live_count() == 1);
|
|
scr_box_release(fb);
|
|
CHECK(scr_closure_live_count() == 0);
|
|
|
|
/* identity roots: an adapter of an adapter answers the original */
|
|
ScrClosure *root = scr_closure_new((void *)&dummy_fn, 0);
|
|
ScrClosure *view = scr_closure_new((void *)&dummy_fn, 0);
|
|
ScrClosure *view2 = scr_closure_new((void *)&dummy_fn, 0);
|
|
ScrClosure *other = scr_closure_new((void *)&dummy_fn, 0);
|
|
CHECK(scr_closure_identity(root) == root && scr_closure_identity(NULL) == NULL);
|
|
scr_closure_adopt_identity(view, root, 1);
|
|
scr_closure_adopt_identity(view2, view, 3);
|
|
CHECK(scr_closure_identity(view) == root && scr_closure_identity(view2) == root);
|
|
CHECK(view->identity_length == 1 && view2->identity_length == 1);
|
|
CHECK(scr_closure_identity_equal(view2, root) && scr_closure_identity_equal(view, view2));
|
|
CHECK(!scr_closure_identity_equal(view, other) && !scr_closure_identity_equal(root, other));
|
|
scr_closure_release(view2);
|
|
scr_closure_release(view);
|
|
scr_closure_release(other);
|
|
scr_closure_release(root);
|
|
CHECK(scr_closure_live_count() == 0);
|
|
|
|
/* immortal closure: retain/release are no-ops */
|
|
static struct { size_t rc; void *fn; size_t ncaps; } immortal = {SIZE_MAX, NULL, 0};
|
|
immortal.fn = (void *)&dummy_fn;
|
|
ScrClosure *ic = (ScrClosure *)&immortal;
|
|
CHECK(scr_closure_retain(ic) == ic);
|
|
scr_closure_release(ic);
|
|
CHECK(ic->rc == SIZE_MAX);
|
|
|
|
/* union boxes: only a box whose trace visits its payload can be a cycle
|
|
* root, so only such a box is buffered when a release leaves it alive */
|
|
ScrUnion *su = scr_union_new_ref(0, scr_str_new("u", 1), &scr_str_retain_v,
|
|
&scr_str_release_v, NULL);
|
|
scr_union_retain(su);
|
|
scr_union_release(su);
|
|
CHECK(!scr_cyc_hdr(su)->buffered);
|
|
scr_union_release(su);
|
|
CHECK(scr_str_live_count() == 0);
|
|
ScrUnion *fu = scr_union_new_f64(1, 2.5);
|
|
scr_union_retain(fu);
|
|
scr_union_release(fu);
|
|
CHECK(!scr_cyc_hdr(fu)->buffered);
|
|
scr_union_release(fu);
|
|
ScrUnion *cu = scr_union_new_ref(0, scr_closure_new((void *)&dummy_fn, 0),
|
|
&scr_closure_retain_v, &scr_closure_release_v,
|
|
&scr_closure_trace_v);
|
|
scr_union_retain(cu);
|
|
scr_union_release(cu);
|
|
CHECK(scr_cyc_hdr(cu)->buffered);
|
|
scr_union_release(cu); /* dies while buffered: leaves the buffer, frees */
|
|
CHECK(scr_closure_live_count() == 0);
|
|
|
|
/* a cycle through a traced union box is still collected:
|
|
* closure -> box -> union -> closure */
|
|
ScrClosure *loop = scr_closure_new((void *)&dummy_fn, 1);
|
|
ScrBox *ub = scr_box_new_obj(&scr_union_retain_v, &scr_union_release_v,
|
|
&scr_union_trace_v);
|
|
loop->caps[0] = ub; /* the closure owns the box's only reference */
|
|
scr_box_set_ref(ub, scr_union_new_ref(0, scr_closure_retain(loop),
|
|
&scr_closure_retain_v,
|
|
&scr_closure_release_v,
|
|
&scr_closure_trace_v));
|
|
scr_closure_release(loop); /* only the cycle keeps it alive now */
|
|
CHECK(scr_closure_live_count() == 1);
|
|
scr_collect_cycles();
|
|
CHECK(scr_closure_live_count() == 0);
|
|
CHECK(scr_box_live_count() == 0);
|
|
CHECK(scr_union_live_count() == 0);
|
|
|
|
fprintf(stderr, "%s\n", failures ? "FAILED" : "all closure tests passed");
|
|
return failures ? 1 : 0;
|
|
}
|