Skip to content

Commit 4a7c1d5

Browse files
committed
fix(callgrind): aggregate (cxt==0) and underflow leaks under a sentinel cxt
When setup_bbcc's (cxt==0) clause or handleUnderflow would force-push a skipped fn into the current context, push a synthetic (skipped) fn instead. The skipped fn keeps its costs (routed normally through the sentinel cxt) but never surfaces as its own fn= block — the dump shows a single ob=??? fl=(callgrind-internal) fn=(skipped) block aggregating all leaked frames. The sentinel itself has skip=False so the (cxt==0 && skip) substitution doesn't recurse on it. Created lazily on first need via a singleton in fn.c, attached to the anonymous '???' obj. Verified against both C reproducers (runtime_obj_skip_c and runtime_obj_skip_underflow): no skipme_* fn= blocks appear, totals are preserved. Verified against a non-skipped-attribution test that main / do_main_work still emit normally; the sentinel only engages on the leak paths.
1 parent f243797 commit 4a7c1d5

3 files changed

Lines changed: 32 additions & 0 deletions

File tree

callgrind/bbcc.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -518,6 +518,13 @@ static void handleUnderflow(BB* bb)
518518
"obj='%s' skip=%d\n",
519519
bb_addr(bb), caller->name,
520520
caller->file->obj->name, caller->skip);
521+
522+
/* A (sentinel): if the fn we'd return into is itself skipped, push
523+
* the (skipped) sentinel instead so the skipped fn doesn't surface
524+
* as its own fn= block in the dump. */
525+
if (caller->skip)
526+
caller = CLG_(get_skipped_sentinel)();
527+
521528
CLG_(push_cxt)( caller );
522529

523530
if (!seen_before) {
@@ -837,6 +844,9 @@ void CLG_(setup_bbcc)(BB* bb)
837844
push_fn->name,
838845
push_fn->file->obj->name,
839846
(int)jmpkind, (int)delayed_push);
847+
/* A (sentinel): substitute the (skipped) sentinel so the
848+
* skipped fn doesn't appear as its own fn= block in the dump. */
849+
push_fn = CLG_(get_skipped_sentinel)();
840850
}
841851
CLG_(push_cxt)(push_fn);
842852
}

callgrind/fn.c

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,27 @@ static Bool name_contains(const HChar* hay, const HChar* needle)
336336
return False;
337337
}
338338

339+
static fn_node* new_fn_node(const HChar *fnname,
340+
file_node* file, fn_node* next);
341+
342+
/* Singleton sentinel fn_node used as a placeholder cxt when we'd
343+
* otherwise be forced to push a skipped fn into an empty (cxt == 0)
344+
* context. Keeping skip == False on the sentinel itself is crucial:
345+
* the (cxt == 0 && skip) check that would push it must NOT recurse
346+
* on the sentinel. */
347+
static fn_node* skipped_sentinel = NULL;
348+
349+
fn_node* CLG_(get_skipped_sentinel)(void)
350+
{
351+
if (skipped_sentinel) return skipped_sentinel;
352+
353+
obj_node* obj = CLG_(get_obj_node)(NULL); /* anonymous "???" obj */
354+
file_node* file = CLG_(get_file_node)(obj, "", "(callgrind-internal)");
355+
skipped_sentinel = new_fn_node("(skipped)", file, NULL);
356+
skipped_sentinel->skip = False;
357+
return skipped_sentinel;
358+
}
359+
339360
void CLG_(dump_python_fn_summary)(void)
340361
{
341362
Int total = 0, checked = 0, skipped = 0;

callgrind/global.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -725,6 +725,7 @@ UInt* CLG_(get_fn_entry)(Int n);
725725
void CLG_(init_obj_table)(void);
726726
void CLG_(count_obj_skip_checked_fns)(Int* checked, Int* skipped);
727727
void CLG_(dump_python_fn_summary)(void);
728+
fn_node* CLG_(get_skipped_sentinel)(void);
728729
obj_node* CLG_(get_obj_node)(DebugInfo* si);
729730
file_node* CLG_(get_file_node)(obj_node*, const HChar *dirname,
730731
const HChar* filename);

0 commit comments

Comments
 (0)