diff --git a/cpp/main.cpp b/cpp/main.cpp index ee6451b..0068bca 100644 --- a/cpp/main.cpp +++ b/cpp/main.cpp @@ -55,16 +55,21 @@ private: int main() { LinkedList list; - + int count=0; + char * message1 = "hello world"; // Demonstrate pushing items list.push_front(10); + count++; list.push_front(20); + count++; list.push_front(30); + count++; // Demonstrate peeking at the top item if (const int* top = list.peek_front()) { - std::cout << "Top element: " << *top << "\n"; // Output: 30 + std::cout << "Top element: " << *top << "\n"<<"count: "<'s single "[contained value]" child + -- to recurse into exactly like a struct's own fields, so a contained + pointer still becomes a proper edge instead of an opaque string. + `empty_text` is what to show when there are no children (e.g. an + empty/disengaged std::optional, or an empty std::vector): the + printer's own to_string(). + + Leaning on gdb's pretty-printers rather than hardcoding STL internals + (std::optional's actual data lives behind private base-class + subobjects our own field walk would otherwise skip) means this works + uniformly for std::optional, std::unique_ptr, std::vector, etc. -- + the same principle as _flatten_variant leaning on gdb for Rust enums + instead of reimplementing niche-optimization layout ourselves.""" + printer = gdb.default_visualizer(val) + if printer is None: + return None + leaves = [] + if hasattr(printer, "children"): + try: + leaves = [(str(name), child_val) for name, child_val in printer.children()] + except gdb.error: + leaves = [] + if leaves: + return leaves, None + try: + return [], str(printer.to_string()) + except gdb.error: + return [], "" + + +def _type_name(t): + """Best-effort display name for a type (e.g. "std::optional", + "core::option::Option<...>::Some"), used as a typeHint so unwrapped + fields/roots don't lose what wrapper they came from.""" + try: + return t.tag or str(t) + except gdb.error: + return None + + +def _append_field(fields_out, name, value, is_pointer=False, type_hint=None): + field = {"name": name, "value": value, "isPointer": is_pointer} + if type_hint: + field["typeHint"] = type_hint + fields_out.append(field) + + +def _process_member(label, member_val, node_id, fields_out, edges, nodes, visited, type_hint=None): member_type = member_val.type.strip_typedefs() if member_type.code == gdb.TYPE_CODE_PTR: @@ -111,31 +164,62 @@ def _process_member(label, member_val, node_id, fields_out, edges, nodes, visite ptr_int = int(member_val) except gdb.error: ptr_int = 0 - fields_out.append({"name": label, "value": _addr_str(ptr_int), "isPointer": True}) + _append_field(fields_out, label, _addr_str(ptr_int), is_pointer=True, type_hint=type_hint) if ptr_int: edges.append((node_id, _addr_str(ptr_int), label)) _visit_pointer(member_val, ptr_int, nodes, edges, visited) return + if member_type.code in (gdb.TYPE_CODE_STRUCT, gdb.TYPE_CODE_UNION): + summary = _pretty_printer_summary(member_val) + if summary is not None: + leaves, empty_text = summary + wrapper_name = _type_name(member_type) + if not leaves: + _append_field(fields_out, label, empty_text, type_hint=wrapper_name) + elif len(leaves) == 1: + _process_member(label, leaves[0][1], node_id, fields_out, edges, nodes, visited, wrapper_name) + else: + for leaf_label, leaf_val in leaves: + _process_member( + "%s.%s" % (label, leaf_label), leaf_val, node_id, fields_out, edges, nodes, visited, + wrapper_name, + ) + return + if _is_variant_like(member_type): leaves = list(_flatten_variant(member_val, member_type, label)) + wrapper_name = _type_name(member_type) if leaves: for leaf_label, leaf_val in leaves: - _process_member(leaf_label, leaf_val, node_id, fields_out, edges, nodes, visited) + _process_member(leaf_label, leaf_val, node_id, fields_out, edges, nodes, visited, wrapper_name) else: # Unit variant (e.g. None) or an active variant we couldn't # resolve -- fall back to gdb's own rendering of it. - fields_out.append( - {"name": label, "value": _format_scalar(member_val), "isPointer": False} - ) + _append_field(fields_out, label, _format_scalar(member_val), type_hint=wrapper_name) return - fields_out.append({"name": label, "value": _format_scalar(member_val), "isPointer": False}) + _append_field(fields_out, label, _format_scalar(member_val), type_hint=type_hint) def _describe_struct(val, node_id, nodes, edges, visited): """val: a gdb.Value of struct/union type (already dereferenced).""" fields_out = [] + + summary = _pretty_printer_summary(val) + if summary is not None: + leaves, empty_text = summary + wrapper_name = _type_name(val.type.strip_typedefs()) + if not leaves: + _append_field(fields_out, "value", empty_text, type_hint=wrapper_name) + elif len(leaves) == 1: + _process_member("value", leaves[0][1], node_id, fields_out, edges, nodes, visited, wrapper_name) + else: + for leaf_label, leaf_val in leaves: + _process_member(leaf_label, leaf_val, node_id, fields_out, edges, nodes, visited, wrapper_name) + nodes[node_id] = fields_out + return + t = val.type.strip_typedefs() for f in t.fields(): if f.name is None or getattr(f, "is_base_class", False): @@ -170,6 +254,22 @@ def _visit_pointer(ptr_val, ptr_int, nodes, edges, visited): visited.add(node_id) pointee = ptr_val.dereference() _describe_struct(pointee, node_id, nodes, edges, visited) + elif pointee_type.code in (gdb.TYPE_CODE_INT, gdb.TYPE_CODE_CHAR) and pointee_type.sizeof == 1: + # char*/const char*: show the whole C string, not just *ptr (one + # byte) -- the node's own id/header is already the pointer's + # address, so this gives address + full value like everything else. + # No explicit `length`: that disables stopping at the NUL + # terminator entirely (reads exactly N bytes, running into + # whatever garbage memory follows a shorter real string). + if node_id in nodes: + return + try: + text = ptr_val.string() + if len(text) > 500: + text = text[:500] + "…" + except (gdb.error, UnicodeDecodeError): + text = _format_scalar(ptr_val.dereference()) + nodes[node_id] = [{"name": "value", "value": text, "isPointer": False}] else: if node_id in nodes: return # already described (richly or otherwise) via another path @@ -205,9 +305,10 @@ def _walk_primary_root(root_val, nodes, edges, visited): def _add_named_root(name, val, kind, nodes, edges, visited, roots_out, skip_addrs): """Walks `val` (a local/global/watched variable) same as a primary root, - then records it as a (name, value, kind) root entry -- unless its + then records it as a (name, value, kind, type) root entry -- unless its address is in skip_addrs (already covered by the primary root).""" t = val.type.strip_typedefs() + type_name = _type_name(t) if t.code == gdb.TYPE_CODE_PTR: try: ptr_int = int(val) @@ -219,7 +320,7 @@ def _add_named_root(name, val, kind, nodes, edges, visited, roots_out, skip_addr if addr in skip_addrs: return _visit_pointer(val, ptr_int, nodes, edges, visited) - roots_out.append((name, addr, kind)) + roots_out.append((name, addr, kind, type_name)) elif t.code in (gdb.TYPE_CODE_STRUCT, gdb.TYPE_CODE_UNION): try: addr = _addr_str(int(val.address)) @@ -230,9 +331,9 @@ def _add_named_root(name, val, kind, nodes, edges, visited, roots_out, skip_addr if addr not in visited: visited.add(addr) _describe_struct(val, addr, nodes, edges, visited) - roots_out.append((name, addr, kind)) + roots_out.append((name, addr, kind, type_name)) else: - roots_out.append((name, _format_scalar(val), kind)) + roots_out.append((name, _format_scalar(val), kind, type_name)) def _frame_local_symbols(frame): @@ -293,7 +394,7 @@ def build_graph_json(root_val, watched_roots): except gdb.error: ptr_int = 0 addr = _addr_str(ptr_int) - roots.append((name, addr, "watched")) + roots.append((name, addr, "watched", _type_name(ptr_val.type.strip_typedefs()))) if ptr_int and addr not in skip_addrs: _visit_pointer(ptr_val, ptr_int, nodes, edges, visited) @@ -329,7 +430,12 @@ def build_graph_json(root_val, watched_roots): for (src, dst, label) in edges if dst in nodes ] - roots_json = [{"name": name, "value": value, "kind": kind} for (name, value, kind) in roots] + roots_json = [ + {"name": name, "value": value, "kind": kind, "type": type_name} + if type_name + else {"name": name, "value": value, "kind": kind} + for (name, value, kind, type_name) in roots + ] return json.dumps( { diff --git a/scripts/dump_debug_graph.py b/scripts/dump_debug_graph.py index 9c05309..d375eae 100755 --- a/scripts/dump_debug_graph.py +++ b/scripts/dump_debug_graph.py @@ -103,14 +103,18 @@ def verify_top_delves_into_list(data): edges = data.get("edges", []) roots_by_name = {r.get("name"): r for r in data.get("roots", [])} - bare_leaves = [ + # Other auto-discovered locals (e.g. a `char*` local) can legitimately + # produce their own bare scalar-leaf nodes -- only care whether *some* + # node looks like a real Node (data+next), i.e. top's address got + # upgraded into the full struct rather than stopping at a lone int. + node_shaped = [ n for n in nodes - if [f.get("name") for f in n.get("fields", [])] == ["value"] + if [f.get("name") for f in n.get("fields", [])] == ["data", "next"] ] - if bare_leaves: - print(" VERIFY FAIL: $debug_graph(top) produced bare scalar leaf node(s) %s -- " - "top's address was never upgraded to the full Node it coincides with" - % [n.get("id") for n in bare_leaves]) + if not node_shaped: + print(" VERIFY FAIL: $debug_graph(top) produced no data+next Node-shaped node -- " + "top's address was never upgraded to the full Node it coincides with " + "(nodes=%r)" % [n.get("id") for n in nodes]) return False next_edges = [e for e in edges if e.get("label") == "next"] @@ -162,9 +166,9 @@ def main(): out_dir = Path(sys.argv[1]) if len(sys.argv) > 1 else Path.home() / "lg" out_dir.mkdir(parents=True, exist_ok=True) - ok_cpp = dump("cpp", "gdb", str(REPO / "cpp" / "list_example_cpp"), "cpp/main.cpp", 66, out_dir) + ok_cpp = dump("cpp", "gdb", str(REPO / "cpp" / "list_example_cpp"), "cpp/main.cpp", 70, out_dir) ok_cpp_top = dump( - "cpp_top", "gdb", str(REPO / "cpp" / "list_example_cpp"), "cpp/main.cpp", 66, out_dir, + "cpp_top", "gdb", str(REPO / "cpp" / "list_example_cpp"), "cpp/main.cpp", 70, out_dir, expr="$debug_graph(top)", verify=verify_top_delves_into_list, ) diff --git a/vis-plugins/node-table-visualizer.js b/vis-plugins/node-table-visualizer.js index 3b058d8..17fb4b0 100644 --- a/vis-plugins/node-table-visualizer.js +++ b/vis-plugins/node-table-visualizer.js @@ -28,6 +28,7 @@ module.exports = function (register, lib) { name: sj.sString(), value: sj.sString(), isPointer: sj.sOptionalProp(sj.sBoolean(), {}), + typeHint: sj.sOptionalProp(sj.sString(), {}), }); var sNode = sj.sOpenObject({ @@ -45,6 +46,7 @@ module.exports = function (register, lib) { name: sj.sString(), value: sj.sString(), kind: sj.sOptionalProp(sj.sString(), {}), + type: sj.sOptionalProp(sj.sString(), {}), }); var sData = sj.sOpenObject({ @@ -293,6 +295,7 @@ module.exports = function (register, lib) { bodyBg: "#252526", fieldName: "#9cdcfe", fieldValue: "#d4d4d4", + fieldType: "#7a7a7a", pointerValue: "#ce9178", edge: "#8a8a8a", viewportBg: "#1e1e1e", @@ -304,6 +307,7 @@ module.exports = function (register, lib) { bodyBg: "#ffffff", fieldName: "#0451a5", fieldValue: "#1a1a1a", + fieldType: "#8a8a8a", pointerValue: "#a31515", edge: "#6b6b6b", viewportBg: "#fafafa", @@ -532,13 +536,23 @@ module.exports = function (register, lib) { n.fields.forEach(function (f) { var row = el("tr"); + if (f.typeHint) row.title = f.name + ": " + f.typeHint + " = " + f.value; var nameCell = el("td", { padding: "2px 8px", color: colors.fieldName, whiteSpace: "nowrap", height: ROW_HEIGHT - 4 + "px", }); - nameCell.textContent = f.name; + nameCell.appendChild(document.createTextNode(f.name)); + if (f.typeHint) { + var typeSpan = el("span", { + color: colors.fieldType, + fontSize: "10px", + marginLeft: "5px", + }); + typeSpan.textContent = " " + f.typeHint; + nameCell.appendChild(typeSpan); + } var valueCell = el("td", { padding: "2px 8px", color: f.isPointer ? colors.pointerValue : colors.fieldValue, @@ -579,7 +593,7 @@ module.exports = function (register, lib) { overflow: "hidden", textOverflow: "ellipsis", }); - badge.title = r.name + " = " + r.value; + badge.title = r.name + (r.type ? " : " + r.type : "") + " = " + r.value; badge.textContent = r.name + (matched ? "" : " (" + r.value + ")"); world.appendChild(badge); var line = null; @@ -850,7 +864,7 @@ module.exports = function (register, lib) { if (rb.line) rb.line.style.display = targetId ? "" : "none"; if (!targetId) return; var redirected = targetId !== rb.root.value; - rb.badge.title = rb.root.name + " = " + rb.root.value + + rb.badge.title = rb.root.name + (rb.root.type ? " : " + rb.root.type : "") + " = " + rb.root.value + (redirected ? " (inside collapsed " + targetId + ")" : ""); var target = byId[targetId]; var stackIndex = badgesPerTarget[targetId] || 0;