Fix circular class reference %p-printing causing infinite recursion (#7106).

Fixes #7106.
This commit is contained in:
Wilson Snyder 2026-02-19 20:15:37 -05:00
parent 4a4f8c6698
commit 0d2fcfd49d
10 changed files with 106 additions and 59 deletions

View File

@ -114,6 +114,7 @@ Verilator 5.045 devel
* Fix constant propagating DPI-written variables (#7074). [Geza Lore, Testorrent USA, Inc.]
* Fix conditional expressions in constraints (#7087). [Ryszard Rozak, Antmicro Ltd.]
* Fix time to not advance after `$finish` (#7095).
* Fix circular class reference %p-printing causing infinite recursion (#7106).
Verilator 5.044 2026-01-01

View File

@ -1884,6 +1884,9 @@ public:
VlClass() {}
VlClass(const VlClass& copied) {}
~VlClass() override = default;
// METHODS
virtual const char* typeName() const { return "VlClass"; }
virtual std::string to_string() const { return ""; }
};
//===================================================================
@ -2045,6 +2048,30 @@ static inline bool VL_CAST_DYNAMIC(VlNull in, VlClassRef<T_Lhs>& outr) {
return true;
}
// For printing class references under a container, several choices:
// 1. Dump recursively the pointed-to object. Can be huge. Might be circular.
// 2. Print object type and pointer as C pointer. Astable when rerun.
// 3. Print object type and pointer as an incrementing number. Needs num storage.
// 4. Print object type alone. Avoids above issues.
template <typename T_Lhs>
inline std::string VL_TO_STRING(const VlClassRef<T_Lhs>& obj) {
return obj ? obj->typeName() : "null";
}
// Entry point for string conversion (called from not under a container);
// dereference VlClassRef objects to print members
template <typename T_Lhs> // Default if no specialization
inline std::string VL_TO_STRING_DEREF(T_Lhs obj) {
return VL_TO_STRING(obj);
}
template <typename T_Lhs> // Specialization
inline std::string VL_TO_STRING_DEREF(const VlClassRef<T_Lhs>& obj) {
return obj ? obj->to_string() : "null";
}
template <typename T_Lhs> // Specialization
inline std::string VL_TO_STRING_DEREF(VlClassRef<T_Lhs>& obj) {
return obj ? obj->to_string() : "null";
}
//=============================================================================
// VlSampleQueue stores samples for input clockvars in clocking blocks. At a clocking event,
// samples from this queue should be written to the correct input clockvar.

View File

@ -5553,7 +5553,9 @@ public:
ASTGEN_MEMBERS_AstToStringN;
void numberOperate(V3Number& out, const V3Number& lhs) override { V3ERROR_NA; }
string emitVerilog() override { return "$sformatf(\"%p\", %l)"; }
string emitC() override { return isWide() ? "VL_TO_STRING_W(%nw, %li)" : "VL_TO_STRING(%li)"; }
string emitC() override {
return isWide() ? "VL_TO_STRING_W(%nw, %li)" : "VL_TO_STRING_DEREF(%li)";
}
bool cleanOut() const override { return true; }
bool cleanLhs() const override { return true; }
bool sizeMattersLhs() const override { return false; }

View File

@ -488,6 +488,7 @@ class AstCFunc final : public AstNode {
// an explicitly passed 'self' pointer as the first argument. This can
// be slightly faster due to __restrict, and we do not declare in header
// so adding/removing loose functions doesn't recompile everything.
bool m_isOverride : 1; // Override virtual function
bool m_isVirtual : 1; // Virtual function
bool m_entryPoint : 1; // User may call into this top level function
bool m_dpiPure : 1; // Pure DPI function
@ -519,6 +520,7 @@ public:
m_isDestructor = false;
m_isMethod = true;
m_isLoose = false;
m_isOverride = false;
m_isVirtual = false;
m_needProcess = false;
m_entryPoint = false;
@ -582,6 +584,11 @@ public:
bool isLoose() const { return m_isLoose; }
void isLoose(bool flag) { m_isLoose = flag; }
bool isProperMethod() const { return isMethod() && !isLoose(); }
bool isOverride() const { return m_isOverride; }
void isOverride(bool flag) {
m_isOverride = flag;
if (flag) isVirtual(true);
}
bool isVirtual() const { return m_isVirtual; }
void isVirtual(bool flag) { m_isVirtual = flag; }
bool needProcess() const { return m_needProcess; }

View File

@ -3233,6 +3233,7 @@ void AstCFunc::dump(std::ostream& str) const {
if (isDestructor()) str << " [DTOR]";
if (isMethod()) str << " [METHOD]";
if (isLoose()) str << " [LOOSE]";
if (isOverride()) str << " [OVERRIDE]";
if (isVirtual()) str << " [VIRT]";
if (isCoroutine()) str << " [CORO]";
if (needProcess()) str << " [NPRC]";
@ -3249,6 +3250,7 @@ void AstCFunc::dumpJson(std::ostream& str) const {
dumpJsonBoolFuncIf(str, dpiContext);
dumpJsonBoolFuncIf(str, isConstructor);
dumpJsonBoolFuncIf(str, isDestructor);
dumpJsonBoolFuncIf(str, isOverride);
dumpJsonBoolFuncIf(str, isVirtual);
dumpJsonBoolFuncIf(str, isCoroutine);
dumpJsonBoolFuncIf(str, needProcess);

View File

@ -37,10 +37,6 @@ string V3Common::makeToStringCall(AstNodeDType* nodep, const std::string& lhs) {
stmt += "VL_TO_STRING_W(";
stmt += cvtToStr(nodep->widthWords());
stmt += ", ";
} else if (VN_IS(nodep->skipRefp(), BasicDType) && nodep->isWide()) {
stmt += "VL_TO_STRING_W(";
stmt += cvtToStr(nodep->widthWords());
stmt += ", ";
} else {
stmt += "VL_TO_STRING(";
}
@ -49,20 +45,6 @@ string V3Common::makeToStringCall(AstNodeDType* nodep, const std::string& lhs) {
return stmt;
}
static void makeVlToString(AstClass* nodep) {
AstCFunc* const funcp
= new AstCFunc{nodep->fileline(), "VL_TO_STRING", nullptr, "std::string"};
funcp->argTypes("const VlClassRef<" + EmitCUtil::prefixNameProtect(nodep) + ">& obj");
funcp->isMethod(false);
funcp->isConst(false);
funcp->isStatic(false);
funcp->protect(false);
AstNodeExpr* const exprp
= new AstCExpr{nodep->fileline(), "obj ? obj->to_string() : \"null\""};
exprp->dtypeSetString();
funcp->addStmtsp(new AstCReturn{nodep->fileline(), exprp});
nodep->addStmtsp(funcp);
}
static void makeVlToString(AstIface* nodep) {
AstCFunc* const funcp
= new AstCFunc{nodep->fileline(), "VL_TO_STRING", nullptr, "std::string"};
@ -112,6 +94,7 @@ static void makeToString(AstClass* nodep) {
AstCFunc* const funcp = new AstCFunc{nodep->fileline(), "to_string", nullptr, "std::string"};
funcp->isConst(true);
funcp->isStatic(false);
funcp->isOverride(true);
funcp->protect(false);
AstCExpr* const exprp = new AstCExpr{nodep->fileline(), R"("'{"s + to_string_middle() + "}")"};
exprp->dtypeSetString();
@ -128,10 +111,9 @@ static void makeToStringMiddle(AstClass* nodep) {
std::string comma;
for (AstNode* itemp = nodep->membersp(); itemp; itemp = itemp->nextp()) {
if (const auto* const varp = VN_CAST(itemp, Var)) {
const AstBasicDType* const basicp = varp->dtypeSkipRefp()->basicp();
if (!varp->isParam() && !varp->isInternal()
&& !(varp->dtypeSkipRefp()->basicp()
&& (varp->dtypeSkipRefp()->basicp()->isRandomGenerator()
|| varp->dtypeSkipRefp()->basicp()->isStdRandomGenerator()))) {
&& !(basicp && (basicp->isRandomGenerator() || basicp->isStdRandomGenerator()))) {
string stmt = "out += \"";
stmt += comma;
comma = ", ";
@ -146,7 +128,7 @@ static void makeToStringMiddle(AstClass* nodep) {
}
if (nodep->extendsp()) {
string stmt = "out += ";
if (!comma.empty()) stmt += "\", \"+ ";
if (!comma.empty()) stmt += "\", \" + ";
// comma = ", "; // Nothing further so not needed
stmt += EmitCUtil::prefixNameProtect(nodep->extendsp()->dtypep());
stmt += "::to_string_middle();";
@ -174,7 +156,6 @@ void V3Common::commonAll() {
for (AstNode* nodep = v3Global.rootp()->modulesp(); nodep; nodep = nodep->nextp()) {
if (AstClass* const classp = VN_CAST(nodep, Class)) {
// Create ToString methods
makeVlToString(classp);
makeToString(classp);
makeToStringMiddle(classp);
} else if (AstIface* const ifacep = VN_CAST(nodep, Iface)) {

View File

@ -153,9 +153,10 @@ void EmitCBaseVisitorConst::emitCFuncDecl(const AstCFunc* funcp, const AstNodeMo
if (funcp->isStatic() && funcp->isProperMethod()) putns(funcp, "static ");
if (funcp->isVirtual()) {
UASSERT_OBJ(funcp->isProperMethod(), funcp, "Virtual function is not a proper method");
putns(funcp, "virtual ");
if (!funcp->isOverride()) putns(funcp, "virtual ");
}
emitCFuncHeader(funcp, modp, /* withScope: */ false);
if (funcp->isOverride()) putns(funcp, " override");
if (funcp->emptyBody() && !funcp->isLoose() && !cLinkage) {
putns(funcp, " {}\n");
} else {

View File

@ -183,6 +183,11 @@ class EmitCHeader final : public EmitCConstInit {
if (!VN_IS(modp, Class)) {
decorateFirst(first, section);
puts("void " + protect("__Vconfigure") + "(bool first);\n");
} else {
decorateFirst(first, section);
const std::string name = V3OutFormatter::quoteNameControls(
VIdProtect::protectWordsIf(modp->prettyName(), v3Global.opt.protectIds()));
puts("const char* typeName() const override { return \""s + name + "\"; }\n");
}
if (v3Global.opt.coverage() && !VN_IS(modp, Class)) {

View File

@ -1,4 +1,7 @@
''{b:'h1, i:'h2a, carray4:'{'h11, 'h22, 'h33, 'h44}, cwide:'{'h0, 'h0}, name:"object_name", r:2.2}'
''{b:'h1, i:'h2a, carray4:'{'h911, 'h922, 'h933, 'h944}, cwide:'{'h0, 'h0}, name:"object_name", r:2.2}'
DEBUG: object_name (@0) message
''{m_in_a:'h0, m_b:null, m_in_base:'h0}'
''{m_in_a:'h0, m_b:$unit::ClsB, m_in_base:'h0}'
''{m_in_a:'h0, m_b:$unit::ClsB, m_in_base:'h0}'
*-* All Finished *-*

View File

@ -4,46 +4,64 @@
// SPDX-FileCopyrightText: 2021 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
`ifdef verilator
`define stop $stop
`else
`define stop
`endif
`define checks(gotv,expv) do if ((gotv) != (expv)) begin $write("%%Error: %s:%0d: got='%s' exp='%s'\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0);
class Cls;
bit b;
int i;
bit [15:0] carray4 [4];
bit [64:0] cwide[2];
string name;
real r;
task debug();
$display("DEBUG: %s (@%0t) %s", this.name, $realtime, "message");
endtask
bit b;
int i;
bit [15:0] carray4[4];
bit [64:0] cwide[2];
string name;
real r;
task debug();
$display("DEBUG: %s (@%0t) %s", this.name, $realtime, "message");
endtask
endclass
class Base;
int m_in_base;
endclass
class ClsA extends Base;
int m_in_a;
Base m_b;
endclass
class ClsB extends Base;
int m_in_b;
Base m_a;
endclass
module t;
initial begin
Cls c;
c = new;
c.b = '1;
c.i = 42;
c.r = 2.2;
c.name = "object_name";
initial begin
Cls c;
ClsA ca;
ClsB cb;
c.carray4[0] = 16'h11;
c.carray4[1] = 16'h22;
c.carray4[2] = 16'h33;
c.carray4[3] = 16'h44;
$display("'%p'", c);
c = new;
c.b = '1;
c.i = 42;
c.r = 2.2;
c.name = "object_name";
c.carray4 = '{16'h911, 16'h922, 16'h933, 16'h944};
$display("'%p'", c);
c.carray4[0] = 16'h11;
c.carray4[1] = 16'h22;
c.carray4[2] = 16'h33;
c.carray4[3] = 16'h44;
$display("'%p'", c);
c.debug();
c.carray4 = '{16'h911, 16'h922, 16'h933, 16'h944};
$display("'%p'", c);
$write("*-* All Finished *-*\n");
$finish;
end
c.debug();
ca = new;
$display("'%p'", ca);
cb = new;
ca.m_b = cb;
$display("'%p'", ca);
cb.m_a = ca; // Circular
$display("'%p'", ca);
$write("*-* All Finished *-*\n");
$finish;
end
endmodule