Skip to content

Conversation

@ashfordium
Copy link
Contributor

Fixes #123459.

This changes checking of the returned expr to also look for memory regions whose stack frame context was a child of the current stack frame context, e.g., for cases like this given in #123459:

struct S { int *p; };
S f() {
  S s;
  {
    int a = 1;
    s.p = &a;
  }
  return s;
}

@llvmbot llvmbot added clang Clang issues not falling into any other category clang:static analyzer labels Feb 12, 2025
@llvmbot
Copy link
Member

llvmbot commented Feb 12, 2025

@llvm/pr-subscribers-clang

Author: Michael Flanders (Flandini)

Changes

Fixes #123459.

This changes checking of the returned expr to also look for memory regions whose stack frame context was a child of the current stack frame context, e.g., for cases like this given in #123459:

struct S { int *p; };
S f() {
  S s;
  {
    int a = 1;
    s.p = &a;
  }
  return s;
}

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

3 Files Affected:

  • (modified) clang/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp (+7-1)
  • (modified) clang/test/Analysis/stack-addr-ps.cpp (+45)
  • (modified) clang/test/Analysis/stackaddrleak.c (+22)
diff --git a/clang/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp
index c9df15ceb3b40..2a22e8e10efb0 100644
--- a/clang/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp
@@ -274,7 +274,13 @@ class FindStackRegionsSymbolVisitor final : public SymbolVisitor {
   void SaveIfEscapes(const MemRegion *MR) {
     const StackSpaceRegion *SSR =
         MR->getMemorySpace()->getAs<StackSpaceRegion>();
-    if (SSR && SSR->getStackFrame() == PoppedStackFrame)
+
+    if (!SSR)
+      return;
+
+    const StackFrameContext *CapturedSFC = SSR->getStackFrame();
+    if (CapturedSFC == PoppedStackFrame ||
+        PoppedStackFrame->isParentOf(CapturedSFC))
       EscapingStackRegions.push_back(MR);
   }
 
diff --git a/clang/test/Analysis/stack-addr-ps.cpp b/clang/test/Analysis/stack-addr-ps.cpp
index bf988d0a16959..2e509f358b49f 100644
--- a/clang/test/Analysis/stack-addr-ps.cpp
+++ b/clang/test/Analysis/stack-addr-ps.cpp
@@ -982,6 +982,51 @@ int& ret_local_field_ref() {
 }
 } //namespace return_address_of_true_positives
 
