Skip to content

VNX-1045: Parent Class with Virtual Destructor and Child Without Virtual Destructor

Reports a C++ child class whose own destructor is declared without virtual, override or final although a parent in its base list declares a virtual destructor.

Updated 02 Nov 2026Checked against cli@de9b52a
Field Value
Rule ID VNX-1045
Severity Medium
SARIF level warning
Kind sast
Languages C++
CWE CWE-1045
CAPEC CAPEC-123
CWSS vector CWSS:1.0/TI:M/AP:RU/AL:S/IC:N/FC:LT/RP:RU/RL:S/AV:L/AS:N/IN:A/SC:A/BI:M/DI:H/EX:M/EC:N/P:C
Tags cpp virtual-destructor inheritance override cwe-1045
Source internal/sast/rules/vnx-cwe1045-auto.rego

Overview

VNX-1045 reports CWE-1045 in C++. The case is a class that derives from a parent whose destructor is declared virtual, and that declares its own destructor without virtual, override or final.

The finding is on the child’s in-class destructor declaration: ~Child();, ~Child() = default;, ~Child() noexcept; or an inline body. The parent can be named with any access specifier, with or without a namespace or template arguments, in a base list on the class line or wrapped onto the following lines (class Child\n : public Base {), and can be declared in any C++ file of the scanned tree with a destructor marked virtual (including on the line above), override or final, so a grandchild of a virtual base is judged too.

Not reported:

  • A child destructor marked virtual, override or final.
  • A child with no user-declared destructor. The implicit destructor is virtual and needs no annotation.
  • A parent whose destructor is not virtual. That is a different and more dangerous weakness (CWE-1079 and CWE-1087), judged by VNX-1079 and VNX-1087.
  • A parent defined outside the scanned tree.
  • Out-of-class definitions (Child::~Child() {}), test code and comments.

Why This Matters

C++ makes the child’s destructor virtual anyway, so today the code is correct. The problem is that the declaration reads as non-virtual. A reviewer cannot tell from the child alone whether delete base_ptr runs it. If the parent’s virtual is ever removed, for example in a refactor that turns the parent into a plain value type, every child silently loses its virtual destructor. Deleting through a parent pointer then skips the child’s cleanup, which leaks memory and handles or leaves half-destroyed state behind. override turns that silent change into a compile error.

What Gets Flagged

class Codec {
public:
    virtual ~Codec() = default;
};

class GzipCodec : public Codec {
public:
    ~GzipCodec();                  // FLAGGED
};

class Socket : private NonCopyable, public Stream<char> {
public:
    ~Socket() = default;           // FLAGGED: Stream<T> declares virtual ~Stream()
};

What Is Not Flagged

class GzipCodec : public Codec {
public:
    ~GzipCodec() override;
};

class LzCodec : public Codec {     // no user-declared destructor
    int encode() override;
};

Remediation

  1. Declare the child’s destructor override (~GzipCodec() override;), or virtual if your style guide prefers it.
  2. Turn on -Winconsistent-missing-destructor-override (Clang) or clang-tidy’s modernize-use-override to enforce it.
  3. If the child needs no cleanup, remove the user-declared destructor and rely on the implicit one.

References

Suppressing this rule

Put a nosec comment on the line, or on line 1 to cover the whole file. Use the comment marker of the file’s language (#, //, -- or ;):

# nosec vnx-1045
// nosec vnx-1045

Or record an ignore rule: vulnetix ignore add --rule vnx-1045 --reason "...". See Suppressing findings.

Edit this pageSources: cli/internal/sast/rules/vnx-cwe1045-auto.rego, cli/internal/sast/docs/vnx-cwe1045-auto.md
esc
↑ ↓ move↵ openSearches in your browser. A key like autonomy.max or a code like threshold_required jumps straight to it.