Fix new <obj> shallow copy not preserving polymorphic runtime type (#7105) (#7109)

This commit is contained in:
Yilou Wang 2026-02-22 15:22:37 +01:00 committed by GitHub
parent 78ee787bb1
commit 79e1f33173
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 196 additions and 4 deletions

View File

@ -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<T_OtherClass> dynamicCast() const {
return VlClassRef<T_OtherClass>{dynamic_cast<T_OtherClass*>(m_objp)};
}
// Polymorphic shallow clone (IEEE 1800-2017 8.7: new <handle> 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<T_Class*>(clonedp);
return result;
}
// Dereference operators
T_Class& operator*() const { return *m_objp; }
T_Class* operator->() const { return m_objp; }

View File

@ -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

View File

@ -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;

View File

@ -0,0 +1,2 @@
%Error: t/t_class_new_copy_null_bad.v:16: Null pointer dereferenced
Aborting...

View File

@ -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()

View File

@ -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

View File

@ -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()

View File

@ -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 <handle>` (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