xazax.hun updated this revision to Diff 251569.
xazax.hun marked an inline comment as done.
xazax.hun added a comment.

- Rebase.
- Address some review comments.


CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D72810/new/

https://reviews.llvm.org/D72810

Files:
  clang/include/clang/AST/Attr.h
  clang/include/clang/AST/LifetimeAttr.h
  clang/include/clang/Basic/Attr.td
  clang/include/clang/Basic/DiagnosticGroups.td
  clang/include/clang/Basic/DiagnosticSemaKinds.td
  clang/lib/AST/CMakeLists.txt
  clang/lib/AST/LifetimeAttr.cpp
  clang/lib/Sema/SemaDeclAttr.cpp
  clang/lib/Sema/SemaInit.cpp
  clang/lib/Sema/SemaType.cpp
  clang/test/SemaCXX/attr-psets-annotation.cpp

Index: clang/test/SemaCXX/attr-psets-annotation.cpp
===================================================================
--- /dev/null
+++ clang/test/SemaCXX/attr-psets-annotation.cpp
@@ -0,0 +1,135 @@
+// NOT RUN: %clang_cc1 -fsyntax-only -Wlifetime -Wlifetime-dump-contracts -verify %s
+
+namespace gsl {
+struct null_t {
+  template <typename T>
+  operator T() const;
+} null;
+
+struct global_t {
+  template <typename T>
+  operator T() const;
+} global;
+
+struct invalid_t {
+  template <typename T>
+  operator T() const;
+} invalid;
+
+struct return_t {
+  template <typename T>
+  operator T() const;
+} Return;
+
+template<typename T> T &&declval();
+
+template<typename T>
+struct PointerTraits {
+  using DerefType = decltype(*declval<T>());
+};
+
+struct PSet {
+  template<typename... T>
+  PSet(const T&...);
+};
+
+template <typename T>
+auto deref(const T &t) -> typename PointerTraits<T>::DerefType;
+
+template <typename T>
+bool lifetime(const T &lhs, const PSet &rhs);
+} // namespace gsl
+
+void basic(int *a, int *b) [[gsl::pre(gsl::lifetime(b, {a}))]];
+// expected-warning@-1 {{Pre { b -> { a }; }}}
+
+void specials(int *a, int *b, int *c)
+    [[gsl::pre(gsl::lifetime(a, {gsl::null}))]]
+    [[gsl::pre(gsl::lifetime(b, {gsl::global}))]]
+    [[gsl::pre(gsl::lifetime(c, {gsl::invalid}))]];
+// expected-warning@-4 {{Pre { a -> { null }; b -> { global }; c -> { invalid }; }}}
+
+void variadic(int *a, int *b, int *c)
+    [[gsl::pre(gsl::lifetime(b, {a, c}))]];
+// expected-warning@-2 {{Pre { b -> { a c }; }}}
+
+void variadic_special(int *a, int *b, int *c)
+    [[gsl::pre(gsl::lifetime(b, {a, gsl::null}))]];
+// expected-warning@-2 {{Pre { b -> { null a }; }}}
+
+void multiple_annotations(int *a, int *b, int *c)
+    [[gsl::pre(gsl::lifetime(b, {a}))]]
+    [[gsl::pre(gsl::lifetime(c, {a}))]];
+// expected-warning@-3 {{Pre { b -> { a }; c -> { a }; }}}
+
+void multiple_annotations_chained(int *a, int *b, int *c)
+    [[gsl::pre(gsl::lifetime(b, {a}))]]
+    [[gsl::pre(gsl::lifetime(c, {b}))]];
+// expected-warning@-3 {{Pre { b -> { a }; c -> { a }; }}}
+
+void deref_ptr(int *a, int *b, int **c)
+    [[gsl::pre(gsl::lifetime(gsl::deref(c), {a}))]];
+// expected-warning@-2 {{Pre { *c -> { a }; }}}
+
+void deref_ptr_pointee(int *a, int *b, int **c)
+    [[gsl::pre(gsl::lifetime(a, {gsl::deref(c)}))]];
+// expected-warning@-2 {{Pre { a -> { *c }; }}}
+
+void deref_ref(int *a, int *b, int *&c)
+    [[gsl::pre(gsl::lifetime(gsl::deref(c), {a}))]];
+// expected-warning@-2 {{Pre { *c -> { a }; }}}
+
+void deref_ref_pointee(int *a, int *b, int *&c)
+    [[gsl::pre(gsl::lifetime(a, {gsl::deref(c)}))]];
+// expected-warning@-2 {{Pre { a -> { *c }; }}}
+
+struct [[gsl::Owner(void)]] X {
+  void f(X **out)
+      [[gsl::post(gsl::lifetime(gsl::deref(out), {this}))]];
+  // expected-warning@-2 {{Pre { }  Post { *out -> { this }; }}}
+  X *operator+(const X& other)
+      [[gsl::post(gsl::lifetime(gsl::Return, {other}))]];
+  // expected-warning@-2 {{Pre { }  Post { (return value) -> { other }; }}}
+};
+
+template <typename It, typename T>
+It find(It begin, It end, const T &val)
+    [[gsl::pre(gsl::lifetime(end, {begin}))]]
+    [[gsl::post(gsl::lifetime(gsl::Return, {begin}))]];
+// expected-warning@-3 {{Pre { end -> { begin }; }  Post { (return value) -> { begin }; }}}
+
+int *find_nontemp(int *begin, int *end, const int &val)
+    [[gsl::pre(gsl::lifetime(end, {begin}))]]
+    [[gsl::post(gsl::lifetime(gsl::Return, {begin}))]];
+// expected-warning@-3 {{Pre { end -> { begin }; }  Post { (return value) -> { begin }; }}}
+
+struct [[gsl::Owner(int)]] MyOwner {
+  int *begin()
+      [[gsl::post(lifetime(gsl::Return, {this}))]];
+  // expected-warning@-2 {{Pre { }  Post { (return value) -> { this }; }}}
+  int *end()
+      [[gsl::post(lifetime(gsl::Return, {this}))]];
+  // expected-warning@-2 {{Pre { }  Post { (return value) -> { this }; }}}
+};
+
+void testGslWarning() {
+  int *res = find(MyOwner{}.begin(), MyOwner{}.end(), 5);
+  // expected-warning@-1 {{object backing the pointer will be destroyed at the end of the full-expression}}
+  (void)res;
+  int *res2 = find_nontemp(MyOwner{}.begin(), MyOwner{}.end(), 5);
+  // expected-warning@-1 {{object backing the pointer will be destroyed at the end of the full-expression}}
+  (void)res2;
+  X x;
+  // TODO: this should work without X annotated as owner.
+  X *xp = x + X{};
+  // expected-warning@-1 {{object backing the pointer will be destroyed at the end of the full-expression}}
+  (void)xp;
+}
+
+// Warnings/errors
+
+void unsupported_contract(int *a, int *b) [[gsl::pre(gsl::lifetime(b, {a++}))]];
+// expected-warning@-1 {{this pre/postcondition is not supported}}
+
+void type_error(int *a, int *b) [[gsl::pre(gsl::lifetime(b, {**a}))]];
+// expected-error@-1 {{indirection requires pointer operand ('int' invalid)}}
Index: clang/lib/Sema/SemaType.cpp
===================================================================
--- clang/lib/Sema/SemaType.cpp
+++ clang/lib/Sema/SemaType.cpp
@@ -7728,6 +7728,12 @@
         HandleLifetimeBoundAttr(state, type, attr);
       break;
 
