scripts: Improved cycle detection notes in scripts

- Prevented childrenof memoization from hiding the source of a
  detected cycle.

- Deduplicated multiple cycle detected notes.

- Fixed note rendering when last column does not have a notes list.
  Currently this only happens when entry is None (no results).
This commit is contained in:
Christopher Haster
2024-12-07 00:55:00 -06:00
parent 56d888933f
commit ac79c88c6f
9 changed files with 91 additions and 37 deletions

View File

@@ -889,8 +889,13 @@ def table(Result, results, diff_results=None, *,
getattr(r, k, None), getattr(r, k, None),
getattr(diff_r, k, None))))) getattr(diff_r, k, None)))))
# append any notes # append any notes
if hasattr(Result, '_notes'): if hasattr(Result, '_notes') and r is not None:
entry[-1][1].extend(getattr(r, Result._notes)) notes = getattr(r, Result._notes)
if isinstance(entry[-1], tuple):
entry[-1] = (entry[-1][0], entry[-1][1] + notes)
else:
entry[-1] = (entry[-1], notes)
return entry return entry
# recursive entry helper, only used by some scripts # recursive entry helper, only used by some scripts

View File

@@ -623,8 +623,13 @@ def table(Result, results, diff_results=None, *,
getattr(r, k, None), getattr(r, k, None),
getattr(diff_r, k, None))))) getattr(diff_r, k, None)))))
# append any notes # append any notes
if hasattr(Result, '_notes'): if hasattr(Result, '_notes') and r is not None:
entry[-1][1].extend(getattr(r, Result._notes)) notes = getattr(r, Result._notes)
if isinstance(entry[-1], tuple):
entry[-1] = (entry[-1][0], entry[-1][1] + notes)
else:
entry[-1] = (entry[-1], notes)
return entry return entry
# recursive entry helper, only used by some scripts # recursive entry helper, only used by some scripts

View File

@@ -1638,8 +1638,13 @@ def table(Result, results, diff_results=None, *,
getattr(r, k, None), getattr(r, k, None),
getattr(diff_r, k, None))))) getattr(diff_r, k, None)))))
# append any notes # append any notes
if hasattr(Result, '_notes'): if hasattr(Result, '_notes') and r is not None:
entry[-1][1].extend(getattr(r, Result._notes)) notes = getattr(r, Result._notes)
if isinstance(entry[-1], tuple):
entry[-1] = (entry[-1][0], entry[-1][1] + notes)
else:
entry[-1] = (entry[-1], notes)
return entry return entry
# recursive entry helper, only used by some scripts # recursive entry helper, only used by some scripts

View File

