Skip to content

[clang] Diagnose dangling issues for the "Container<GSLPointer>" case. #107213

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

Merged
merged 3 commits into from
Sep 11, 2024

Conversation

hokein
Copy link
Collaborator

@hokein hokein commented Sep 4, 2024

This pull request enhances the GSL lifetime analysis to detect situations where a dangling Container<GSLPointer> object is constructed:

std::vector<std::string_view> bad = {std::string()}; // dangling

The assignment case is not yet supported, but they will be addressed in a follow-up.

Fixes #100526 (excluding the push_back case).

@hokein hokein requested review from Xazax-hun and usx95 September 4, 2024 10:09
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Sep 4, 2024
@llvmbot
Copy link
Member

llvmbot commented Sep 4, 2024

@llvm/pr-subscribers-clang

Author: Haojian Wu (hokein)

Changes

This pull request enhances the GSL lifetime analysis to detect situations where a dangling Container&lt;GSLPointer&gt; object is constructed:

std::vector&lt;std::string_view&gt; bad = {std::string()}; // dangling

The assignment case is not yet supported, but they will be addressed in a follow-up.

Fixes #100526 (excluding the push_back case).


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

2 Files Affected:

  • (modified) clang/lib/Sema/CheckExprLifetime.cpp (+23-5)
  • (modified) clang/test/Sema/warn-lifetime-analysis-nocfg.cpp (+44)
diff --git a/clang/lib/Sema/CheckExprLifetime.cpp b/clang/lib/Sema/CheckExprLifetime.cpp
index f7540a6e3a8979..a58ea39c6bd5e5 100644
--- a/clang/lib/Sema/CheckExprLifetime.cpp
+++ b/clang/lib/Sema/CheckExprLifetime.cpp
@@ -363,10 +363,14 @@ static bool implicitObjectParamIsLifetimeBound(const FunctionDecl *FD) {
     if (ATL.getAttrAs<LifetimeBoundAttr>())
       return true;
   }
-
   return isNormalAsisgnmentOperator(FD);
 }
 
+bool isFirstTemplateArgumentGSLPointer(const TemplateArgumentList &TAs) {
+  return TAs.size() > 0 && TAs[0].getKind() == TemplateArgument::Type &&
+         isRecordWithAttr<PointerAttr>(TAs[0].getAsType());
+}
+
 // Visit lifetimebound or gsl-pointer arguments.
 static void visitFunctionCallArguments(IndirectLocalPath &Path, Expr *Call,
                                        LocalVisitor Visit,
@@ -470,10 +474,24 @@ static void visitFunctionCallArguments(IndirectLocalPath &Path, Expr *Call,
         VisitGSLPointerArg(Callee, Args[0],
                            !Callee->getReturnType()->isReferenceType());
       } else {
-        if (auto *CCE = dyn_cast<CXXConstructExpr>(Call);
-            CCE && CCE->getConstructor()->getParent()->hasAttr<PointerAttr>())
-          VisitGSLPointerArg(CCE->getConstructor()->getParamDecl(0), Args[0],
-                             true);
+        if (auto *Ctor = dyn_cast<CXXConstructExpr>(Call)) {
+          const auto *ClassD = Ctor->getConstructor()->getParent();
+          // Constructing the Container<GSLPointer> case (e.g.
+          // std::optional<string_view>) case.
+          if (const auto *CTSD =
+                  dyn_cast<ClassTemplateSpecializationDecl>(ClassD)) {
+            if (isFirstTemplateArgumentGSLPointer(CTSD->getTemplateArgs()) &&
+                CTSD->hasAttr<OwnerAttr>()) {
+              VisitGSLPointerArg(Ctor->getConstructor()->getParamDecl(0),
+                                 Args[0], true);
+              return;
+            }
+          }
+          // Constructing the GSLPointer (e.g. std::string_view) case.
+          if (ClassD->hasAttr<PointerAttr>())
+            VisitGSLPointerArg(Ctor->getConstructor()->getParamDecl(0), Args[0],
+                               true);
+        }
       }
     }
   }