+namespace return_from_child_block_scope {
+struct S {
+  int *p;
+};
+
+S return_child_stack_context() {
+  S s;
+  {
+    int a = 1;
+    s = (S){ &a };
+  }
+  return s; // expected-warning {{Address of stack memory associated with local variable 'a' returned to caller}}
+}
+
+S return_child_stack_context_field() {
+  S s;
+  {
+    int a = 1;
+    s.p = &a;
+  }
+  return s; // expected-warning {{Address of stack memory associated with local variable 'a' returned to caller}}
+}
+
+// The below are reproducers from Issue #123459
+template <typename V>
+struct T {
+    V* q{};
+    T() = default;
+    T(T&& rhs) { q = rhs.q; rhs.q = nullptr;}
+    T& operator=(T&& rhs) { q = rhs.q; rhs.q = nullptr;}
+    void push_back(const V& v) { if (q == nullptr) q = new V(v); }
+    ~T() { delete q; }
+};
+
+T<S> f() {
+    T<S> t;
+    {
+        int a = 1;
+        t.push_back({ &a });
+    }
+    return t; // expected-warning {{Address of stack memory associated with local variable 'a' returned to caller}}
+}
+
+} // namespace return_from_child_block_scope
+
 namespace true_negatives_return_expressions {
 struct Container { int *x; };
 
diff --git a/clang/test/Analysis/stackaddrleak.c b/clang/test/Analysis/stackaddrleak.c
index f8101525401b0..95175996e8274 100644
--- a/clang/test/Analysis/stackaddrleak.c
+++ b/clang/test/Analysis/stackaddrleak.c
@@ -68,3 +68,25 @@ int *g_no_lifetime_bound() {
   int i = 0;
   return f_no_lifetime_bound(&i); // no-warning
 }
+
+struct child_stack_context_s {
+  int *p;
+};
+
+struct child_stack_context_s return_child_stack_context() {
+  struct child_stack_context_s s;
+  {
+    int a = 1;
+    s = (struct child_stack_context_s){ &a };
+  }
+  return s; // expected-warning {{Address of stack memory associated with local variable 'a' returned to caller}}
+}
+
+struct child_stack_context_s return_child_stack_context_field() {
+  struct child_stack_context_s s;
+  {
+    int a = 1;
+    s.p = &a;
+  }
+  return s; // expected-warning {{Address of stack memory associated with local variable 'a' returned to caller}}
+}

@llvmbot
Copy link
Member

llvmbot commented Feb 12, 2025

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

Author: Michael Flanders (Flandini)

Changes

Fixes #123459.

This changes checking of the returned expr to also look for memory regions whose stack frame context was a child of the current stack frame context, e.g., for cases like this given in #123459:

struct S { int *p; };
S f() {
  S s;
  {
    int a = 1;
    s.p = &amp;a;
  }
  return s;
}

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

3 Files Affected:

  • (modified) clang/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp (+7-1)
  • (modified) clang/test/Analysis/stack-addr-ps.cpp (+45)
  • (modified) clang/test/Analysis/stackaddrleak.c (+22)
diff --git a/clang/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp
index c9df15ceb3b40..2a22e8e10efb0 100644
--- a/clang/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp
@@ -274,7 +274,13 @@ class FindStackRegionsSymbolVisitor final : public SymbolVisitor {
   void SaveIfEscapes(const MemRegion *MR) {
     const StackSpaceRegion *SSR =
         MR->getMemorySpace()->getAs<StackSpaceRegion>();
-    if (SSR && SSR->getStackFrame() == PoppedStackFrame)
+
+    if (!SSR)
+      return;
+
+    const StackFrameContext *CapturedSFC = SSR->getStackFrame();
+    if (CapturedSFC == PoppedStackFrame ||
+        PoppedStackFrame->isParentOf(CapturedSFC))
       EscapingStackRegions.push_back(MR);
   }
 
diff --git a/clang/test/Analysis/stack-addr-ps.cpp b/clang/test/Analysis/stack-addr-ps.cpp
index bf988d0a16959..2e509f358b49f 100644
--- a/clang/test/Analysis/stack-addr-ps.cpp
+++ b/clang/test/Analysis/stack-addr-ps.cpp
@@ -982,6 +982,51 @@ int& ret_local_field_ref() {
 }
 } //namespace return_address_of_true_positives
 
+namespace return_from_child_block_scope {
+struct S {
+  int *p;
+};
+
+S return_child_stack_context() {
+  S s;
+  {
+    int a = 1;
+    s = (S){ &a };
+  }
+  return s; // expected-warning {{Address of stack memory associated with local variable 'a' returned to caller}}
+}
+
+S return_child_stack_context_field() {
+  S s;
+  {
+    int a = 1;
+    s.p = &a;
+  }
+  return s; // expected-warning {{Address of stack memory associated with local variable 'a' returned to caller}}
+}
+
+// The below are reproducers from Issue #123459
+template <typename V>
+struct T {
+    V* q{};
+    T() = default;
+    T(T&& rhs) { q = rhs.q; rhs.q = nullptr;}
+    T& operator=(T&& rhs) { q = rhs.q; rhs.q = nullptr;}
+    void push_back(const V& v) { if (q == nullptr) q = new V(v); }
+    ~T() { delete q; }
+};
+
+T<S> f() {
+    T<S> t;
+    {
+        int a = 1;
+        t.push_back({ &a });
+    }
+    return t; // expected-warning {{Address of stack memory associated with local variable 'a' returned to caller}}
+}
+
+} // namespace return_from_child_block_scope
+
 namespace true_negatives_return_expressions {
 struct Container { int *x; };
 
diff --git a/clang/test/Analysis/stackaddrleak.c b/clang/test/Analysis/stackaddrleak.c
index f8101525401b0..95175996e8274 100644
--- a/clang/test/Analysis/stackaddrleak.c
+++ b/clang/test/Analysis/stackaddrleak.c
@@ -68,3 +68,25 @@ int *g_no_lifetime_bound() {
   int i = 0;
   return f_no_lifetime_bound(&i); // no-warning
 }
+
+struct child_stack_context_s {
+  int *p;
+};
+
+struct child_stack_context_s return_child_stack_context() {
+  struct child_stack_context_s s;
+  {
+    int a = 1;
+    s = (struct child_stack_context_s){ &a };
+  }
+  return s; // expected-warning {{Address of stack memory associated with local variable 'a' returned to caller}}
+}
+
+struct child_stack_context_s return_child_stack_context_field() {
+  struct child_stack_context_s s;
+  {
+    int a = 1;
+    s.p = &a;
+  }
+  return s; // expected-warning {{Address of stack memory associated with local variable 'a' returned to caller}}
+}

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.

LG, thanks!

@ashfordium
Copy link
Contributor Author

Could you merge this for me?

@steakhal
Copy link
Contributor

I want to review this.

Copy link
Contributor

@steakhal steakhal 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 for your patience!

@steakhal steakhal merged commit 9f6b7b4 into llvm:main Feb 16, 2025
11 checks passed
sivan-shani pushed a commit to sivan-shani/llvm-project that referenced this pull request Feb 24, 2025
…frames (llvm#126986)

Fixes llvm#123459.

This changes checking of the returned expr to also look for memory
regions whose stack frame context was a child of the current stack frame
context, e.g., for cases like this given in llvm#123459:

```
struct S { int *p; };
S f() {
  S s;
  {
    int a = 1;
    s.p = &a;
  }
  return s;
}
```
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.

False negatives clang-analyzer-core.StackAddressEscape when storing pointers/references in container

4 participants