@@ -157,7 +157,8 @@ class CtxResult(co.namedtuple('CtxResult', [
else other.i if self.i is None else other.i if self.i is None
else min(self.i, other.i), else min(self.i, other.i),
self.children + other.children, self.children + other.children,
self.notes + other.notes) list(co.OrderedDict.fromkeys(it.chain(
self.notes, other.notes)).keys()))
def openio(path, mode='r', buffering=-1): def openio(path, mode='r', buffering=-1):
@@ -681,7 +682,7 @@ def collect(obj_paths, *,
def childrenof(entry, seen=set()): def childrenof(entry, seen=set()):
# found a cycle? stop here # found a cycle? stop here
if entry.off in seen: if entry.off in seen:
return [], ['cycle detected'] return [], ['cycle detected'], True
# cached? # cached?
if not hasattr(childrenof, 'cache'): if not hasattr(childrenof, 'cache'):
childrenof.cache = {} childrenof.cache = {}
@@ -690,7 +691,7 @@ def collect(obj_paths, *,
# pointer? deref and include size # pointer? deref and include size
if entry.tag == 'DW_TAG_pointer_type': if entry.tag == 'DW_TAG_pointer_type':
children, notes = [], [] children, notes, dirty = [], [], False
if 'DW_AT_type' in entry: if 'DW_AT_type' in entry:
type = info[int(entry['DW_AT_type'].strip('<>'), 0)] type = info[int(entry['DW_AT_type'].strip('<>'), 0)]
# skip modifiers to try to find name # skip modifiers to try to find name
@@ -702,8 +703,9 @@ def collect(obj_paths, *,
and type.tag != 'DW_TAG_subroutine_type'): and type.tag != 'DW_TAG_subroutine_type'):
name_ = type.name name_ = type.name
size_ = sizeof(type, seen | {entry.off}) size_ = sizeof(type, seen | {entry.off})
children_, notes_ = childrenof( children_, notes_, dirty_ = childrenof(
type, seen | {entry.off}) type, seen | {entry.off})
dirty = dirty or dirty_
children.append(CtxResult(file, name_, size_, children.append(CtxResult(file, name_, size_,
children=children_, children=children_,
notes=notes_)) notes=notes_))
@@ -711,14 +713,15 @@ def collect(obj_paths, *,
elif entry.tag in { elif entry.tag in {
'DW_TAG_structure_type', 'DW_TAG_structure_type',
'DW_TAG_union_type'}: 'DW_TAG_union_type'}:
children, notes = [], [] children, notes, dirty = [], [], False
for child in entry.children: for child in entry.children:
if child.tag != 'DW_TAG_member': if child.tag != 'DW_TAG_member':
continue continue
name_ = child.name name_ = child.name
size_ = sizeof(child, seen | {entry.off}) size_ = sizeof(child, seen | {entry.off})
children_, notes_ = childrenof( children_, notes_, dirty_ = childrenof(
child, seen | {entry.off}) child, seen | {entry.off})
dirty = dirty or dirty_
children.append(CtxResult(file, name_, size_, children.append(CtxResult(file, name_, size_,
i=child.off, i=child.off,
children=children_, children=children_,
@@ -727,7 +730,7 @@ def collect(obj_paths, *,
elif entry.tag in { elif entry.tag in {
'DW_TAG_base_type', 'DW_TAG_base_type',
'DW_TAG_subroutine_type'}: 'DW_TAG_subroutine_type'}:
children, notes = [], [] children, notes, dirty = [], [], False
# a modifier? # a modifier?
elif (entry.tag in { elif (entry.tag in {
'DW_TAG_typedef', 'DW_TAG_typedef',
@@ -740,17 +743,18 @@ def collect(obj_paths, *,
'DW_TAG_restrict_type'} 'DW_TAG_restrict_type'}
and 'DW_AT_type' in entry): and 'DW_AT_type' in entry):
type = int(entry['DW_AT_type'].strip('<>'), 0) type = int(entry['DW_AT_type'].strip('<>'), 0)
children, notes = childrenof( children, notes, dirty = childrenof(
info[type], seen | {entry.off}) info[type], seen | {entry.off})
# void? # void?
elif ('DW_AT_type' not in entry elif ('DW_AT_type' not in entry
and 'DW_AT_byte_size' not in entry): and 'DW_AT_byte_size' not in entry):
children, notes = [], [] children, notes = [], [], False
else: else:
assert False, "Unknown dwarf entry? %r" % entry.tag assert False, "Unknown dwarf entry? %r" % entry.tag
childrenof.cache[entry.off] = children, notes if not dirty:
return children, notes childrenof.cache[entry.off] = children, notes, dirty
return children, notes, dirty
# find each function's context # find each function's context
for sym in syms: for sym in syms:
@@ -796,7 +800,7 @@ def collect(obj_paths, *,
size_ = sizeof(param) size_ = sizeof(param)
# find children, recursing if necessary # find children, recursing if necessary
children_, notes_ = childrenof(param) children_, notes_, _ = childrenof(param)
params.append(CtxResult(file, name_, size_, params.append(CtxResult(file, name_, size_,
i=param.off, i=param.off,
@@ -1078,8 +1082,13 @@ def table(Result, results, diff_results=None, *,
getattr(r, k, None), getattr(r, k, None),
getattr(diff_r, k, None))))) getattr(diff_r, k, None)))))
# append any notes # append any notes
if hasattr(Result, '_notes'): if hasattr(Result, '_notes') and r is not None:
entry[-1][1].extend(getattr(r, Result._notes)) notes = getattr(r, Result._notes)
if isinstance(entry[-1], tuple):
entry[-1] = (entry[-1][0], entry[-1][1] + notes)
else:
entry[-1] = (entry[-1], notes)
return entry return entry
# recursive entry helper, only used by some scripts # recursive entry helper, only used by some scripts

View File

@@ -889,8 +889,13 @@ def table(Result, results, diff_results=None, *,
getattr(r, k, None), getattr(r, k, None),
getattr(diff_r, k, None))))) getattr(diff_r, k, None)))))
# append any notes # append any notes
if hasattr(Result, '_notes'): if hasattr(Result, '_notes') and r is not None:
entry[-1][1].extend(getattr(r, Result._notes)) notes = getattr(r, Result._notes)
if isinstance(entry[-1], tuple):
entry[-1] = (entry[-1][0], entry[-1][1] + notes)
else:
entry[-1] = (entry[-1], notes)
return entry return entry
# recursive entry helper, only used by some scripts # recursive entry helper, only used by some scripts

View File

@@ -1069,8 +1069,13 @@ def table(Result, results, diff_results=None, *,
getattr(r, k, None), getattr(r, k, None),
getattr(diff_r, k, None))))) getattr(diff_r, k, None)))))
# append any notes # append any notes
if hasattr(Result, '_notes'): if hasattr(Result, '_notes') and r is not None:
entry[-1][1].extend(getattr(r, Result._notes)) notes = getattr(r, Result._notes)
if isinstance(entry[-1], tuple):
entry[-1] = (entry[-1][0], entry[-1][1] + notes)
else:
entry[-1] = (entry[-1], notes)
return entry return entry
# recursive entry helper, only used by some scripts # recursive entry helper, only used by some scripts

View File

