[clang] [clang][analyzer] Don't warn about virtual calls in final class destructors (PR #178654)
via cfe-commits
cfe-commits at lists.llvm.org
Sat Jan 31 09:09:39 PST 2026
https://github.com/mugiwaraluffy56 updated https://github.com/llvm/llvm-project/pull/178654
>From bc8ac887ae99462c67341b06dbed98c4517b1384 Mon Sep 17 00:00:00 2001
From: mugiwaraluffy56 <myakampuneeth at gmail.com>
Date: Thu, 29 Jan 2026 19:09:20 +0530
Subject: [PATCH] [clang][analyzer] Don't warn about virtual calls in final
class destructors
When a virtual method is called during construction or destruction of an
object whose type is a final class, virtual dispatch is safe because there
can be no derived classes that could override the method.
This patch checks:
1. If the object's type is effectively final
2. If the called method's class is effectively final
Added comprehensive tests including nested/interprocedural call scenarios.
Fixes #178643.
Assisted-by: claude
---
.../Checkers/VirtualCallChecker.cpp | 13 ++++
clang/test/Analysis/virtualcall.cpp | 75 ++++++++++++++++++-
2 files changed, 87 insertions(+), 1 deletion(-)
diff --git a/clang/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp
index 6c27f58d308aa..2856fb928d77e 100644
--- a/clang/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp
@@ -123,6 +123,19 @@ void VirtualCallChecker::checkPreCall(const CallEvent &Call,
if (!ObState)
return;
+ // If the object's dynamic type is a final class, virtual dispatch is safe
+ // because there can be no derived classes. We check both:
+ // 1. The object's type (for calls on objects of final class type)
+ // 2. The called method's class (for methods declared in final classes)
+ if (const auto *TR = dyn_cast<TypedValueRegion>(Reg)) {
+ QualType ObjType = TR->getValueType();
+ if (const auto *RD = ObjType->getAsCXXRecordDecl())
+ if (RD->isEffectivelyFinal())
+ return;
+ }
+ if (MD->getParent()->isEffectivelyFinal())
+ return;
+
bool IsPure = MD->isPureVirtual();
// At this point we're sure that we're calling a virtual method
diff --git a/clang/test/Analysis/virtualcall.cpp b/clang/test/Analysis/virtualcall.cpp
index 82285b6d12844..0a7f9fd1df971 100644
--- a/clang/test/Analysis/virtualcall.cpp
+++ b/clang/test/Analysis/virtualcall.cpp
@@ -84,6 +84,75 @@ class E final : public B {
int foo() override;
};
+// GH#178643: Virtual calls in destructor of a final class should not warn.
+class GH178643Base {
+public:
+ virtual void virtualMethod() {}
+ virtual ~GH178643Base() {
+ // Base class destructor should still warn even when destructing a final
+ // derived class, because the vtable points to the base class at this point.
+ virtualMethod(); // impure-warning {{Call to virtual method 'GH178643Base::virtualMethod' during destruction bypasses virtual dispatch}}
+ }
+};
+
+class GH178643Derived final : public GH178643Base {
+public:
+ ~GH178643Derived() {
+ virtualMethod(); // no-warning: class is final, no derived classes exist
+ }
+};
+
+// Test constructor case for final class.
+class GH178643CtorBase {
+public:
+ virtual void virtualMethod() {}
+ GH178643CtorBase() {
+ virtualMethod(); // impure-warning {{Call to virtual method 'GH178643CtorBase::virtualMethod' during construction bypasses virtual dispatch}}
+ }
+};
+
+class GH178643CtorDerived final : public GH178643CtorBase {
+public:
+ GH178643CtorDerived() {
+ virtualMethod(); // no-warning: class is final
+ }
+};
+
+// Test nested calls from destructor - destructor calls helper which makes
+// virtual call. This tests interprocedural behavior.
+class GH178643NestedBase {
+public:
+ virtual void virtualMethod() {}
+ void helper() {
+ // Called from destructor via nested call - should still warn because
+ // this helper could be called from non-final derived classes too.
+ virtualMethod(); // impure-warning {{Call to virtual method 'GH178643NestedBase::virtualMethod' during destruction bypasses virtual dispatch}}
+ }
+ virtual ~GH178643NestedBase() = default;
+};
+
+class GH178643NestedDerived final : public GH178643NestedBase {
+public:
+ ~GH178643NestedDerived() {
+ helper(); // Calls helper() which calls virtualMethod()
+ }
+};
+
+// Test: final class with method overridden - should not warn.
+class GH178643OverrideBase {
+public:
+ virtual void virtualMethod() {}
+ virtual ~GH178643OverrideBase() = default;
+};
+
+class GH178643OverrideDerived final : public GH178643OverrideBase {
+public:
+ void virtualMethod() override {}
+ ~GH178643OverrideDerived() {
+ virtualMethod(); // no-warning: method is in final class
+ }
+};
+
class F {
public:
F() {
@@ -175,12 +244,16 @@ int main() {
G g;
H h;
H h1(1);
- X x;
+ X x;
X x1(1);
M m;
Y *y = new Y;
delete y;
header::Z z;
+ GH178643Derived gh178643;
+ GH178643CtorDerived gh178643ctor;
+ GH178643NestedDerived gh178643nested;
+ GH178643OverrideDerived gh178643override;
}
namespace PR34451 {
More information about the cfe-commits
mailing list