xazax.hun updated this revision to Diff 240335.
xazax.hun added a comment.
- 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/Sema/attr-psets-annotation.cpp
Index: clang/test/Sema/attr-psets-annotation.cpp
===================================================================
--- /dev/null
+++ clang/test/Sema/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
@@ -7724,6 +7724,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/TargetInfo.h"
@@ -6770,6 +6771,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) {
@@ -6800,32 +6853,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;
}
}
@@ -6848,28 +6907,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())
@@ -6882,14 +6923,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"
@@ -4511,6 +4512,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())
@@ -7227,6 +7255,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;
@@ -7480,6 +7511,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,153 @@
+//===--- SemaType.cpp - Semantic Analysis for Types -----------------------===//
+//
+// 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 {
+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
@@ -78,6 +78,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
@@ -3408,6 +3408,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
@@ -906,6 +906,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
@@ -2899,6 +2899,48 @@
let Documentation = [LifetimePointerDocs];
}
+def LifetimeContract : InheritableAttr {
+ let Spellings = [CXX11<"gsl", "pre">, CXX11<"gsl", "post">];
+ 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 lifetime of a
+/// LifetimeContractVariable to be at least as big as the lifetime of other
+/// LifetimeContractVariables.
+///
+/// The memory locations that we can describe are: return values of a function,
+/// this pointer, any function parameter, a "drilldown" expression based on
+/// function parameters, 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 != *J)
+ return std::less<const FieldDecl *>()(*I, *J);
+ }
+ 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();
+ break;
+ }
+
+ for (unsigned I = 0; I < FDs.size(); ++I) {
+ if (FDs[I]) {
+ if (I > 0 && !FDs[I - 1])
+ Result = "(" + Result + ")";
+ Result += "." + std::string(FDs[I]->getName());
+ } else
+ Result.insert(0, 1, '*');
+ }
+ return Result;
+ }
+
+private:
+ union {
+ const RecordDecl *RD;
+ unsigned ParamIdx;
+ };
+
+ enum TagType {
+ Global,
+ Null,
+ Invalid,
+ This,
+ Return,
+ Param,
+ } Tag;
+
+ LifetimeContractVariable(TagType T) : Tag(T) {}
+ LifetimeContractVariable(const RecordDecl *RD) : RD(RD), Tag(This) {}
+ 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
@@ -17,6 +17,7 @@
#include "clang/AST/AttrIterator.h"
#include "clang/AST/Decl.h"
#include "clang/AST/Expr.h"
+#include "clang/AST/LifetimeAttr.h"
#include "clang/AST/Type.h"
#include "clang/Basic/AttrKinds.h"
#include "clang/Basic/AttributeCommonInfo.h"
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits