Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[analyzer] Use dynamic type when invalidating by a member function call #111138

Merged

Conversation

steakhal
Copy link
Contributor

@steakhal steakhal commented Oct 4, 2024

When instantiating "callable", the "class CallableType" nested type will only have a declaration in the copy for the instantiation - because it's not refereed to directly by any other code that would need a complete definition.

However, in the past, when conservative eval calling member function, we took the static type of the "this" expr, and looked up the CXXRecordDecl it refereed to to see if it has any mutable members (to decide if it needs to refine invalidation or not). Unfortunately, that query needs a definition, and it asserts otherwise, thus we crashed.

To fix this, we should consult the dynamic type of the object, because that will have the definition.
I anyways added a check for "hasDefinition" just to be on the safe side.

Fixes #77378

When instantiating "callable<T>", the "class CallableType" nested type
will only have a declaration in the copy for the instantiation - because
it's not refered to directly by any other code that would need a
complete definition.

However, in the past, when conservative eval calling member function,
we took the static type of the "this" expr, and looked up the
CXXRecordDecl it refered to to see if it has any mutable members (to
decide if it needs to refine invalidation or not).
Unfortunately, that query needs a definition, and it asserts otherwise,
thus we crashed.

To fix this, we should consult the dynamic type of the object, because
that will have the definition.
I anyways added a check for "hasDefinition" just to be on the safe side.

Fixes llvm#77378
@llvmbot llvmbot added the clang Clang issues not falling into any other category label Oct 4, 2024
@llvmbot
Copy link
Member

llvmbot commented Oct 4, 2024

@llvm/pr-subscribers-clang

@llvm/pr-subscribers-clang-static-analyzer-1

Author: Balazs Benics (steakhal)

Changes

When instantiating "callable<T>", the "class CallableType" nested type will only have a declaration in the copy for the instantiation - because it's not refereed to directly by any other code that would need a complete definition.

However, in the past, when conservative eval calling member function, we took the static type of the "this" expr, and looked up the CXXRecordDecl it refereed to to see if it has any mutable members (to decide if it needs to refine invalidation or not). Unfortunately, that query needs a definition, and it asserts otherwise, thus we crashed.

To fix this, we should consult the dynamic type of the object, because that will have the definition.
I anyways added a check for "hasDefinition" just to be on the safe side.

Fixes #77378


Full diff: https://github.com/llvm/llvm-project/pull/111138.diff

3 Files Affected:

  • (modified) clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h (+4)
  • (modified) clang/lib/StaticAnalyzer/Core/CallEvent.cpp (+30-25)
  • (modified) clang/test/Analysis/const-method-call.cpp (+46)
diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h
index 549c864dc91ef2..209f40b5b9f4c5 100644
--- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h
+++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h
@@ -690,6 +690,10 @@ class CXXInstanceCall : public AnyFunctionCall {
       ValueList &Values,
       RegionAndSymbolInvalidationTraits *ETraits) const override;
 
+  /// Returns the decl refered to by the "dynamic type" of the current object.
+  /// This may be null.
+  const CXXRecordDecl *getDeclForDynamicType() const;
+
 public:
   /// Returns the expression representing the implicit 'this' object.
   virtual const Expr *getCXXThisExpr() const { return nullptr; }
diff --git a/clang/lib/StaticAnalyzer/Core/CallEvent.cpp b/clang/lib/StaticAnalyzer/Core/CallEvent.cpp
index 1efac6ac1c57f4..2c04b42629b756 100644
--- a/clang/lib/StaticAnalyzer/Core/CallEvent.cpp
+++ b/clang/lib/StaticAnalyzer/Core/CallEvent.cpp
@@ -711,18 +711,17 @@ void CXXInstanceCall::getExtraInvalidatedValues(
   if (const auto *D = cast_or_null<CXXMethodDecl>(getDecl())) {
     if (!D->isConst())
       return;
-    // Get the record decl for the class of 'This'. D->getParent() may return a
-    // base class decl, rather than the class of the instance which needs to be
-    // checked for mutable fields.
-    // TODO: We might as well look at the dynamic type of the object.
-    const Expr *Ex = getCXXThisExpr()->IgnoreParenBaseCasts();
-    QualType T = Ex->getType();
-    if (T->isPointerType()) // Arrow or implicit-this syntax?
-      T = T->getPointeeType();
-    const CXXRecordDecl *ParentRecord = T->getAsCXXRecordDecl();
-    assert(ParentRecord);
+
+    // Get the record decl for the class of 'This'. D->getParent() may return
+    // a base class decl, rather than the class of the instance which needs to
+    // be checked for mutable fields.
+    const CXXRecordDecl *ParentRecord = getDeclForDynamicType();
+    if (!ParentRecord || !ParentRecord->hasDefinition())
+      return;
+
     if (ParentRecord->hasMutableFields())
       return;
+
     // Preserve CXXThis.
     const MemRegion *ThisRegion = ThisVal.getAsRegion();
     if (!ThisRegion)
@@ -748,6 +747,22 @@ SVal CXXInstanceCall::getCXXThisVal() const {
   return ThisVal;
 }
 
+const CXXRecordDecl *CXXInstanceCall::getDeclForDynamicType() const {
+  const MemRegion *R = getCXXThisVal().getAsRegion();
+  if (!R)
+    return nullptr;
+
+  DynamicTypeInfo DynType = getDynamicTypeInfo(getState(), R);
+  if (!DynType.isValid())
+    return nullptr;
+
+  QualType Ty = DynType.getType()->getPointeeType();
+  if (Ty.isNull())
+    return nullptr;
+
+  return Ty->getAsCXXRecordDecl();
+}
+
 RuntimeDefinition CXXInstanceCall::getRuntimeDefinition() const {
   // Do we have a decl at all?
   const Decl *D = getDecl();
@@ -759,21 +774,7 @@ RuntimeDefinition CXXInstanceCall::getRuntimeDefinition() const {
   if (!MD->isVirtual())
     return AnyFunctionCall::getRuntimeDefinition();
 
-  // Do we know the implicit 'this' object being called?
-  const MemRegion *R = getCXXThisVal().getAsRegion();
-  if (!R)
-    return {};
-
-  // Do we know anything about the type of 'this'?
-  DynamicTypeInfo DynType = getDynamicTypeInfo(getState(), R);
-  if (!DynType.isValid())
-    return {};
-
-  // Is the type a C++ class? (This is mostly a defensive check.)
-  QualType RegionType = DynType.getType()->getPointeeType();
-  assert(!RegionType.isNull() && "DynamicTypeInfo should always be a pointer.");
-
-  const CXXRecordDecl *RD = RegionType->getAsCXXRecordDecl();
+  const CXXRecordDecl *RD = getDeclForDynamicType();
   if (!RD || !RD->hasDefinition())
     return {};
 
@@ -797,6 +798,10 @@ RuntimeDefinition CXXInstanceCall::getRuntimeDefinition() const {
     return {};
   }
 
+  const MemRegion *R = getCXXThisVal().getAsRegion();
+  DynamicTypeInfo DynType = getDynamicTypeInfo(getState(), R);
+  assert(DynType.isValid() && "ensured by getDeclForDynamicType()");
+
   // Does the decl that we found have an implementation?
   const FunctionDecl *Definition;
   if (!Result->hasBody(Definition)) {
diff --git a/clang/test/Analysis/const-method-call.cpp b/clang/test/Analysis/const-method-call.cpp
index 8e1fd3b125f0e2..7da7ca5554a23e 100644
--- a/clang/test/Analysis/const-method-call.cpp
+++ b/clang/test/Analysis/const-method-call.cpp
@@ -271,3 +271,49 @@ void checkThatConstMethodCallDoesInvalidateObjectForCircularReferences() {
   // FIXME: Should be UNKNOWN.
   clang_analyzer_eval(t.x); // expected-warning{{TRUE}}
 }
+
+namespace gh77378 {
+template <typename Signature> class callable;
+
+template <typename R> class callable<R()> {
+  struct CallableType {
+    bool operator()();
+  };
+  using MethodType = R (CallableType::*)();
+  CallableType *object_{nullptr};
+  MethodType method_;
+
+public:
+  callable() = default;
+
+  template <typename T>
+  constexpr callable(const T &obj)
+      : object_{reinterpret_cast<CallableType *>(&const_cast<T &>(obj))},
+        method_{reinterpret_cast<MethodType>(
+            static_cast<bool (T::*)() const>(&T::operator()))} {}
+
+  constexpr bool const_method() const {
+    return (object_->*(method_))();
+  }
+
+  callable call() const & {
+    static const auto L = [this]() {
+      while (true) {
+        // This should not crash when conservative eval calling the member function
+        // when it unwinds the call stack due to exhausting the budget or reaching
+        // the inlining limit.
+        if (this->const_method()) {
+          break;
+        }
+      }
+      return true;
+    };
+    return L;
+  }
+};
+
+void entry() {
+  callable<bool()>{}.call().const_method();
+  // expected-warning@-1 {{Address of stack memory associated with temporary object of type 'callable<bool ()>' is still referred to by the static variable 'L' upon returning to the caller.  This will be a dangling reference}}
+}
+} // namespace gh77378

@steakhal
Copy link
Contributor Author

steakhal commented Oct 4, 2024

I haven't checked this change internally. Only on the LIT tests.

@steakhal
Copy link
Contributor Author

steakhal commented Oct 4, 2024

@necto

@steakhal
Copy link
Contributor Author

steakhal commented Oct 7, 2024

FYI this crashed since clang-7 I think.

@steakhal
Copy link
Contributor Author

Ping

Copy link
Collaborator

@Xazax-hun Xazax-hun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me, some minor nits inline.

clang/lib/StaticAnalyzer/Core/CallEvent.cpp Outdated Show resolved Hide resolved
clang/lib/StaticAnalyzer/Core/CallEvent.cpp Outdated Show resolved Hide resolved
@steakhal steakhal requested a review from Xazax-hun October 24, 2024 08:48
Copy link
Collaborator

@Xazax-hun Xazax-hun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks!

@steakhal steakhal merged commit 4affb2d into llvm:main Oct 24, 2024
6 of 8 checks passed
@steakhal steakhal deleted the bb/use-dyn-type-for-method-call-invalidation branch October 24, 2024 11:22
@frobtech frobtech mentioned this pull request Oct 25, 2024
NoumanAmir657 pushed a commit to NoumanAmir657/llvm-project that referenced this pull request Nov 4, 2024
…ll (llvm#111138)

When instantiating "callable<T>", the "class CallableType" nested type
will only have a declaration in the copy for the instantiation - because
it's not refereed to directly by any other code that would need a
complete definition.

However, in the past, when conservative eval calling member function, we
took the static type of the "this" expr, and looked up the CXXRecordDecl
it refereed to to see if it has any mutable members (to decide if it
needs to refine invalidation or not). Unfortunately, that query needs a
definition, and it asserts otherwise, thus we crashed.

To fix this, we should consult the dynamic type of the object, because
that will have the definition.
I anyways added a check for "hasDefinition" just to be on the safe side.

Fixes llvm#77378
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:static analyzer clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Clang-tidy-14 crashes
3 participants