mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-30 07:14:39 +08:00
ProgramLifetime in sanitized builds: skip the RC audit's leak report and scan instances as LeakSanitizer roots
Program-lifetime instances are never freed, and neither is anything they hold, so the RC audit of --sanitize builds reported every one of them, and every string, array and map they reference, as live at exit (4633-contract-program-lifetime failed the sanitized differential lane: 306 strings, 15 arrays, 338 objects). Like a program that abandons fibers, a program that keeps program-lifetime objects now skips the leak report with "scriptc RC audit skipped: N program-lifetime object(s) never freed", which the harnesses already filter. Sanitized constructors call scr_obj_lifetime_note(obj), which counts the instance and registers it with LeakSanitizer as an intentional root, as publication does for published objects: once the audit no longer exits early, LeakSanitizer would otherwise report the instances the program drops. Plain builds emit nothing.
This commit is contained in:
@@ -123,7 +123,12 @@ test("ProgramLifetime hierarchies are immortal from construction and take no ref
|
||||
expect(nw, cls).toContain("store i64 -1, ptr %o");
|
||||
expect(nw, cls).not.toContain("scr_cyc_alloc");
|
||||
expect(llvm, cls).not.toContain(`@${mangleClassTrace(cls)}.cyctype`);
|
||||
// The RC audit (sanitize builds) learns that the program keeps
|
||||
// program-lifetime objects; other builds emit nothing for it.
|
||||
expect(nw, cls).toContain("call void @scr_obj_lifetime_note(ptr %o)");
|
||||
}
|
||||
const plain = emitLlvmModule(mod, { pointerBits: 64, objectAudit: false });
|
||||
expect(plain).not.toContain("scr_obj_lifetime_note");
|
||||
// The mortal Owner keeps its header, counts and possible-root step.
|
||||
expect(body(mangleClassNew("Owner"))).toContain("store i64 1, ptr %o");
|
||||
expect(body(mangleClassNew("Owner"))).toContain("scr_cyc_alloc");
|
||||
|
||||
@@ -632,6 +632,13 @@ export function emitClassShapes(
|
||||
),
|
||||
);
|
||||
nw.push(` store ${host.sizeType} ${lifetime ? -1 : 1}, ptr %o`);
|
||||
if (lifetime && audit) {
|
||||
// --sanitize builds: the RC audit skips its leak report when the
|
||||
// program keeps program-lifetime objects (scr_console.c), and
|
||||
// LeakSanitizer scans them as roots (scr_object.c).
|
||||
host.declare(`declare void @scr_obj_lifetime_note(ptr)`);
|
||||
nw.push(` call void @scr_obj_lifetime_note(ptr %o)`);
|
||||
}
|
||||
if (meta.hierarchy) {
|
||||
nw.push(
|
||||
` %vtp = getelementptr inbounds %${struct}, ptr %o, i64 0, i32 1`,
|
||||
|
||||
@@ -43,6 +43,7 @@ extern long scr_map_live_count(void); /* scr_map.c */
|
||||
extern long scr_box_live_count(void); /* scr_closure.c */
|
||||
extern long scr_closure_live_count(void); /* scr_closure.c */
|
||||
extern long scr_obj_live_count(void); /* scr_object.c */
|
||||
extern long scr_obj_lifetime_count(void); /* scr_object.c */
|
||||
extern long scr_union_live_count(void); /* scr_union.c */
|
||||
extern long scr_dyn_live_count(void); /* scr_json.c */
|
||||
extern long scr_bytes_live_count(void); /* scr_bytes.c */
|
||||
@@ -59,6 +60,15 @@ static void scr_rc_audit_at_exit(void) {
|
||||
scr_abandoned_fibers);
|
||||
return;
|
||||
}
|
||||
/* ProgramLifetime instances (@scriptc/types) are never freed, and neither
|
||||
* is anything they hold: a strict audit would only report that deliberate
|
||||
* retention. */
|
||||
long lifetime = scr_obj_lifetime_count();
|
||||
if (lifetime > 0) {
|
||||
fprintf(stderr, "scriptc RC audit skipped: %ld program-lifetime object(s) never freed\n",
|
||||
lifetime);
|
||||
return;
|
||||
}
|
||||
long strings = scr_str_live_count();
|
||||
long arrays = scr_arr_live_count();
|
||||
long maps = scr_map_live_count();
|
||||
|
||||
@@ -87,12 +87,24 @@ void scr_record_key_miss(ScrStr *k) {
|
||||
|
||||
#ifdef SCR_RC_AUDIT
|
||||
static SCR_TL long scr_live_objects = 0;
|
||||
/* ProgramLifetime instances (@scriptc/types) this thread created: never
|
||||
* freed by design, and neither is anything they hold (see the exit audit in
|
||||
* scr_console.c). */
|
||||
static SCR_TL long scr_lifetime_objects = 0;
|
||||
long scr_obj_live_count(void) { return scr_live_objects; }
|
||||
long scr_obj_lifetime_count(void) { return scr_lifetime_objects; }
|
||||
void scr_obj_alloc_note(void) { scr_live_objects++; }
|
||||
void scr_obj_free_note(void) { scr_live_objects--; }
|
||||
/* Also an intentional LeakSanitizer root, like published objects: what the
|
||||
* instance holds stays reachable. */
|
||||
void scr_obj_lifetime_note(const void *obj) {
|
||||
scr_lifetime_objects++;
|
||||
scr_lsan_immortal(obj);
|
||||
}
|
||||
#else
|
||||
#undef scr_obj_alloc_note
|
||||
#undef scr_obj_free_note
|
||||
void scr_obj_alloc_note(void) {}
|
||||
void scr_obj_free_note(void) {}
|
||||
void scr_obj_lifetime_note(const void *obj) { (void)obj; }
|
||||
#endif
|
||||
|
||||
@@ -5416,8 +5416,13 @@ long scr_dyn_live_count(void);
|
||||
*/
|
||||
void scr_obj_alloc_note(void);
|
||||
void scr_obj_free_note(void);
|
||||
/* Emitted by the RC-audit lane's ProgramLifetime constructors, after the
|
||||
* allocation note: such instances are never freed (the exit audit skips its
|
||||
* leak report, and LeakSanitizer scans them as roots). */
|
||||
void scr_obj_lifetime_note(const void *obj);
|
||||
#ifdef SCR_RC_AUDIT
|
||||
long scr_obj_live_count(void);
|
||||
long scr_obj_lifetime_count(void);
|
||||
#else
|
||||
/* Only the RC audit lane counts live objects. Other runtimes keep the
|
||||
* out-of-line no-op symbols for direct-emission objects that still call
|
||||
|
||||
Reference in New Issue
Block a user