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@-1 {{reg_${{[0-9]+}}<unsigned int 
Element{SymRegion{reg_${{[0-9]+}}<Ctrl * p>},0 S64b,struct 
{{[0-9A-Za-z_]+}}::Ctrl}.c>}}
+  // expected-warning@-2 {{conj_$}}
+  clang_analyzer_eval(p->cmd() == p->cmd());
+  // expected-warning@-1 {{TRUE}}
+  // expected-warning@-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@-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@-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@-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@-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@-1 {{reg_${{[0-9]+}}<unsigned int 
Base{SymRegion{reg_${{[0-9]+}}<Child * childp>},Ctrl}.c>}}
+  clang_analyzer_eval(p->cmd() == p->cmd());
+  // expected-warning@-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@-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@-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@-1 {{reg_${{[0-9]+}}<unsigned int 
Base{SymRegion{reg_${{[0-9]+}}<Child * childp>},Ctrl}.c>}}
+  clang_analyzer_eval(p->cmd() == p->cmd());
+  // expected-warning@-1 {{TRUE}}
+}
+} // namespace nonfinal_method_on_ptr_with_dyn_type_final


        
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to