From 79e1f3317321320921458be7ec4c55fb54177012 Mon Sep 17 00:00:00 2001 From: Yilou Wang Date: Sun, 22 Feb 2026 15:22:37 +0100 Subject: [PATCH] Fix new shallow copy not preserving polymorphic runtime type (#7105) (#7109) --- include/verilated_types.h | 11 ++ src/V3EmitCFunc.h | 20 +++- src/V3EmitCHeaders.cpp | 9 ++ test_regress/t/t_class_new_copy_null_bad.out | 2 + test_regress/t/t_class_new_copy_null_bad.py | 18 ++++ test_regress/t/t_class_new_copy_null_bad.v | 21 ++++ .../t/t_class_new_copy_polymorphism.py | 18 ++++ .../t/t_class_new_copy_polymorphism.v | 101 ++++++++++++++++++ 8 files changed, 196 insertions(+), 4 deletions(-) create mode 100644 test_regress/t/t_class_new_copy_null_bad.out create mode 100755 test_regress/t/t_class_new_copy_null_bad.py create mode 100644 test_regress/t/t_class_new_copy_null_bad.v create mode 100755 test_regress/t/t_class_new_copy_polymorphism.py create mode 100644 test_regress/t/t_class_new_copy_polymorphism.v diff --git a/include/verilated_types.h b/include/verilated_types.h index 6554d67c9..da8c94977 100644 --- a/include/verilated_types.h +++ b/include/verilated_types.h @@ -1885,6 +1885,8 @@ public: VlClass() {} VlClass(const VlClass& copied) {} ~VlClass() override = default; + // Polymorphic shallow clone. Overridden in each generated concrete class. + virtual VlClass* clone() const { return nullptr; } // METHODS virtual const char* typeName() const { return "VlClass"; } virtual std::string to_string() const { return ""; } @@ -2008,6 +2010,15 @@ public: VlClassRef dynamicCast() const { return VlClassRef{dynamic_cast(m_objp)}; } + // Polymorphic shallow clone (IEEE 1800-2017 8.7: new preserves runtime type) + VlClassRef clone(VlDeleter& deleter) const { + VlClass* clonedp = m_objp->clone(); + if (VL_UNLIKELY(!clonedp)) return {}; + clonedp->m_deleterp = &deleter; + VlClassRef result; + result.m_objp = dynamic_cast(clonedp); + return result; + } // Dereference operators T_Class& operator*() const { return *m_objp; } T_Class* operator->() const { return m_objp; } diff --git a/src/V3EmitCFunc.h b/src/V3EmitCFunc.h index 6566b715a..269ba2459 100644 --- a/src/V3EmitCFunc.h +++ b/src/V3EmitCFunc.h @@ -1536,10 +1536,22 @@ public: puts(")"); } void visit(AstNewCopy* nodep) override { - putns(nodep, "VL_NEW(" + EmitCUtil::prefixNameProtect(nodep->dtypep())); - puts(", *"); // i.e. make into a reference - iterateAndNextConstNull(nodep->rhsp()); - puts(")"); + // Polymorphic shallow clone: preserves runtime type via virtual clone() + // VL_NULL_CHECK enforces null check per IEEE 1800-2017 8.7 + putns(nodep, "VL_NULL_CHECK("); + if (VN_IS(nodep->rhsp(), Const) && VN_AS(nodep->rhsp(), Const)->isNull()) { + // V3Const folded rhs to null: emit a typed empty ref so VL_NULL_CHECK fires + const AstClassRefDType* const refDTypep + = VN_CAST(nodep->dtypep()->skipRefp(), ClassRefDType); + puts(refDTypep->cType("", false, false) + "{}"); + } else { + iterateAndNextConstNull(nodep->rhsp()); + } + puts(", "); + putsQuoted(protect(nodep->fileline()->filename())); + puts(", "); + puts(cvtToStr(nodep->fileline()->lineno())); + puts(").clone(vlSymsp->__Vm_deleter)"); } void visit(AstSel* nodep) override { // Note ASSIGN checks for this on a LHS diff --git a/src/V3EmitCHeaders.cpp b/src/V3EmitCHeaders.cpp index 869e9e636..77124ad91 100644 --- a/src/V3EmitCHeaders.cpp +++ b/src/V3EmitCHeaders.cpp @@ -213,6 +213,15 @@ class EmitCHeader final : public EmitCConstInit { puts("void " + protect("__Vserialize") + "(VerilatedSerialize& os);\n"); puts("void " + protect("__Vdeserialize") + "(VerilatedDeserialize& os);\n"); } + + // Polymorphic clone for concrete (non-abstract, non-interface) classes + if (const AstClass* const classp = VN_CAST(modp, Class)) { + if (!classp->isInterfaceClass() && !classp->isVirtual()) { + decorateFirst(first, section); + putns(classp, "VlClass* clone() const { return new " + + EmitCUtil::prefixNameProtect(classp) + "(*this); }\n"); + } + } } void emitEnums(const AstNodeModule* modp) { bool first = true; diff --git a/test_regress/t/t_class_new_copy_null_bad.out b/test_regress/t/t_class_new_copy_null_bad.out new file mode 100644 index 000000000..72d6f9298 --- /dev/null +++ b/test_regress/t/t_class_new_copy_null_bad.out @@ -0,0 +1,2 @@ +%Error: t/t_class_new_copy_null_bad.v:16: Null pointer dereferenced +Aborting... diff --git a/test_regress/t/t_class_new_copy_null_bad.py b/test_regress/t/t_class_new_copy_null_bad.py new file mode 100755 index 000000000..346dfe57a --- /dev/null +++ b/test_regress/t/t_class_new_copy_null_bad.py @@ -0,0 +1,18 @@ +#!/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('simulator') + +test.compile() + +test.execute(fails=True, expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_class_new_copy_null_bad.v b/test_regress/t/t_class_new_copy_null_bad.v new file mode 100644 index 000000000..19ea4a395 --- /dev/null +++ b/test_regress/t/t_class_new_copy_null_bad.v @@ -0,0 +1,21 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2025 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 + +class Base; + int value; +endclass + +module t; + Base b; + Base a; + initial begin + b = null; + a = new b; // BAD: null handle dereference (IEEE 8.7) + if (a != null) $write("unexpected clone\n"); + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_class_new_copy_polymorphism.py b/test_regress/t/t_class_new_copy_polymorphism.py new file mode 100755 index 000000000..8a938befd --- /dev/null +++ b/test_regress/t/t_class_new_copy_polymorphism.py @@ -0,0 +1,18 @@ +#!/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('simulator') + +test.compile() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_class_new_copy_polymorphism.v b/test_regress/t/t_class_new_copy_polymorphism.v new file mode 100644 index 000000000..ba0a530e1 --- /dev/null +++ b/test_regress/t/t_class_new_copy_polymorphism.v @@ -0,0 +1,101 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 + +// Test that `new ` (shallow copy) preserves the runtime type +// of the source object, per IEEE 1800-2017 8.7. + +// verilog_format: off +`define stop $stop +`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); +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +class Base; + int value; + function new(); + value = 10; + endfunction + virtual function string get_type(); + return "BASE"; + endfunction + virtual function int get_id(); + return 1; + endfunction +endclass + +class Derived extends Base; + int extra; + function new(); + super.new(); + value = 20; + extra = 99; + endfunction + virtual function string get_type(); + return "DERIVED"; + endfunction + virtual function int get_id(); + return 2; + endfunction +endclass + +class GrandChild extends Derived; + function new(); + super.new(); + value = 30; + extra = 88; + endfunction + virtual function string get_type(); + return "GRANDCHILD"; + endfunction + virtual function int get_id(); + return 3; + endfunction +endclass + +module t; + initial begin + Base b; + Derived d; + Base copy; + + // Test 1: Copy via base handle pointing to Derived + d = new(); + b = d; + copy = new b; + `checks(copy.get_type(), "DERIVED"); + `checkd(copy.get_id(), 2); + `checkd(copy.value, 20); + + // Test 2: Verify it's a true copy (not alias) + copy.value = 999; + `checkd(d.value, 20); + + // Test 3: Copy via base handle pointing to GrandChild + begin + GrandChild gc; + gc = new(); + b = gc; + copy = new b; + `checks(copy.get_type(), "GRANDCHILD"); + `checkd(copy.get_id(), 3); + `checkd(copy.value, 30); + end + + // Test 4: Copy of base-type object (no polymorphism, still works) + begin + Base b2; + Base copy2; + b2 = new(); + copy2 = new b2; + `checks(copy2.get_type(), "BASE"); + `checkd(copy2.get_id(), 1); + `checkd(copy2.value, 10); + end + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule