[clang] 0cf45d0 - [analyzer] Confidently inline final methods (#224070)
via cfe-commits
cfe-commits at lists.llvm.org
Sat Sep 19 09:25:09 PDT 2026
Author: DonĂ¡t Nagy
Date: 2026-09-19T18:25:03+02:00
New Revision: 0cf45d08f5d412cdc8681709d960e65a2757e5a6
URL: https://github.com/llvm/llvm-project/commit/0cf45d08f5d412cdc8681709d960e65a2757e5a6
DIFF: https://github.com/llvm/llvm-project/commit/0cf45d08f5d412cdc8681709d960e65a2757e5a6.diff
LOG: [analyzer] Confidently inline final methods (#224070)
When the analyzer sees a virtual method call, it usually splits two
execution paths: one where the method is inlined, and one where it is
evaluated conservatively (i.e. invalidates stuff and returns a conjured
symbol) to represent the possibility that an overriding method is called
from a more derived class.
This commit disables this logic in the case when the method
corresponding to the (static or known dynamic) type of the object is
`final` (or the type of the object is `final`), because in these cases
we can definitely know that it is the method that is actually called.
Fixes #222960.
Added:
clang/test/Analysis/inlining-final-methods.cpp
Modified:
clang/lib/StaticAnalyzer/Core/CallEvent.cpp
Removed:
################################################################################
diff --git a/clang/lib/StaticAnalyzer/Core/CallEvent.cpp b/clang/lib/StaticAnalyzer/Core/CallEvent.cpp
index 2338c06d5f992..d0b53a24e61ad 100644
--- a/clang/lib/StaticAnalyzer/Core/CallEvent.cpp
+++ b/clang/lib/StaticAnalyzer/Core/CallEvent.cpp
@@ -798,10 +798,18 @@ RuntimeDefinition CXXInstanceCall::getRuntimeDefinition() const {
if (!MD->isVirtual())
return AnyFunctionCall::getRuntimeDefinition();
+ // If the method is final or declared in a final class, we can inline it.
+ if (MD->hasAttr<FinalAttr>() || MD->getParent()->hasAttr<FinalAttr>())
+ return AnyFunctionCall::getRuntimeDefinition();
+
auto [RD, CanBeSubClass] = getDeclForDynamicType();
if (!RD || !RD->hasDefinition())
return {};
+ // We can confidently inline a method called on an object with final type.
+ if (RD->hasAttr<FinalAttr>())
+ CanBeSubClass = false;
+
// Find the decl for this method in that class.
const CXXMethodDecl *Result = MD->getCorrespondingMethodInClass(RD, true);
if (!Result) {
@@ -822,6 +830,10 @@ RuntimeDefinition CXXInstanceCall::getRuntimeDefinition() const {
return {};
}
+ // A final method cannot be overriden in a subclass.
+ if (Result->hasAttr<FinalAttr>())
+ CanBeSubClass = false;
+
// Does the decl that we found have an implementation?
const FunctionDecl *Definition;
if (!Result->hasBody(Definition)) {
diff --git a/clang/test/Analysis/inlining-final-methods.cpp b/clang/test/Analysis/inlining-final-methods.cpp
new file mode 100644
index 0000000000000..53ded4990a9bf
--- /dev/null
+++ b/clang/test/Analysis/inlining-final-methods.cpp
@@ -0,0 +1,151 @@
+// RUN: %clang_analyze_cc1 -analyzer-checker=debug.ExprInspection -verify %s
+
+void clang_analyzer_dump(unsigned);
+void clang_analyzer_eval(bool);
+
+struct Msg {
+ virtual unsigned cmd() const = 0;
+};
+
+namespace nonfinal_bifurcates {
+// When the method is non-final and the dynamic type is unclear, the analysis
+// should bifurcate, with one branch inlining the method and the other branch
+// doing a conservative evaluation (which represents that another overriding
+// method is called). (This is the baseline which is disabled in some cases.)
+struct Ctrl : Msg {
+ unsigned c;
+ unsigned cmd() const override { return c; }
+};
+
+void test(Ctrl* p) {
+ clang_analyzer_dump(p->cmd());
+ // expected-warning-re at -1 {{reg_${{[0-9]+}}<unsigned int Element{SymRegion{reg_${{[0-9]+}}<Ctrl * p>},0 S64b,struct {{[0-9A-Za-z_]+}}::Ctrl}.c>}}
+ // expected-warning at -2 {{conj_$}}
+ clang_analyzer_eval(p->cmd() == p->cmd());
+ // expected-warning at -1 {{TRUE}}
+ // expected-warning at -2 {{FALSE}}
+}
+} // namespace nonfinal_bifurcates
+
+namespace gh222960 {
+// Ctrl::cmd() is final, the analyzer should not split off a "maybe dynamic
+// dispatch invokes a
diff erent overriding method" execution path, and only
+// follow the path where the method body is inlined.
+struct Ctrl : Msg {
+ unsigned c;
+ unsigned cmd() const final { return c; }
+};
+
+void test(Ctrl* p) {
+ clang_analyzer_dump(p->cmd());
+ // expected-warning-re at -1 {{reg_${{[0-9]+}}<unsigned int Element{SymRegion{reg_${{[0-9]+}}<Ctrl * p>},0 S64b,struct {{[0-9A-Za-z_]+}}::Ctrl}.c>}}
+ clang_analyzer_eval(p->cmd() == p->cmd()); // expected-warning {{TRUE}}
+}
+} // namespace gh222960
+
+namespace final_struct {
+// The analyzer should also confidently inline the method of a final class.
+struct Ctrl final : Msg {
+ unsigned c;
+ unsigned cmd() const override { return c; }
+};
+
+void test(Ctrl* p)
+{
+ clang_analyzer_dump(p->cmd());
+ // expected-warning-re at -1 {{reg_${{[0-9]+}}<unsigned int Element{SymRegion{reg_${{[0-9]+}}<Ctrl * p>},0 S64b,struct {{[0-9A-Za-z_]+}}::Ctrl}.c>}}
+ clang_analyzer_eval(p->cmd() == p->cmd()); // expected-warning {{TRUE}}
+}
+} // namespace final_struct
+
+namespace final_method_on_child_ptr {
+// A final method should also be inlined when it is called through a pointer
+// whose (static) type is a child of the class where it was defined.
+struct Ctrl : Msg {
+ unsigned c;
+ unsigned cmd() const final { return c; }
+};
+
+struct Child : Ctrl {};
+
+void test(Child* p) {
+ clang_analyzer_dump(p->cmd());
+ // expected-warning-re at -1 {{reg_${{[0-9]+}}<unsigned int Base{SymRegion{reg_${{[0-9]+}}<Child * p>},Ctrl}.c>}}
+ clang_analyzer_eval(p->cmd() == p->cmd()); // expected-warning {{TRUE}}
+}
+} // namespace final_method_on_child_ptr
+
+namespace nonfinal_method_on_final_child_ptr {
+// We can confidently inline even a non-final method of a non-final class if it
+// is called on an object whose type is final and does not override it.
+struct Ctrl : Msg {
+ unsigned c;
+ unsigned cmd() const override { return c; }
+};
+
+struct Child final : Ctrl {};
+
+void test(Child* p) {
+ clang_analyzer_dump(p->cmd());
+ // expected-warning-re at -1 {{reg_${{[0-9]+}}<unsigned int Base{SymRegion{reg_${{[0-9]+}}<Child * p>},Ctrl}.c>}}
+ clang_analyzer_eval(p->cmd() == p->cmd()); // expected-warning {{TRUE}}
+}
+} // namespace nonfinal_method_on_final_child_ptr
+
+
+namespace final_method_on_ptr_with_dyn_type_child {
+// A final method should also be inlined when it is called through a pointer
+// whose dynamic type is a child of the class where it was defined.
+struct Ctrl : Msg {
+ unsigned c;
+ unsigned cmd() const final { return c; }
+};
+
+struct Child : Ctrl {};
+
+void test(Child* childp) {
+ Ctrl *p = childp;
+ clang_analyzer_dump(p->cmd());
+ // expected-warning-re at -1 {{reg_${{[0-9]+}}<unsigned int Base{SymRegion{reg_${{[0-9]+}}<Child * childp>},Ctrl}.c>}}
+ clang_analyzer_eval(p->cmd() == p->cmd());
+ // expected-warning at -1 {{TRUE}}
+}
+} // namespace final_method_on_ptr_with_dyn_type_child
+
+namespace final_method_on_base_ptr_with_known_dyn_type {
+// A final method should also be inlined when it is called through a pointer
+// base pointer with a known dynamic type.
+struct Base : Msg {};
+
+struct Ctrl : Base {
+ unsigned c;
+ unsigned cmd() const final { return c; }
+};
+
+void test(Ctrl* ctrlp) {
+ Base *p = ctrlp;
+ clang_analyzer_dump(p->cmd());
+ // expected-warning-re at -1 {{reg_${{[0-9]+}}<unsigned int Element{SymRegion{reg_${{[0-9]+}}<Ctrl * ctrlp>},0 S64b,struct {{[0-9A-Za-z_]+}}::Ctrl}.c>}}
+ clang_analyzer_eval(p->cmd() == p->cmd());
+ // expected-warning at -1 {{TRUE}}
+}
+} // namespace final_method_on_base_ptr_with_known_dyn_type
+
+namespace nonfinal_method_on_ptr_with_dyn_type_final {
+// We can confidently inline even a non-final method of a non-final class if it
+// is called on an object whose dynamic type is final and does not override it.
+struct Ctrl : Msg {
+ unsigned c;
+ unsigned cmd() const override { return c; }
+};
+
+struct Child final : Ctrl {};
+
+void test(Child* childp) {
+ Ctrl *p = childp;
+ clang_analyzer_dump(p->cmd());
+ // expected-warning-re at -1 {{reg_${{[0-9]+}}<unsigned int Base{SymRegion{reg_${{[0-9]+}}<Child * childp>},Ctrl}.c>}}
+ clang_analyzer_eval(p->cmd() == p->cmd());
+ // expected-warning at -1 {{TRUE}}
+}
+} // namespace nonfinal_method_on_ptr_with_dyn_type_final
More information about the cfe-commits
mailing list