diff --git a/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp b/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
index cd1904db327105..dd0ed138c99676 100644
--- a/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
+++ b/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
@@ -158,17 +158,26 @@ auto begin(C &c) -> decltype(c.begin());
 template<typename T, int N>
 T *begin(T (&array)[N]);
 
+using size_t = decltype(sizeof(0));
+
+template<typename T>
+struct initializer_list {
+  const T* ptr; size_t sz;
+};
 template <typename T>
 struct vector {
   typedef __gnu_cxx::basic_iterator<T> iterator;
   iterator begin();
   iterator end();
   const T *data() const;
+  vector();
+  vector(initializer_list<T> __l);
   T &at(int n);
 };
 
 template<typename T>
 struct basic_string_view {
+  basic_string_view();
   basic_string_view(const T *);
   const T *begin() const;
 };
@@ -203,11 +212,21 @@ template<typename T>
 struct optional {
   optional();
   optional(const T&);
+
+  template<typename U = T>
+	optional(U&& t);
+
+  template<typename U>
+	optional(optional<U>&& __t);
+
   T &operator*() &;
   T &&operator*() &&;
   T &value() &;
   T &&value() &&;
 };
+template<typename T>
+optional<T> make_optional(T&&);
+
 
 template<typename T>
 struct stack {
@@ -499,3 +518,28 @@ std::string_view test2(int i, std::optional<std::string_view> a) {
   return std::move(a.value());
 }
 }
+
+namespace GH100526 {
+void test() {
+  std::vector<std::string_view> t1 = {std::string()}; // expected-warning {{object backing the pointer will be destroyed at the end}}
+  std::optional<std::string_view> t2 = std::string(); // expected-warning {{object backing the pointer}}
+
+  std::string s;
+  // This is a tricky use-after-free case, what it does:
+  //   1. make_optional creates a temporary "optional<string>"" object
+  //   2. the temporary object owns the underlying string which is copied from s.
+  //   3. the t3 object holds the view to the underlying string of the temporary object.
+  std::optional<std::string_view> t3 = std::make_optional(s); // expected-warning {{object backing the pointer}}
+
+  // FIXME: should work for assignment cases
+  t1 = {std::string()};
+  t2 = std::string();
+
+  // no warning on copying pointers.
+  std::vector<std::string_view> n1 = {std::string_view()};
+  std::optional<std::string_view> n2 = {std::string_view()};
+  std::optional<std::string_view> n3 = std::make_optional(std::string_view());
+
+}
+
+} // namespace GH100526

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!

Copy link
Contributor

@usx95 usx95 left a comment

Choose a reason for hiding this comment

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

Thanks. This looks quite useful.

Since this is not limited to a hardcoded set of types/stl-containers, this is a substantial change in the behaviour of general Owner<Pointer> types and warrants extra documentation both in GSL analysis docs and ReleaseNotes.