+    // Move function type attribute to the declarator.
+    case ParsedAttr::AT_LifetimeContract:
+      moveAttrFromListToList(attr, state.getCurrentAttributes(),
+                             state.getDeclarator().getAttributes());
+      break;
+
     case ParsedAttr::AT_NoDeref: {
       ASTContext &Ctx = state.getSema().Context;
       type = state.getAttributedType(createSimpleAttr<NoDerefAttr>(Ctx, attr),
Index: clang/lib/Sema/SemaInit.cpp
===================================================================
--- clang/lib/Sema/SemaInit.cpp
+++ clang/lib/Sema/SemaInit.cpp
@@ -15,6 +15,7 @@
 #include "clang/AST/ExprCXX.h"
 #include "clang/AST/ExprObjC.h"
 #include "clang/AST/ExprOpenMP.h"
+#include "clang/AST/LifetimeAttr.h"
 #include "clang/AST/TypeLoc.h"
 #include "clang/Basic/CharInfo.h"
 #include "clang/Basic/SourceManager.h"
@@ -6788,6 +6789,58 @@
   return false;
 }
 
+static bool shouldTrackContract(const LifetimeContractAttr *LCAttr,
+                                const FunctionDecl *FD,
+                                LifetimeContractVariable CV) {
+  if (!LCAttr || !LCAttr->PostContracts)
+    return false;
+  const LifetimeContracts &PM = *LCAttr->PostContracts;
+  auto It = PM.find(LifetimeContractVariable::returnVal());
+  if (It == PM.end())
+    return false;
+  return It->second.count(CV);
+}
+
+static LifetimeContractAttr *getLifetimeAttr(const FunctionDecl *FD) {
+  if (const auto LCAttr = FD->getAttr<LifetimeContractAttr>()) {
+    // The actual information is stored at primary templates for
+    // specializations.
+    if (const auto *FTD = FD->getPrimaryTemplate()) {
+      assert(FTD->getTemplatedDecl()->hasAttr<LifetimeContractAttr>());
+      return FTD->getTemplatedDecl()->getAttr<LifetimeContractAttr>();
+    }
+    return LCAttr;
+  }
+  return nullptr;
+}
+
+namespace {
+struct CallInfo {
+  FunctionDecl *Callee = nullptr;
+  ArrayRef<Expr *> Args;
+  Expr *ObjectArg = nullptr;
+};
+} // namespace
+
+static CallInfo getCallInfo(Expr *Call) {
+  CallInfo Info;
+  if (auto *CE = dyn_cast<CallExpr>(Call)) {
+    Info.Callee = CE->getDirectCallee();
+    Info.Args = llvm::makeArrayRef(CE->getArgs(), CE->getNumArgs());
+  } else {
+    auto *CCE = cast<CXXConstructExpr>(Call);
+    Info.Callee = CCE->getConstructor();
+    Info.Args = llvm::makeArrayRef(CCE->getArgs(), CCE->getNumArgs());
+  }
+  if (isa<CXXOperatorCallExpr>(Call) && Info.Callee->isCXXInstanceMember()) {
+    Info.ObjectArg = Info.Args[0];
+    Info.Args = Info.Args.slice(1);
+  } else if (auto *MCE = dyn_cast<CXXMemberCallExpr>(Call)) {
+    Info.ObjectArg = MCE->getImplicitObjectArgument();
+  }
+  return Info;
+}
+
 static void handleGslAnnotatedTypes(IndirectLocalPath &Path, Expr *Call,
                                     LocalVisitor Visit) {
   auto VisitPointerArg = [&](const Decl *D, Expr *Arg, bool Value) {
@@ -6818,32 +6871,38 @@
     Path.pop_back();
   };
 
-  if (auto *MCE = dyn_cast<CXXMemberCallExpr>(Call)) {
-    const auto *MD = cast_or_null<CXXMethodDecl>(MCE->getDirectCallee());
-    if (MD && shouldTrackImplicitObjectArg(MD))
-      VisitPointerArg(MD, MCE->getImplicitObjectArgument(),
-                      !MD->getReturnType()->isReferenceType());
+  CallInfo CI = getCallInfo(Call);
+  if (!CI.Callee)
     return;
-  } else if (auto *OCE = dyn_cast<CXXOperatorCallExpr>(Call)) {
-    FunctionDecl *Callee = OCE->getDirectCallee();
-    if (Callee && Callee->isCXXInstanceMember() &&
-        shouldTrackImplicitObjectArg(cast<CXXMethodDecl>(Callee)))
-      VisitPointerArg(Callee, OCE->getArg(0),
-                      !Callee->getReturnType()->isReferenceType());
+
+  bool ReturnsRef = CI.Callee->getReturnType()->isReferenceType();
+
+  if (auto *CCE = dyn_cast<CXXConstructExpr>(Call)) {
+    const CXXRecordDecl *RD = CCE->getConstructor()->getParent();
+    if (CI.Args.size() > 0 && RD->hasAttr<PointerAttr>())
+      VisitPointerArg(CI.Callee->getParamDecl(0), CI.Args[0], true);
     return;
-  } else if (auto *CE = dyn_cast<CallExpr>(Call)) {
-    FunctionDecl *Callee = CE->getDirectCallee();
-    if (Callee && shouldTrackFirstArgument(Callee))
-      VisitPointerArg(Callee, CE->getArg(0),
-                      !Callee->getReturnType()->isReferenceType());
+  }
+
+  const auto LCAttr = getLifetimeAttr(CI.Callee);
+  for (unsigned I = 0; I < CI.Args.size() && I < CI.Callee->getNumParams(); ++I)
+    if (shouldTrackContract(LCAttr, CI.Callee,
+                            LifetimeContractVariable::paramBasedVal(
+                                CI.Callee->getParamDecl(I))))
+      VisitPointerArg(CI.Callee, CI.Args[I], !ReturnsRef);
+
+  if (auto *MD = dyn_cast<CXXMethodDecl>(CI.Callee)) {
+    if (shouldTrackImplicitObjectArg(MD) ||
+        shouldTrackContract(LCAttr, CI.Callee,
+                            LifetimeContractVariable::thisVal(MD->getParent())))
+      VisitPointerArg(MD, CI.ObjectArg, !ReturnsRef);
     return;
   }
 
-  if (auto *CCE = dyn_cast<CXXConstructExpr>(Call)) {
-    const auto *Ctor = CCE->getConstructor();
-    const CXXRecordDecl *RD = Ctor->getParent();
-    if (CCE->getNumArgs() > 0 && RD->hasAttr<PointerAttr>())
-      VisitPointerArg(Ctor->getParamDecl(0), CCE->getArgs()[0], true);
+  if (auto *CE = dyn_cast<CallExpr>(Call)) {
+    if (shouldTrackFirstArgument(CI.Callee))
+      VisitPointerArg(CI.Callee, CI.Args[0], !ReturnsRef);
+    return;
   }
 }
 
@@ -6866,28 +6925,10 @@
 
 static void visitLifetimeBoundArguments(IndirectLocalPath &Path, Expr *Call,
                                         LocalVisitor Visit) {
-  const FunctionDecl *Callee;
-  ArrayRef<Expr*> Args;
-
-  if (auto *CE = dyn_cast<CallExpr>(Call)) {
-    Callee = CE->getDirectCallee();
-    Args = llvm::makeArrayRef(CE->getArgs(), CE->getNumArgs());
-  } else {
-    auto *CCE = cast<CXXConstructExpr>(Call);
-    Callee = CCE->getConstructor();
-    Args = llvm::makeArrayRef(CCE->getArgs(), CCE->getNumArgs());
-  }
-  if (!Callee)
+  CallInfo CI = getCallInfo(Call);
+  if (!CI.Callee)
     return;
 
-  Expr *ObjectArg = nullptr;
-  if (isa<CXXOperatorCallExpr>(Call) && Callee->isCXXInstanceMember()) {
-    ObjectArg = Args[0];
-    Args = Args.slice(1);
-  } else if (auto *MCE = dyn_cast<CXXMemberCallExpr>(Call)) {
-    ObjectArg = MCE->getImplicitObjectArgument();
-  }
-
   auto VisitLifetimeBoundArg = [&](const Decl *D, Expr *Arg) {
     Path.push_back({IndirectLocalPathEntry::LifetimeBoundCall, Arg, D});
     if (Arg->isGLValue())
@@ -6900,14 +6941,14 @@
     Path.pop_back();
   };
 
-  if (ObjectArg && implicitObjectParamIsLifetimeBound(Callee))
-    VisitLifetimeBoundArg(Callee, ObjectArg);
+  if (CI.ObjectArg && implicitObjectParamIsLifetimeBound(CI.Callee))
+    VisitLifetimeBoundArg(CI.Callee, CI.ObjectArg);
 
-  for (unsigned I = 0,
-                N = std::min<unsigned>(Callee->getNumParams(), Args.size());
+  for (unsigned I = 0, N = std::min<unsigned>(CI.Callee->getNumParams(),
+                                              CI.Args.size());
        I != N; ++I) {
-    if (Callee->getParamDecl(I)->hasAttr<LifetimeBoundAttr>())
-      VisitLifetimeBoundArg(Callee->getParamDecl(I), Args[I]);
+    if (CI.Callee->getParamDecl(I)->hasAttr<LifetimeBoundAttr>())
+      VisitLifetimeBoundArg(CI.Callee->getParamDecl(I), CI.Args[I]);
   }
 }
 
Index: clang/lib/Sema/SemaDeclAttr.cpp
===================================================================
--- clang/lib/Sema/SemaDeclAttr.cpp
+++ clang/lib/Sema/SemaDeclAttr.cpp
@@ -22,6 +22,7 @@
 #include "clang/AST/Mangle.h"
 #include "clang/AST/RecursiveASTVisitor.h"
 #include "clang/Basic/CharInfo.h"
+#include "clang/Basic/SourceLocation.h"
 #include "clang/Basic/SourceManager.h"
 #include "clang/Basic/TargetBuiltins.h"
 #include "clang/Basic/TargetInfo.h"
@@ -4513,6 +4514,33 @@
   }
 }
 
+
+static void handleLifetimeContractAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
+  LifetimeContractAttr *LCAttr;
+  if (auto *Existing = D->getAttr<LifetimeContractAttr>())
+    LCAttr = Existing;
+  else {
+    LCAttr = LifetimeContractAttr::Create(S.Context, AL.getArgAsExpr(0), AL);
+    D->addAttr(LCAttr);
+    LCAttr->PreContracts = new (S.Context) LifetimeContracts{};
+    LCAttr->PostContracts = new (S.Context) LifetimeContracts{};
+  }
+
+  using namespace process_lifetime_contracts;
+
+  Optional<SourceRange> ErrorRange;
+  if (AL.getAttributeSpellingListIndex())
+    ErrorRange = fillContractFromExpr(AL.getArgAsExpr(0), *LCAttr->PostContracts);
+  else
+    ErrorRange = fillContractFromExpr(AL.getArgAsExpr(0), *LCAttr->PreContracts);
+
+  if (ErrorRange) {
+    S.Diag(ErrorRange->getBegin(), diag::warn_unsupported_expression)
+        << *ErrorRange;
+    D->dropAttr<LifetimeContractAttr>();
+  }
+}
+
 bool Sema::CheckCallingConvAttr(const ParsedAttr &Attrs, CallingConv &CC,
                                 const FunctionDecl *FD) {
   if (Attrs.isInvalid())
@@ -7266,6 +7294,9 @@
   case ParsedAttr::AT_Pointer:
     handleLifetimeCategoryAttr(S, D, AL);
     break;
+  case ParsedAttr::AT_LifetimeContract:
+    handleLifetimeContractAttr(S, D, AL);
+    break;
   case ParsedAttr::AT_OpenCLKernel:
     handleSimpleAttribute<OpenCLKernelAttr>(S, D, AL);
     break;
@@ -7523,6 +7554,15 @@
     return;
   }
 
+  if (const auto *LCAttr = D->getAttr<LifetimeContractAttr>()) {
+    if (!getDiagnostics().isIgnored(diag::warn_dump_lifetime_contracts,
+                                    D->getBeginLoc())) {
+      if (const auto *FD = dyn_cast<FunctionDecl>(D))
+        Diag(D->getBeginLoc(), diag::warn_dump_lifetime_contracts)
+            << LCAttr->dumpContracts(FD);
+    }
+  }
+
   // FIXME: We should be able to handle this in TableGen as well. It would be
   // good to have a way to specify "these attributes must appear as a group",
   // for these. Additionally, it would be good to have a way to specify "these
Index: clang/lib/AST/LifetimeAttr.cpp
===================================================================
--- /dev/null
+++ clang/lib/AST/LifetimeAttr.cpp
@@ -0,0 +1,157 @@
+//===--- LifetimeAttr.cpp - Lifetime Contract Attributes ------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/AST/LifetimeAttr.h"
+#include "clang/AST/Expr.h"
+#include "clang/AST/ExprCXX.h"
+#include "clang/Basic/SourceLocation.h"
+
+namespace clang {
+
+// FIXME: There are several identifier checks below. 
+//   1. Optimize them to cache the IdentifierInfo.
+//   2. Also check for the `gsl` namespace to avoid collisions.
+namespace process_lifetime_contracts {
+static const Expr *ignoreWrapperASTNodes(const Expr *E) {
+  const Expr *Original;
+  do {
+    Original = E;
+    E = E->IgnoreImplicit();
+    if (const auto *CE = dyn_cast<CXXConstructExpr>(E)) {
+      const auto *Ctor = CE->getConstructor();
+      if (Ctor->getParent()->getName() == "PSet")
+        return CE;
+      E = CE->getArg(0);
+    }
+    if (const auto *MCE = dyn_cast<CXXMemberCallExpr>(E)) {
+      if (llvm::isa_and_nonnull<CXXConversionDecl>(MCE->getDirectCallee()))
+        E = MCE->getImplicitObjectArgument();
+    }
+  } while (E != Original);
+  return E;
+}
+
+// This function can either collect the PSets of the symbols based on a lookup
+// table or just the symbols into a pset if the lookup table is nullptr.
+static ObjectLifetimeSet collectPSet(const Expr *E,
+                                     const LifetimeContracts *Lookup,
+                                     SourceRange *FailRange) {
+  if (const auto *TE = dyn_cast<CXXThisExpr>(E))
+    return ObjectLifetimeSet{LifetimeContractVariable::thisVal(
+        TE->getType()->getPointeeCXXRecordDecl())};
+  if (const auto *DRE = dyn_cast<DeclRefExpr>(E)) {
+    const auto *VD = dyn_cast<VarDecl>(DRE->getDecl());
+    if (!VD) {
+      *FailRange = DRE->getSourceRange();
+      return ObjectLifetimeSet{};
+    }
+    StringRef Name = VD->getName();
+    if (Name == "null")
+      return ObjectLifetimeSet{LifetimeContractVariable::nullVal()};
+    else if (Name == "global")
+      return ObjectLifetimeSet{LifetimeContractVariable::globalVal()};
+    else if (Name == "invalid")
+      return ObjectLifetimeSet{LifetimeContractVariable::invalid()};
+    else if (Name == "Return")
+      return ObjectLifetimeSet{LifetimeContractVariable::returnVal()};
+    else {
+      const auto *PVD = dyn_cast<ParmVarDecl>(VD);
+      if (!PVD) {
+        *FailRange = DRE->getSourceRange();
+        return ObjectLifetimeSet{};
+      }
+      if (Lookup) {
+        auto it = Lookup->find(LifetimeContractVariable::paramBasedVal(PVD));
+        if (it != Lookup->end())
+          return it->second;
+      }
+      return ObjectLifetimeSet{LifetimeContractVariable::paramBasedVal(PVD)};
+    }
+    *FailRange = DRE->getSourceRange();
+    return ObjectLifetimeSet{};
+  }
+  if (const auto *CE = dyn_cast<CallExpr>(E)) {
+    const FunctionDecl *FD = CE->getDirectCallee();
+    if (!FD || !FD->getIdentifier() || FD->getName() != "deref") {
+      *FailRange = CE->getSourceRange();
+      return ObjectLifetimeSet{};
+    }
+    ObjectLifetimeSet Result =
+        collectPSet(ignoreWrapperASTNodes(CE->getArg(0)), Lookup, FailRange);
+    auto VarsCopy = Result;
+    Result.clear();
+    for (auto Var : VarsCopy)
+      Result.insert(Var.deref());
+    return Result;
+  }
+  auto processArgs = [&](ArrayRef<const Expr *> Args) {
+    ObjectLifetimeSet Result;
+    for (const auto *Arg : Args) {
+      ObjectLifetimeSet Elem =
+          collectPSet(ignoreWrapperASTNodes(Arg), Lookup, FailRange);
+      if (Elem.empty())
+        return Elem;
+      Result.insert(Elem.begin(), Elem.end());
+    }
+    return Result;
+  };
+  if (const auto *CE = dyn_cast<CXXConstructExpr>(E))
+    return processArgs({CE->getArgs(), CE->getNumArgs()});
+  if (const auto *IE = dyn_cast<InitListExpr>(E))
+    return processArgs(IE->inits());
+  *FailRange = E->getSourceRange();
+  return ObjectLifetimeSet{};
+}
+
+// We are only interested in identifier names
+// of function calls and variables. The AST, however, has a lot of other
+// information such as casts, termporary objects and so on. They do not have
+// any semantic meaning for contracts so much of the code is just skipping
+// these unwanted nodes. The rest is collecting the identifiers and their
+// hierarchy.
+// Also, the code might be rewritten a more simple way in the future
+// piggybacking this work: https://reviews.llvm.org/rL365355
+llvm::Optional<SourceRange> fillContractFromExpr(const Expr *E,
+                                                 LifetimeContracts &Fill) {
+  const auto *CE = dyn_cast<CallExpr>(E);
+  if (!CE)
+    return E->getSourceRange();
+  do {
+    if (const auto *ULE = dyn_cast<UnresolvedLookupExpr>(CE->getCallee())) {
+      if (ULE->getName().isIdentifier() &&
+          ULE->getName().getAsIdentifierInfo()->getName() == "lifetime")
+        break;
+    }
+    const FunctionDecl *FD = CE->getDirectCallee();
+    if (!FD || !FD->getIdentifier() || FD->getName() != "lifetime")
+      return E->getSourceRange();
+  } while (false);
+
+  const Expr *LHS = ignoreWrapperASTNodes(CE->getArg(0));
+  if (!LHS)
+    return CE->getArg(0)->getSourceRange();
+  const Expr *RHS = ignoreWrapperASTNodes(CE->getArg(1));
+  if (!RHS)
+    return CE->getArg(1)->getSourceRange();
+
+  SourceRange ErrorRange;
+  ObjectLifetimeSet LhsPSet = collectPSet(LHS, nullptr, &ErrorRange);
+  if (LhsPSet.size() != 1)
+    return LHS->getSourceRange();
+  if (ErrorRange.isValid())
+    return ErrorRange;
+
+  LifetimeContractVariable VD = *LhsPSet.begin();
+  ObjectLifetimeSet RhsPSet = collectPSet(RHS, &Fill, &ErrorRange);
+  if (ErrorRange.isValid())
+    return ErrorRange;
+  Fill[VD] = RhsPSet;
+  return {};
+}
+} // namespace process_lifetime_contracts
+} // namespace clang
Index: clang/lib/AST/CMakeLists.txt
===================================================================
--- clang/lib/AST/CMakeLists.txt
+++ clang/lib/AST/CMakeLists.txt
@@ -80,6 +80,7 @@
   ItaniumCXXABI.cpp
   ItaniumMangle.cpp
   JSONNodeDumper.cpp
+  LifetimeAttr.cpp
   Mangle.cpp
   MicrosoftCXXABI.cpp
   MicrosoftMangle.cpp
Index: clang/include/clang/Basic/DiagnosticSemaKinds.td
===================================================================
--- clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -3464,6 +3464,11 @@
   "argument not in expected state; expected '%0', observed '%1'">,
   InGroup<Consumed>, DefaultIgnore;
 
+// Lifetime Analysis
+def warn_unsupported_expression : Warning<"this pre/postcondition is not supported">,
+  InGroup<LifetimeAnalysis>, DefaultIgnore;
+def warn_dump_lifetime_contracts : Warning<"%0">, InGroup<LifetimeDumpContracts>, DefaultIgnore;
+
 // no_sanitize attribute
 def warn_unknown_sanitizer_ignored : Warning<
   "unknown sanitizer '%0' ignored">, InGroup<UnknownSanitizers>;
Index: clang/include/clang/Basic/DiagnosticGroups.td
===================================================================
--- clang/include/clang/Basic/DiagnosticGroups.td
+++ clang/include/clang/Basic/DiagnosticGroups.td
@@ -922,6 +922,9 @@
 // Uniqueness Analysis warnings
 def Consumed       : DiagGroup<"consumed">;
 
+def LifetimeAnalysis : DiagGroup<"lifetime">;
+def LifetimeDumpContracts : DiagGroup<"lifetime-dump-contracts">;
+
 // Note that putting warnings in -Wall will not disable them by default. If a
 // warning should be active _only_ when -Wall is passed in, mark it as
 // DefaultIgnore in addition to putting it here.
Index: clang/include/clang/Basic/Attr.td
===================================================================
--- clang/include/clang/Basic/Attr.td
+++ clang/include/clang/Basic/Attr.td
@@ -2921,6 +2921,49 @@
   let Documentation = [LifetimePointerDocs];
 }
 
