Internals: Coverage cleanups before UAF fix (#8494)

This commit is contained in:
em2machine
2026-09-25 18:54:53 -04:00
committed by GitHub
parent f6ff5b4cfb
commit 435af6afe6
5 changed files with 60 additions and 193 deletions
+7 -17
View File
@@ -3358,8 +3358,7 @@ class LinkDotResolveVisitor final : public VNVisitor {
// Capture a ParamTypeDType reference for interface typedef retargeting.
// Called when a RefDType resolves to a ParamTypeDType owned by an interface.
void captureIfaceParamType(AstRefDType* nodep, AstParamTypeDType* defp,
const V3LinkDotIfaceCapture::CapturedEntry* capEntryp) {
void captureIfaceParamType(AstRefDType* nodep, AstParamTypeDType* defp) {
if (!V3LinkDotIfaceCapture::enabled() || !m_statep->forPrimary()) return;
AstNodeModule* const defOwnerModp = V3LinkDotIfaceCapture::findOwnerModule(defp);
if (!defOwnerModp || !VN_IS(defOwnerModp, Iface)) return;
@@ -3369,8 +3368,7 @@ class LinkDotResolveVisitor final : public VNVisitor {
UINFO(9, indent() << "iface capture add paramtype " << nodep
<< " iface=" << defOwnerModp->prettyNameQ());
V3LinkDotIfaceCapture::addParamType(nodep, cellForCapture->name(), m_modp, defp,
defOwnerModp->name(),
capEntryp ? capEntryp->ifacePortVarp : nullptr);
defOwnerModp->name());
}
AstNodeStmt* addImplicitSuperNewCall(AstFunc* const nodep,
@@ -4780,9 +4778,8 @@ class LinkDotResolveVisitor final : public VNVisitor {
refp->typedefp(defp);
V3LinkDotIfaceCapture::captureTypedefContext(
refp, "typedef", static_cast<int>(m_ds.m_dotPos),
m_ds.m_dotPos == DP_FINAL, m_ds.m_dotText, m_ds.m_dotSymp, m_curSymp,
m_modp, nodep, [this]() { return indent(); });
refp, "typedef", static_cast<int>(m_ds.m_dotPos), m_ds.m_dotText,
m_ds.m_dotSymp, m_modp, [this]() { return indent(); });
if (VN_IS(nodep->backp(), SelExtract)) {
m_packedArrayDtp = refp;
@@ -4799,9 +4796,8 @@ class LinkDotResolveVisitor final : public VNVisitor {
refp->refDTypep(defp);
V3LinkDotIfaceCapture::captureTypedefContext(
refp, "paramtype", static_cast<int>(m_ds.m_dotPos),
m_ds.m_dotPos == DP_FINAL, m_ds.m_dotText, m_ds.m_dotSymp, m_curSymp,
m_modp, nodep, [this]() { return indent(); });
refp, "paramtype", static_cast<int>(m_ds.m_dotPos), m_ds.m_dotText,
m_ds.m_dotSymp, m_modp, [this]() { return indent(); });
if (VN_IS(nodep->backp(), SelExtract)) {
m_packedArrayDtp = refp;
@@ -6102,10 +6098,6 @@ class LinkDotResolveVisitor final : public VNVisitor {
}
// Resolve its reference
if (V3LinkDotIfaceCapture::find(nodep)) {
UINFO(9, indent() << "iface capture visit captured typedef ptr=" << nodep
<< " user2=" << nodep->user2p());
}
if (m_statep->forParamed() && nodep->user3()) {
if (V3LinkDotIfaceCapture::enabled() && nodep->user2p()) {
UINFO(9, indent() << "iface capture clear user3 for captured typedef name="
@@ -6189,8 +6181,6 @@ class LinkDotResolveVisitor final : public VNVisitor {
VL_DO_DANGLING(pushDeletep(cpackagep->unlinkFrBack()), cpackagep);
}
const V3LinkDotIfaceCapture::CapturedEntry* capEntryp = V3LinkDotIfaceCapture::find(nodep);
if (m_ds.m_dotp && (m_ds.m_dotPos == DP_PACKAGE || m_ds.m_dotPos == DP_SCOPE)) {
UASSERT_OBJ(VN_IS(m_ds.m_dotp->lhsp(), ClassOrPackageRef), m_ds.m_dotp->lhsp(),
"Bad package link");
@@ -6271,7 +6261,7 @@ class LinkDotResolveVisitor final : public VNVisitor {
} else {
nodep->refDTypep(defp);
nodep->classOrPackagep(foundp->classOrPackagep());
captureIfaceParamType(nodep, defp, capEntryp);
captureIfaceParamType(nodep, defp);
}
} else if (AstClass* const defp
= foundp ? VN_CAST(foundp->nodep(), Class) : nullptr) {
+22 -94
View File
@@ -19,9 +19,8 @@
// The IfaceCapture system has three phases with strict responsibilities:
//
// 1. CAPTURE (V3LinkDot, primary pass):
// add() / addParamType() / addTypedef() record template entries.
// Template entries store the REFDTYPE, its cellPath, and the
// original paramTypep / typedefp from the template module.
// add() / addParamType() / addClass() record template entries.
// Template entries store the REFDTYPE and its cellPath.
// Template entries have cloneCellPath = "".
//
// 2. CLONE REGISTRATION (V3Param, deepCloneModule):
@@ -30,8 +29,8 @@
// At this point the cloned module's cells still reference template
// interface modules (cell->modp() is stale). Any attempt to walk
// cellPath here finds the wrong module. Clone entries store the
// cloned REFDTYPE and cloneCellPath but clear paramTypep/typedefp
// so that stale template pointers are never carried forward.
// cloned REFDTYPE and cloneCellPath, and drop the template's extra
// REFDTYPEs so that stale template pointers are never carried forward.
//
// 3. TARGET RESOLUTION (finalizeIfaceCapture, after V3Param):
// Runs after all cloning is complete and cell pointers are wired
@@ -371,7 +370,7 @@ void V3LinkDotIfaceCapture::nullStaleLedgerRefs(const std::unordered_set<const A
void V3LinkDotIfaceCapture::purgeStaleRefs() {
if (!s_enabled || s_map.empty() || !v3Global.rootp()) return;
// Collect every live AstNode* in the AST so we can detect stale pointers
// in the ledger (refp, ownerModp, typedefp, paramTypep, etc.).
// in the ledger (refp, ownerModp, extraRefps).
const LiveNodes liveNodes = collectLiveNodes();
nullStaleLedgerRefs(liveNodes);
}
@@ -412,34 +411,20 @@ void V3LinkDotIfaceCapture::dumpEntries(const string& label) {
const CaptureKey& key = pair.first;
const CapturedEntry& entry = pair.second;
const char* captType = (entry.captureType == CaptureType::IFACE) ? "IFACE" : "CLASS";
UINFO(9,
" [" << idx << "] " << captType << " key={" << key.ownerModName << ","
<< key.refName << "," << key.cellPath << "," << key.cloneCellPath << "}"
<< " ref=" << (entry.refp ? entry.refp->name() : "<null>")
<< " refp=" << cvtToHex(entry.refp) << " cellPath='" << entry.cellPath << "'"
<< " ownerMod=" << (entry.ownerModp ? entry.ownerModp->name() : "<null>")
<< " typedefp=" << (entry.typedefp ? entry.typedefp->name() : "<null>")
<< " typedefOwnerModName='" << entry.typedefOwnerModName << "'"
<< " paramTypep=" << (entry.paramTypep ? entry.paramTypep->name() : "<null>")
<< " ifacePortVarp="
<< (entry.ifacePortVarp ? entry.ifacePortVarp->name() : "<null>"));
UINFO(9, " [" << idx << "] " << captType << " key={" << key.ownerModName << ","
<< key.refName << "," << key.cellPath << "," << key.cloneCellPath << "}"
<< " ref=" << (entry.refp ? entry.refp->name() : "<null>") << " refp="
<< cvtToHex(entry.refp) << " cellPath='" << entry.cellPath << "'"
<< " ownerMod=" << (entry.ownerModp ? entry.ownerModp->name() : "<null>")
<< " typedefOwnerModName='" << entry.typedefOwnerModName << "'");
++idx;
}
UINFO(9, "========== end iface capture dumpEntries ==========");
}
string V3LinkDotIfaceCapture::extractIfacePortName(const string& dotText) {
string name = dotText;
const size_t dotPos = name.find('.');
if (dotPos != string::npos) name = name.substr(0, dotPos);
const size_t braPos = name.find("__BRA__");
if (braPos != string::npos) name = name.substr(0, braPos);
return name;
}
void V3LinkDotIfaceCapture::add(AstRefDType* refp, const string& cellPath,
AstNodeModule* ownerModp, AstTypedef* typedefp,
const string& typedefOwnerModName, AstVar* ifacePortVarp) {
const string& typedefOwnerModName) {
UASSERT(refp, "add() called with null refp");
UASSERT(ownerModp, "add() called with null ownerModp for refp=" << refp->prettyNameQ());
if (!typedefp) typedefp = refp->typedefp();
@@ -454,18 +439,9 @@ void V3LinkDotIfaceCapture::add(AstRefDType* refp, const string& cellPath,
<< refp->name() << " cellPath='" << cellPath << "'" << " ownerMod="
<< ownerModName << " extraRefps.size=" << it->second.extraRefps.size());
} else {
s_map[key] = CapturedEntry{CaptureType::IFACE,
TargetKind::TYPEDEF,
refp,
cellPath,
/*cloneCellPath=*/"",
/*origClassp=*/nullptr,
ownerModp,
typedefp,
nullptr,
tdOwnerName,
ifacePortVarp,
{}};
s_map[key]
= CapturedEntry{CaptureType::IFACE, TargetKind::TYPEDEF, refp, cellPath,
/*cloneCellPath=*/"", ownerModp, tdOwnerName, {}};
UINFO(9, "iface capture add: refp=" << refp->name() << " cellPath='" << cellPath << "'"
<< " ownerMod=" << ownerModName << " typedefp="
<< (typedefp ? typedefp->name() : "<null>")
@@ -486,40 +462,13 @@ void V3LinkDotIfaceCapture::addClass(AstRefDType* refp, AstClass* origClassp,
UASSERT_OBJ(!cellPath.empty(), origClassp, "addClass() produced empty cellPath");
const string ownerModName = ownerModp->name();
const CaptureKey key{ownerModName, refp->name(), cellPath, ""};
s_map[key] = CapturedEntry{CaptureType::CLASS,
TargetKind::TYPEDEF,
refp,
cellPath,
/*cloneCellPath=*/"",
origClassp,
ownerModp,
typedefp,
nullptr,
tdOwnerName,
nullptr,
{}};
s_map[key] = CapturedEntry{CaptureType::CLASS, TargetKind::TYPEDEF, refp, cellPath,
/*cloneCellPath=*/"", ownerModp, tdOwnerName, {}};
UINFO(9, "iface capture addClass: refp=" << refp->name() << " cellPath='" << cellPath << "'"
<< " ownerMod="
<< (ownerModp ? ownerModp->name() : "<null>"));
}
// Not called in production - retained as a diagnostic/debug entry point
// for inspecting the capture ledger by key (e.g. from GDB or future code).
const V3LinkDotIfaceCapture::CapturedEntry* // LCOV_EXCL_START
V3LinkDotIfaceCapture::find(const CaptureKey& key) {
const auto it = s_map.find(key);
if (VL_UNLIKELY(it == s_map.end())) return nullptr;
return &it->second;
} // LCOV_EXCL_STOP
const V3LinkDotIfaceCapture::CapturedEntry* V3LinkDotIfaceCapture::find(const AstRefDType* refp) {
if (!refp) return nullptr;
for (const auto& kv : s_map) {
if (kv.second.refp == refp) return &kv.second;
}
return nullptr;
}
// Walk a dot-separated cell path through the cell / IFACEREFDTYPE hierarchy
// starting from startModp. Returns the module at the end of the path, or
// nullptr if any component cannot be resolved.
@@ -666,10 +615,8 @@ void V3LinkDotIfaceCapture::forEachOwned(const AstNodeModule* ownerModp,
// replaces the lambda used in V3LinkDot.cpp for iface capture
void V3LinkDotIfaceCapture::captureTypedefContext(AstRefDType* refp, const char* stageLabel,
int dotPos, bool /*dotIsFinal*/,
const std::string& dotText, VSymEnt* dotSymp,
VSymEnt* curSymp, AstNodeModule* modp,
AstNode* /*nodep*/,
int dotPos, const std::string& dotText,
VSymEnt* dotSymp, AstNodeModule* modp,
const std::function<std::string()>& indentFn) {
if (!enabled() || !refp) return;
@@ -700,21 +647,11 @@ void V3LinkDotIfaceCapture::captureTypedefContext(AstRefDType* refp, const char*
UASSERT_OBJ(!dotText.empty(), refp, "captureTypedefContext: dotText empty");
const string cellPath = dotText;
AstVar* ifacePortVarp = nullptr;
if (curSymp) {
const std::string portName = extractIfacePortName(dotText);
if (VSymEnt* const portSymp = curSymp->findIdFallback(portName)) {
ifacePortVarp = VN_CAST(portSymp->nodep(), Var);
UINFO(9, indentFn() << "iface capture found port var '" << portName << "' -> "
<< ifacePortVarp);
}
}
// Check if refDTypep is a ParamTypeDType - if so, use addParamType instead of add
if (AstParamTypeDType* const paramTypep = VN_CAST(refp->refDTypep(), ParamTypeDType)) {
V3LinkDotIfaceCapture::addParamType(refp, cellPath, modp, paramTypep, "", ifacePortVarp);
V3LinkDotIfaceCapture::addParamType(refp, cellPath, modp, paramTypep, "");
} else {
V3LinkDotIfaceCapture::add(refp, cellPath, modp, refp->typedefp(), "", ifacePortVarp);
V3LinkDotIfaceCapture::add(refp, cellPath, modp, refp->typedefp());
}
UINFO(9, indentFn() << "iface capture capture success typedef=" << refp
@@ -772,12 +709,8 @@ void V3LinkDotIfaceCapture::captureInnerParamTypeRefs(AstParamTypeDType* paramTy
innerRefp,
nestedCellName.empty() ? cellPath : nestedCellName,
/*cloneCellPath=*/"",
/*origClassp=*/nullptr,
ptOwnerModp,
innerRefp->typedefp(),
nullptr,
refOwnerModp->name(),
nullptr,
{}};
}
}
@@ -786,8 +719,7 @@ void V3LinkDotIfaceCapture::captureInnerParamTypeRefs(AstParamTypeDType* paramTy
void V3LinkDotIfaceCapture::addParamType(AstRefDType* refp, const string& cellPath,
AstNodeModule* ownerModp, AstParamTypeDType* paramTypep,
const string& paramTypeOwnerModName,
AstVar* ifacePortVarp) {
const string& paramTypeOwnerModName) {
UASSERT(refp, "addParamType() called with null refp");
UASSERT(ownerModp,
"addParamType() called with null ownerModp for refp='" << refp->prettyNameQ() << "'");
@@ -824,12 +756,8 @@ void V3LinkDotIfaceCapture::addParamType(AstRefDType* refp, const string& cellPa
refp,
cellPath,
/*cloneCellPath=*/"",
/*origClassp=*/nullptr,
ownerModp,
nullptr,
paramTypep,
ptOwnerName,
ifacePortVarp,
{}};
}
+7 -31
View File
@@ -1,9 +1,8 @@
// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Interface typedef capture helper.
// Stores (refp, typedefp, cellp, owners, pendingClone) so LinkDot can
// rebind refs when symbol lookup fails, and V3Param clones can retarget
// typedefs without legacy paths.
// Records RefDTypes that reach an interface typedef through a cell path, so
// V3Param clones can retarget them to the correct interface specialization.
//
// Code available from: https://verilator.org
//
@@ -78,17 +77,10 @@ public:
string cellPath; // Template path (e.g. "cca_io.tlb_io") - immutable key component
string cloneCellPath; // Instance-specific path (e.g. "cca_io1.tlb_io") - set by
// propagateClone when V3Param clones; empty for original entries
AstClass* origClassp = nullptr; // For CLASS captures
// Module where the RefDType lives
AstNodeModule* ownerModp = nullptr;
// Typedef definition being referenced
AstTypedef* typedefp = nullptr;
// For PARAMTYPEDTYPE
AstParamTypeDType* paramTypep = nullptr;
// Name of the module/interface that owns the typedef (stable string)
string typedefOwnerModName;
// Interface port variable for matching during cloning
AstVar* ifacePortVarp = nullptr;
// Additional REFDTYPEs sharing the same key (e.g. from macro expansions
// that produce multiple $bits() references to the same interface typedef).
// The primary refp is stored above; extras are appended here so that
@@ -96,11 +88,7 @@ public:
std::vector<AstRefDType*> extraRefps;
// Clear template-specific targets that are stale in a clone context.
// Called by propagateClone before inserting a clone entry.
void clearStaleRefs() {
paramTypep = nullptr;
typedefp = nullptr;
extraRefps.clear();
}
void clearStaleRefs() { extraRefps.clear(); }
// Visit every AstNode* pointer field (analogous to AstNode::foreachLink).
// The callback receives an AstNode* by reference; if it nulls the
// pointer the typed member is nulled accordingly.
@@ -113,10 +101,6 @@ public:
};
callOnNode(refp);
callOnNode(ownerModp);
callOnNode(typedefp);
callOnNode(paramTypep);
callOnNode(ifacePortVarp);
callOnNode(origClassp);
for (auto& xrefp : extraRefps) callOnNode(xrefp);
}
};
@@ -134,7 +118,6 @@ private:
static void reset();
static void clearModuleCache();
static AstIfaceRefDType* ifaceRefFromVarDType(AstNodeDType* dtypep);
static string extractIfacePortName(const string& dotText);
// True if this module is a copy of that one.
static bool isCloneOfModule(const AstNodeModule* modp, const AstNodeModule* templateModp);
// Point a reference at a typedef and fix its other links.
@@ -170,17 +153,11 @@ public:
// Retarget every live RefDType in an entry using only stable capture metadata.
static bool retargetRefToModule(const CapturedEntry& entry, AstNodeModule* targetModp);
static void add(AstRefDType* refp, const string& cellPath, AstNodeModule* ownerModp,
AstTypedef* typedefp = nullptr, const string& typedefOwnerModName = "",
AstVar* ifacePortVarp = nullptr);
AstTypedef* typedefp = nullptr, const string& typedefOwnerModName = "");
static void addClass(AstRefDType* refp, AstClass* origClassp, AstNodeModule* ownerModp,
AstTypedef* typedefp = nullptr, const string& typedefOwnerModName = "");
static void addParamType(AstRefDType* refp, const string& cellPath, AstNodeModule* ownerModp,
AstParamTypeDType* paramTypep, const string& paramTypeOwnerModName,
AstVar* ifacePortVarp);
// Exact lookup by full key
static const CapturedEntry* find(const CaptureKey& key);
// Pointer-based lookup: linear scan with early exit (no std::function overhead)
static const CapturedEntry* find(const AstRefDType* refp);
AstParamTypeDType* paramTypep, const string& paramTypeOwnerModName);
static void forEach(const std::function<void(const CapturedEntry&)>& fn);
static void forEachOwned(const AstNodeModule* ownerModp,
const std::function<void(const CapturedEntry&)>& fn);
@@ -198,9 +175,8 @@ public:
AstNodeModule* newOwnerModp, const string& cloneCellPath);
static void captureTypedefContext(AstRefDType* refp, const char* stageLabel, int dotPos,
bool dotIsFinal, const std::string& dotText,
VSymEnt* dotSymp, VSymEnt* curSymp, AstNodeModule* modp,
AstNode* nodep,
const std::string& dotText, VSymEnt* dotSymp,
AstNodeModule* modp,
const std::function<std::string()>& indentFn);
// Null out ledger entries that point to freed nodes (not in the live AST).
+3 -51
View File
@@ -872,9 +872,7 @@ class ParamProcessor final {
// Fix cross-module REFDTYPE pointers in newModp after cloneTree.
// Phase A: path-based fixup using ledger entries with cellPath.
// Phase B: reachable-set fallback for remaining REFDTYPEs.
void fixupCrossModuleRefDTypes(AstNodeModule* newModp, AstNodeModule* srcModp,
AstNode* /*ifErrorp*/, const IfaceRefRefs& ifaceRefRefs) {
void fixupCrossModuleRefDTypes(AstNodeModule* newModp, AstNodeModule* srcModp) {
if (!V3LinkDotIfaceCapture::enabled()) return;
// Phase A: path-based fixup using ledger entries
std::set<AstRefDType*> ledgerFixed;
@@ -923,51 +921,6 @@ class ParamProcessor final {
});
V3Stats::addStatSum("IfaceCapture, Ledger fixups in V3Param", ledgerFixed.size());
}
// Phase B: reachable-set fallback for REFDTYPEs not handled by ledger
std::set<AstNodeModule*> reachable;
reachable.insert(newModp);
std::function<void(AstNodeModule*)> collectReachable;
collectReachable = [&](AstNodeModule* modp) {
for (AstNode* sp = modp->stmtsp(); sp; sp = sp->nextp()) {
if (AstCell* const cellp = VN_CAST(sp, Cell)) {
AstNodeModule* const cellModp = cellp->modp();
if (cellModp && reachable.insert(cellModp).second) {
collectReachable(cellModp);
}
}
}
};
for (const auto& pair : ifaceRefRefs) {
AstIface* const pinIfacep = pair.second->ifaceViaCellp();
if (pinIfacep && reachable.insert(pinIfacep).second) { collectReachable(pinIfacep); }
}
collectReachable(newModp);
// Phase B (reachable-set fallback): Phase A (path-based ledger fixup)
// always resolves all statement-level REFDTYPEs for current tests and
// Aerial. Assert if any REFDTYPE slips through so we can investigate.
// The loop body is assert-only (no mutations); LCOV_EXCL because
// Phase A always resolves everything and ledgerFixed catches all refs.
for (AstNode* stmtp = newModp->stmtsp(); stmtp; stmtp = stmtp->nextp()) {
AstRefDType* const refp = VN_CAST(stmtp, RefDType);
if (!refp) continue;
if (ledgerFixed.count(refp)) continue; // LCOV_EXCL_LINE
// LCOV_EXCL_START
// Check if typedefp or refDTypep points outside the reachable set
auto checkNotStale = [&](const char* label, AstNode* targetp) {
AstNodeModule* const ownerp = V3LinkDotIfaceCapture::findOwnerModule(targetp);
if (!ownerp || ownerp == newModp || VN_IS(ownerp, Package)
|| reachable.count(ownerp))
return; // OK: owner is reachable or self
v3fatalSrc("Phase B reachable-set fallback triggered for "
<< refp->prettyNameQ() << " " << label << " owner="
<< ownerp->prettyNameQ() << " in " << newModp->prettyNameQ());
};
if (refp->typedefp()) checkNotStale("typedefp", refp->typedefp());
if (refp->refDTypep()) checkNotStale("refDTypep", refp->refDTypep());
// LCOV_EXCL_STOP
}
}
// Return true on success, false on error
@@ -1155,10 +1108,9 @@ class ParamProcessor final {
// to find the correct interface for each VarXRef.
if (!ifaceRefRefs.empty()) { VarXRefRelinkVisitor{newModp}; }
// Fix cross-module REFDTYPE pointers in newModp (Phase A path-based
// + Phase B reachable-set fallback).
// Fix cross-module REFDTYPE pointers in newModp.
UASSERT_OBJ(newModp, srcModp, "newModp null before hierarchy fixup");
fixupCrossModuleRefDTypes(newModp, srcModp, ifErrorp, ifaceRefRefs);
fixupCrossModuleRefDTypes(newModp, srcModp);
// Assign parameters to the constants specified
// DOES clone() so must be finished with module clonep() before here
+21
View File
@@ -0,0 +1,21 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of either the GNU Lesser General Public License Version 3
# or the Perl Artistic License Version 2.0.
# SPDX-FileCopyrightText: 2026 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('vlt')
test.top_filename = "t/t_iface_typedef_bits_uaf.v"
test.lint(
# Check we can dump the interface capture ledger
v_flags=["--debug --debugi 0 --debugi-V3LinkDotIfaceCapture 9"])
test.file_grep(test.compile_log_filename, r'iface capture dumpEntries: after finalizeIfaceCapture')
test.passes()