Comment on lines 474 to 494
VisitGSLPointerArg(Callee, Args[0],
!Callee->getReturnType()->isReferenceType());
} else {
if (auto *CCE = dyn_cast<CXXConstructExpr>(Call);
CCE && CCE->getConstructor()->getParent()->hasAttr<PointerAttr>())
VisitGSLPointerArg(CCE->getConstructor()->getParamDecl(0), Args[0],
true);
if (auto *Ctor = dyn_cast<CXXConstructExpr>(Call)) {
const auto *ClassD = Ctor->getConstructor()->getParent();
// Constructing the Container<GSLPointer> case (e.g.
// std::optional<string_view>) case.
if (const auto *CTSD =
dyn_cast<ClassTemplateSpecializationDecl>(ClassD)) {
if (isFirstTemplateArgumentGSLPointer(CTSD->getTemplateArgs()) &&
CTSD->hasAttr<OwnerAttr>()) {
VisitGSLPointerArg(Ctor->getConstructor()->getParamDecl(0),
Args[0], true);
return;
}
}
// Constructing the GSLPointer (e.g. std::string_view) case.
if (ClassD->hasAttr<PointerAttr>())
VisitGSLPointerArg(Ctor->getConstructor()->getParamDecl(0), Args[0],
true);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

This GSL analysis is done only on the first arg.
Can we separate this out and move outside the loop ?

// Perform GSL analysis for the first argument
bool PerformGSLAnalysis = EnableLifetimeWarnings && !CheckCoroCall && !Callee->getParamDecl(0)->hasAttr<LifetimeBoundAttr>()
if (PerformGSLAnalysis) {
...

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The logic here is tightly coupled with the lifetimebound attribute. If the first argument is already visited due to the lifetimebound attribute, we avoid performing the GSL analysis on it again to prevent duplication. Therefore, both checks need to remain within the same loop.

Copy link
Contributor

@usx95 usx95 Sep 5, 2024

Choose a reason for hiding this comment

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

Isn't bool PerformGSLAnalysisForFirstArg = EnableLifetimeWarnings && !CheckCoroCall && !Callee->getParamDecl(0)->hasAttr<LifetimeBoundAttr>() the sufficient condition to check outside the loop?

@hokein
Copy link
Collaborator Author

hokein commented Sep 4, 2024

Since this is not limited to a hardcoded set of types/stl-containers, this is a substantial change in the behaviour of general Owner types and warrants extra documentation both in GSL analysis docs and ReleaseNotes.

Done.

@hokein hokein force-pushed the auto-infer-lb-for-container branch from 96c6bbd to fe0ca71 Compare September 4, 2024 20:28
Copy link

github-actions bot commented Sep 4, 2024

✅ With the latest revision this PR passed the C/C++ code formatter.

@hokein hokein force-pushed the auto-infer-lb-for-container branch from fe0ca71 to 9d9c4fb Compare September 5, 2024 07:48
@hokein
Copy link
Collaborator Author

hokein commented Sep 5, 2024

After further testing, this change introduces a new false positive -- we emit a return-stack-address warning for the following case, this is not correct.

std::vector<std::string_view> kk() {
  std::vector<std::string_view> s;
  return s;
}

Looking now.

Copy link
Contributor

@usx95 usx95 left a comment

Choose a reason for hiding this comment

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

Thanks. LG. Some comments on adding more tests.

@hokein hokein force-pushed the auto-infer-lb-for-container branch from 9d9c4fb to f6a9125 Compare September 6, 2024 11:37
@hokein
Copy link
Collaborator Author

hokein commented Sep 6, 2024

I have fixed the false positives, and added testcases for them. (I have tested this patch on our internal codebase, no new false positives being found).

Copy link
Contributor

@usx95 usx95 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.

We teach the GSL lifetime analysis to handle this code path, particuly
when constructing the container<GSLPointer> object from a GSLOwner.
@hokein hokein force-pushed the auto-infer-lb-for-container branch from 035c09e to 1ad9666 Compare September 9, 2024 19:26
@hokein hokein merged commit e50131a into llvm:main Sep 11, 2024
9 checks passed
@hokein hokein deleted the auto-infer-lb-for-container branch September 11, 2024 11:21
@aeubanks
Copy link
Contributor

this looks like it has false positives: https://crbug.com/366074092

can we revert in the meantime?

@hokein
Copy link
Collaborator Author

hokein commented Sep 12, 2024

Thanks, it looks like this introduces a new false positive:

#include <optional>
std::optional<int*> func(int a) {
  if (a)
   return std::make_optional(nullptr); // emit a dangling.
}

I'm going to revert it.

hokein added a commit that referenced this pull request Sep 12, 2024
…r>" case. (#107213)"

This reverts commit e50131a.

It introduces a new false positive, see comment #107213 (comment)
hokein added a commit to hokein/llvm-project that referenced this pull request Sep 12, 2024
hokein added a commit to hokein/llvm-project that referenced this pull request Sep 13, 2024
hokein added a commit to hokein/llvm-project that referenced this pull request Sep 23, 2024
hokein added a commit to hokein/llvm-project that referenced this pull request Sep 23, 2024
hokein added a commit that referenced this pull request Sep 25, 2024
…r>" case. #107213 (#108344)

This relands #107213, with with fixes to address false positives
(`make_optional(nullptr)`).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

-Wdangling support for containers with pointer-like elements
5 participants