| 413 | } |
| 414 | |
| 415 | void composeNode(ComposeState& state, const NodeTree& tree, |
| 416 | const Provider& prov, int nodeIdx, int depth, |
| 417 | uint64_t base, uint64_t rootId, bool isArrayChild, |
| 418 | uint64_t scopeId, int arrayElementIdx, |
| 419 | uint64_t arrayContainerAddr) { |
| 420 | const Node& node = tree.nodes[nodeIdx]; |
| 421 | uint64_t absAddr = resolveAddr(state, tree, nodeIdx, base, rootId); |
| 422 | |
| 423 | // Get per-scope widths for this node |
| 424 | int typeW = state.effectiveTypeW(scopeId); |
| 425 | int nameW = state.effectiveNameW(scopeId); |
| 426 | |
| 427 | // Pointer deref expansion — single fold header merges pointer + struct header |
| 428 | if ((node.kind == NodeKind::Pointer32 || node.kind == NodeKind::Pointer64) |
| 429 | && node.refId != 0) { |
| 430 | QString ptrTargetName = resolvePointerTarget(tree, node.refId); |
| 431 | QString ptrTypeOverride = fmt::pointerTypeName(node.kind, ptrTargetName); |
| 432 | |
| 433 | // Emit merged fold header: "Type* Name {" (expanded) or "Type* Name -> val" (collapsed) |
| 434 | { |
| 435 | LineMeta lm; |
| 436 | lm.nodeIdx = nodeIdx; |
| 437 | lm.nodeId = node.id; |
| 438 | lm.depth = depth; |
| 439 | lm.lineKind = node.collapsed ? LineKind::Field : LineKind::Header; |
| 440 | lm.offsetText = fmt::fmtOffsetMargin(tree.baseAddress + absAddr, false, state.offsetHexDigits); |
| 441 | lm.offsetAddr = tree.baseAddress + absAddr; |
| 442 | lm.nodeKind = node.kind; |
| 443 | lm.foldHead = true; |
| 444 | lm.foldCollapsed = node.collapsed; |
| 445 | lm.foldLevel = computeFoldLevel(depth, true); |
| 446 | lm.markerMask = computeMarkers(node, prov, absAddr, false, depth); |
| 447 | lm.effectiveTypeW = typeW; |
| 448 | lm.effectiveNameW = nameW; |
| 449 | lm.pointerTargetName = ptrTargetName; |
| 450 | state.emitLine(fmt::fmtPointerHeader(node, depth, node.collapsed, |
| 451 | prov, absAddr, ptrTypeOverride, |
| 452 | typeW, nameW), lm); |
| 453 | } |
| 454 | |
| 455 | if (!node.collapsed) { |
| 456 | int sz = node.byteSize(); |
| 457 | uint64_t ptrVal = 0; |
| 458 | if (prov.isValid() && sz > 0 && prov.isReadable(absAddr, sz)) { |
| 459 | ptrVal = (node.kind == NodeKind::Pointer32) |
| 460 | ? (uint64_t)prov.readU32(absAddr) : prov.readU64(absAddr); |
| 461 | if (ptrVal != 0) { |
| 462 | // Treat sentinel values as invalid pointers |
| 463 | if (ptrVal == UINT64_MAX || (node.kind == NodeKind::Pointer32 && ptrVal == 0xFFFFFFFF)) |
| 464 | ptrVal = 0; |
| 465 | else { |
| 466 | uint64_t pBase = ptrToProviderAddr(tree, ptrVal); |
| 467 | if (pBase == UINT64_MAX) ptrVal = 0; // ptr below base: invalid |
| 468 | } |
| 469 | } |
| 470 | } |
| 471 | |
| 472 | // Determine if pointer target is actually readable |
no test coverage detected