+def LifetimeContract : InheritableAttr {
+  let Spellings = [CXX11<"gsl", "pre">, CXX11<"gsl", "post">];
+  let LangOpts = [CPlusPlus]; 
+  let Accessors = [Accessor<"isPre", [CXX11<"gsl", "pre">]>,
+                   Accessor<"isPost", [CXX11<"gsl", "post">]>];
+  let Subjects = SubjectList<[Function]>; // TODO: HasFunctionProto?
+  let Args = [ExprArgument<"ContractExpr">];
+  let LateParsed = 1;
+  let TemplateDependent = 1;
+  let ParseArgumentsAsUnevaluated = 1;
+  let AdditionalMembers = [{
+public:
+  LifetimeContracts *PreContracts;
+  LifetimeContracts *PostContracts;
+
+  static std::string dumpSet(const ObjectLifetimeSet &Set,
+                             const FunctionDecl *FD) {
+    std::string Buffer;
+    llvm::raw_string_ostream OS(Buffer);
+    OS << "{ ";
+    for (const auto &CV : Set)
+      OS << CV.dump(FD) << " ";
+    OS << "}";
+    return OS.str();
+  }
+
+  std::string dumpContracts(const FunctionDecl *FD) const {
+    std::string Buffer;
+    llvm::raw_string_ostream OS(Buffer);
+    OS << "Pre {";
+    for (const auto &P : *PreContracts)
+      OS << " " << P.first.dump(FD) << " -> " << dumpSet(P.second, FD) << ";";
+    OS << " }";
+    OS << "  Post {";
+    for (const auto &P : *PostContracts)
+      OS << " " << P.first.dump(FD) << " -> " << dumpSet(P.second, FD) << ";";
+    OS << " }";
+    return OS.str();
+  }
+  }];
+  let Documentation = [Undocumented]; // FIXME
+}
+
 // Microsoft-related attributes
 
 def MSNoVTable : InheritableAttr, TargetSpecificAttr<TargetMicrosoftCXXABI> {
Index: clang/include/clang/AST/LifetimeAttr.h
===================================================================
--- /dev/null
+++ clang/include/clang/AST/LifetimeAttr.h
@@ -0,0 +1,190 @@
+//===--- LifetimeAttrData.h - Classes for lifetime attributes ---*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+//  This file defines classes that are used by lifetime attributes.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_AST_LIFETIMEATTR_H
+#define LLVM_CLANG_AST_LIFETIMEATTR_H
+
+#include "clang/AST/Decl.h"
+#include "clang/AST/Expr.h"
+#include "llvm/ADT/Optional.h"
+#include <set>
+
+namespace clang {
+
+/// An abstract memory location that participates in defining a lifetime
+/// contract. A lifetime contract constrains the lifetime of a
+/// LifetimeContractVariable to be at least as long as the lifetime of other
+/// LifetimeContractVariables.
+///
+/// The memory locations that we can describe are: return values of a function,
+/// this pointer, any function parameter, an expression based on
+/// function parameters and field selectors, null etc.
+class LifetimeContractVariable {
+public:
+  static LifetimeContractVariable paramBasedVal(const ParmVarDecl *PVD,
+                                                int Deref = 0) {
+    return LifetimeContractVariable(PVD, Deref);
+  }
+  static LifetimeContractVariable thisVal(const RecordDecl *RD) {
+    return LifetimeContractVariable(RD);
+  }
+  static LifetimeContractVariable returnVal() {
+    return LifetimeContractVariable(Return);
+  }
+  static LifetimeContractVariable globalVal() {
+    return LifetimeContractVariable(Global);
+  }
+  static LifetimeContractVariable nullVal() {
+    return LifetimeContractVariable(Null);
+  }
+  static LifetimeContractVariable invalid() {
+    return LifetimeContractVariable(Invalid);
+  }
+
+  bool operator==(const LifetimeContractVariable &O) const {
+    if (Tag != O.Tag)
+      return false;
+    if (FDs != O.FDs)
+      return false;
+    if (Tag == Param)
+      return ParamIdx == O.ParamIdx;
+    if (Tag == This)
+      return RD == O.RD;
+    return true;
+  }
+
+  bool operator!=(const LifetimeContractVariable &O) const {
+    return !(*this == O);
+  }
+
+  bool operator<(const LifetimeContractVariable &O) const {
+    if (Tag != O.Tag)
+      return Tag < O.Tag;
+    if (FDs.size() != O.FDs.size())
+      return FDs.size() < O.FDs.size();
+    if (Tag == Param)
+      if (ParamIdx != O.ParamIdx)
+        return ParamIdx < O.ParamIdx;
+    if (Tag == This)
+      if (RD != O.RD)
+        return std::less<const RecordDecl *>()(RD, O.RD);
+
+    for (auto I = FDs.begin(), J = O.FDs.begin(); I != FDs.end(); ++I, ++J) {
+      if ((*I)->getFieldIndex() != (*J)->getFieldIndex())
+        return (*I)->getFieldIndex() < (*J)->getFieldIndex();
+    }
+    return false;
+  }
+
+  bool isThisPointer() const { return Tag == This; }
+  bool isReturnVal() const { return Tag == Return; }
+  bool isNull() const { return Tag == Null; }
+  bool isInvalid() const { return Tag == Invalid; }
+  bool isGlobal() const { return Tag == Global; }
+
+  const ParmVarDecl *asParmVarDecl(const FunctionDecl *FD) const {
+    return Tag == Param ? FD->getParamDecl(ParamIdx) : nullptr;
+  }
+
+  // Chain of field accesses starting from VD. Types must match.
+  void addFieldRef(const FieldDecl *FD) { FDs.push_back(FD); }
+
+  LifetimeContractVariable &deref(int Num = 1) {
+    while (Num--)
+      FDs.push_back(nullptr);
+    return *this;
+  }
+
+  std::string dump(const FunctionDecl *FD) const {
+    std::string Result;
+    switch (Tag) {
+    case Null:
+      return "null";
+    case Global:
+      return "global";
+    case Invalid:
+      return "invalid";
+    case This:
+      Result = "this";
+      break;
+    case Return:
+      Result = "(return value)";
+      break;
+    case Param:
+      Result = FD->getParamDecl(ParamIdx)->getName().str();
+      break;
+    }
+
+    for (unsigned I = 0; I < FDs.size(); ++I) {
+      if (FDs[I]) {
+        if (I > 0 && !FDs[I - 1])
+          Result = "(" + Result + ")";
+        Result += "." + FDs[I]->getName().str();
+      } else
+        Result.insert(0, 1, '*');
+    }
+    return Result;
+  }
+
+private:
+  union {
+    const RecordDecl *RD;
+    unsigned ParamIdx;
+  };
+
+  enum TagType {
+    Global,
+    Null,
+    Invalid,
+    This,
+    Return,
+    Param,
+  } Tag;
+
+  explicit LifetimeContractVariable(TagType T) : Tag(T) {}
+  explicit LifetimeContractVariable(const RecordDecl *RD) : RD(RD), Tag(This) {}
+  explicit LifetimeContractVariable(const ParmVarDecl *PVD, int Deref)
+      : ParamIdx(PVD->getFunctionScopeIndex()), Tag(Param) {
+    deref(Deref);
+  }
+
+  /// Possibly empty list of fields and deref operations on the base.
+  /// The First entry is the field on base, next entry is the field inside
+  /// there, etc. Null pointers represent a deref operation.
+  llvm::SmallVector<const FieldDecl *, 3> FDs;
+};
+
+/// A lifetime of a pointee of a specific pointer-like C++ object. This
+/// lifetime is represented as a disjunction of different lifetime possibilities
+/// (set elements). Each lifetime possibility is specified by naming another
+/// object that the pointee can point at.
+using ObjectLifetimeSet = std::set<LifetimeContractVariable>;
+
+/// Lifetime constraints for multiple objects. The key of the map is the
+/// pointer-like object, the value is the lifetime of the pointee.
+/// Can be used to describe all lifetime constraints required by a given
+/// function, or all lifetimes inferred at a specific program point etc..
+using LifetimeContracts = std::map<LifetimeContractVariable, ObjectLifetimeSet>;
+
+namespace process_lifetime_contracts {
+/// Converts an AST of a lifetime contract (that is, the `gtl::lifetime(...)
+/// call expression) to a LifetimeContracts object that is used throughout the
+/// lifetime analysis.
+///
+/// If the AST does not describe a valid contract, the source range of the
+/// erroneous part is returned.
+llvm::Optional<SourceRange> fillContractFromExpr(const Expr *E,
+                                                 LifetimeContracts &Fill);
+} // namespace process_lifetime_contracts
+} // namespace clang
+
+#endif // LLVM_CLANG_AST_LIFETIMEATTR_H
Index: clang/include/clang/AST/Attr.h
===================================================================
--- clang/include/clang/AST/Attr.h
+++ clang/include/clang/AST/Attr.h
@@ -16,6 +16,8 @@
 #include "clang/AST/ASTFwd.h"
 #include "clang/AST/AttrIterator.h"
 #include "clang/AST/Decl.h"
+#include "clang/AST/Expr.h"
+#include "clang/AST/LifetimeAttr.h"
 #include "clang/AST/OpenMPClause.h"
 #include "clang/AST/Type.h"
 #include "clang/Basic/AttrKinds.h"
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to