@@ -1034,8 +1034,13 @@ def table(Result, results, diff_results=None, *,
getattr(r, k, None), getattr(r, k, None),
getattr(diff_r, k, None))))) getattr(diff_r, k, None)))))
# append any notes # append any notes
if hasattr(Result, '_notes'): if hasattr(Result, '_notes') and r is not None:
entry[-1][1].extend(getattr(r, Result._notes)) notes = getattr(r, Result._notes)
if isinstance(entry[-1], tuple):
entry[-1] = (entry[-1][0], entry[-1][1] + notes)
else:
entry[-1] = (entry[-1], notes)
return entry return entry
# recursive entry helper, only used by some scripts # recursive entry helper, only used by some scripts

View File

@@ -153,7 +153,8 @@ class StackResult(co.namedtuple('StackResult', [
self.frame + other.frame, self.frame + other.frame,
max(self.limit, other.limit), max(self.limit, other.limit),
self.children + other.children, self.children + other.children,
self.notes + other.notes) list(co.OrderedDict.fromkeys(it.chain(
self.notes, other.notes)).keys()))
def openio(path, mode='r', buffering=-1): def openio(path, mode='r', buffering=-1):
@@ -873,7 +874,7 @@ def collect(obj_paths, *,
def limitof(func, seen=set()): def limitof(func, seen=set()):
# found a cycle? stop here # found a cycle? stop here
if id(func) in seen: if id(func) in seen:
return 0, 0 return 0, mt.inf
# cached? # cached?
if not hasattr(limitof, 'cache'): if not hasattr(limitof, 'cache'):
limitof.cache = {} limitof.cache = {}
@@ -903,7 +904,7 @@ def collect(obj_paths, *,
def childrenof(func, seen=set()): def childrenof(func, seen=set()):
# found a cycle? stop here # found a cycle? stop here
if id(func) in seen: if id(func) in seen:
return [], ['cycle detected'] return [], ['cycle detected'], True
# cached? # cached?
if not hasattr(childrenof, 'cache'): if not hasattr(childrenof, 'cache'):
childrenof.cache = {} childrenof.cache = {}
@@ -912,17 +913,20 @@ def collect(obj_paths, *,
# find children recursively # find children recursively
children = [] children = []
dirty = False
for addr, callee in func['calls']: for addr, callee in func['calls']:
file_ = callee['file'] file_ = callee['file']
name_ = callee['sym'].name name_ = callee['sym'].name
frame_, limit_ = limitof(callee, seen | {id(func)}) frame_, limit_ = limitof(callee, seen | {id(func)})
children_, notes_ = childrenof(callee, seen | {id(func)}) children_, notes_, dirty_ = childrenof(callee, seen | {id(func)})
dirty = dirty or dirty_
children.append(StackResult(file_, name_, frame_, limit_, children.append(StackResult(file_, name_, frame_, limit_,
children=children_, children=children_,
notes=notes_)) notes=notes_))
childrenof.cache[id(func)] = children, [] if not dirty:
return children, [] childrenof.cache[id(func)] = children, [], dirty
return children, [], dirty
# build results # build results
results = [] results = []
@@ -930,7 +934,7 @@ def collect(obj_paths, *,
file = func['file'] file = func['file']
name = func['sym'].name name = func['sym'].name
frame, limit = limitof(func) frame, limit = limitof(func)
children, notes = childrenof(func) children, notes, _ = childrenof(func)
results.append(StackResult(file, name, frame, limit, results.append(StackResult(file, name, frame, limit,
children=children, children=children,
@@ -1205,8 +1209,13 @@ def table(Result, results, diff_results=None, *,
getattr(r, k, None), getattr(r, k, None),
getattr(diff_r, k, None))))) getattr(diff_r, k, None)))))
# append any notes # append any notes
if hasattr(Result, '_notes'): if hasattr(Result, '_notes') and r is not None:
entry[-1][1].extend(getattr(r, Result._notes)) notes = getattr(r, Result._notes)
if isinstance(entry[-1], tuple):
entry[-1] = (entry[-1][0], entry[-1][1] + notes)
else:
entry[-1] = (entry[-1], notes)
return entry return entry
# recursive entry helper, only used by some scripts # recursive entry helper, only used by some scripts
@@ -1419,6 +1428,7 @@ def main(obj_paths,
by=by if by is not None else ['function'], by=by if by is not None else ['function'],
fields=fields, fields=fields,
sort=sort, sort=sort,
detect_cycles=False,
**args) **args)
# error on recursion # error on recursion

View File

@@ -906,8 +906,13 @@ def table(Result, results, diff_results=None, *,
getattr(r, k, None), getattr(r, k, None),
getattr(diff_r, k, None))))) getattr(diff_r, k, None)))))
# append any notes # append any notes
if hasattr(Result, '_notes'): if hasattr(Result, '_notes') and r is not None:
entry[-1][1].extend(getattr(r, Result._notes)) notes = getattr(r, Result._notes)
if isinstance(entry[-1], tuple):
entry[-1] = (entry[-1][0], entry[-1][1] + notes)
else:
entry[-1] = (entry[-1], notes)
return entry return entry
# recursive entry helper, only used by some scripts # recursive entry helper, only used by some scripts