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
