xazax.hun created this revision.
xazax.hun added reviewers: mgehre, ymandel, gribozavr2, aaron.ballman, rsmith,
rjmccall.
xazax.hun added a project: clang.
Herald added subscribers: Szelethus, Charusso, gamesh411, dkrupp, rnkovacs,
mgorny.
This patch corresponds to this RFC:
http://lists.llvm.org/pipermail/cfe-dev/2019-December/064067.html
This does not cover everything yet, but I wanted to keep the patch small to be
incremental.
Repository:
rG LLVM Github Monorepo
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,134 @@
+// RUN: %clang_cc1 -fsyntax-only -Wlifetime -Wlifetime-dump-contracts -verify %s
+
+namespace gsl {
+struct null_t {
+ template <typename T>
+ operator T() const;
+} Null;
+struct static_t {
+ template <typename T>
+ operator T() const;
+} Static;
+struct invalid_t {
+ template <typename T>
+ operator T() const;
+} Invalid;
+
+struct return_t {
+ template <typename T>
+ operator T() const;
+} Return;
+
+template <typename T>
+struct PointerTraits {
+ static auto deref(const T &t) -> decltype(*t);
+};
+
+template <typename T>
+struct PointerTraits<T *> {
+ static const T &deref(const T *t);
+};
+
+template <typename T>
+struct PointerTraits<T &> {
+ static const T &deref(const T &t);
+};
+
+struct PSet {
+ PSet(...);
+};
+
+template <typename T>
+auto deref(const T &t) -> decltype(PointerTraits<T>::deref(t));
+
+template <typename T>
+bool lifetime(const T &lhs, const PSet &rhs);
+} // namespace gsl
+
+using namespace gsl;
+
+void basic(int *a, int *b) [[gsl::pre(lifetime(b, {a}))]];
+// expected-warning@-1 {{Pre { b -> { a }; }}}
+
+void specials(int *a, int *b, int *c)
+ [[gsl::pre(lifetime(a, {Null}))]]
+ [[gsl::pre(lifetime(b, {Static}))]]
+ [[gsl::pre(lifetime(c, {Invalid}))]];
+// expected-warning@-4 {{Pre { a -> { Null }; b -> { Static }; c -> { Invalid }; }}}
+
+void variadic(int *a, int *b, int *c)
+ [[gsl::pre(lifetime(b, {a, c}))]];
+// expected-warning@-2 {{Pre { b -> { a c }; }}}
+
+void variadic_special(int *a, int *b, int *c)
+ [[gsl::pre(lifetime(b, {a, Null}))]];
+// expected-warning@-2 {{Pre { b -> { Null a }; }}}
+
+void multiple_annotations(int *a, int *b, int *c)
+ [[gsl::pre(lifetime(b, {a}))]]
+ [[gsl::pre(lifetime(c, {a}))]];
+// expected-warning@-3 {{Pre { b -> { a }; c -> { a }; }}}
+
+void multiple_annotations_chained(int *a, int *b, int *c)
+ [[gsl::pre(lifetime(b, {a}))]]
+ [[gsl::pre(lifetime(c, {b}))]];
+// expected-warning@-3 {{Pre { b -> { a }; c -> { a }; }}}
+
+void deref_ptr(int *a, int *b, int **c)
+ [[gsl::pre(lifetime(deref(c), {a}))]];
+// expected-warning@-2 {{Pre { *c -> { a }; }}}
+
+void deref_ptr_pointee(int *a, int *b, int **c)
+ [[gsl::pre(lifetime(a, {deref(c)}))]];
+// expected-warning@-2 {{Pre { a -> { *c }; }}}
+
+void deref_ref(int *a, int *b, int *&c)
+ [[gsl::pre(lifetime(deref(c), {a}))]];
+// expected-warning@-2 {{Pre { *c -> { a }; }}}
+
+void deref_ref_pointee(int *a, int *b, int *&c)
+ [[gsl::pre(lifetime(a, {deref(c)}))]];
+// expected-warning@-2 {{Pre { a -> { *c }; }}}
+
+struct [[gsl::Owner(void)]] X {
+ void f(X **out)
+ [[gsl::post(lifetime(deref(out), {this}))]];
+ // expected-warning@-2 {{Pre { } Post { *out -> { this }; }}}
+ X *operator+(const X& other)
+ [[gsl::post(lifetime(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(lifetime(end, {begin}))]]
+ [[gsl::post(lifetime(Return, {begin}))]];
+// expected-warning@-3 {{Pre { end -> { begin }; } Post { (return value) -> { begin }; }}}
+
+int *find_nontemp(int *begin, int *end, const int &val)
+ [[gsl::pre(lifetime(end, {begin}))]]
+ [[gsl::post(lifetime(Return, {begin}))]];
+// expected-warning@-3 {{Pre { end -> { begin }; } Post { (return value) -> { begin }; }}}
+
+struct [[gsl::Owner(int)]] MyOwner {
+ int *begin()
+ [[gsl::post(lifetime(Return, {this}))]];
+ // expected-warning@-2 {{Pre { } Post { (return value) -> { this }; }}}
+ int *end()
+ [[gsl::post(lifetime(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;
+}
Index: clang/lib/Sema/SemaType.cpp
===================================================================
--- clang/lib/Sema/SemaType.cpp
+++ clang/lib/Sema/SemaType.cpp
@@ -7552,6 +7552,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"
@@ -6778,6 +6779,57 @@
return false;
}
+static bool shouldTrackContract(const LifetimeContractAttr *LCAttr,
+ const FunctionDecl *FD, ContractVariable CV) {
+ if (!LCAttr || !LCAttr->PostContracts)
+ return false;
+ const LifetimeContractMap &PM = *LCAttr->PostContracts;
+ auto It = PM.find(ContractVariable::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) {
@@ -6808,32 +6860,35 @@
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, 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, 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;
}
}
@@ -6856,28 +6911,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())
@@ -6890,14 +6927,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"
@@ -4507,6 +4508,31 @@
}
}
+
+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) LifetimeContractMap{};
+ LCAttr->PostContracts = new (S.Context) LifetimeContractMap{};
+ }
+
+ using namespace process_lifetime_contracts;
+
+ SourceRange ErrorRange;
+ if (AL.getAttributeSpellingListIndex())
+ ErrorRange = fillContractFromExpr(AL.getArgAsExpr(0), *LCAttr->PostContracts);
+ else
+ ErrorRange = fillContractFromExpr(AL.getArgAsExpr(0), *LCAttr->PreContracts);
+
+ if (ErrorRange.isValid())
+ S.Diag(ErrorRange.getBegin(), diag::warn_unsupported_expression)
+ << ErrorRange;
+}
+
bool Sema::CheckCallingConvAttr(const ParsedAttr &Attrs, CallingConv &CC,
const FunctionDecl *FD) {
if (Attrs.isInvalid())
@@ -7194,6 +7220,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;
@@ -7440,6 +7469,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,146 @@
+//===--- 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 {
+// Easier access the attribute's representation.
+
+static const Expr *ignoreReturnValues(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 LifetimeContractSet collectPSet(const Expr *E,
+ const LifetimeContractMap *Lookup,
+ SourceRange *FailRange) {
+ if (const auto *TE = dyn_cast<CXXThisExpr>(E))
+ return LifetimeContractSet{
+ ContractVariable(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 LifetimeContractSet{};
+ }
+ StringRef Name = VD->getName();
+ if (Name == "Null")
+ return LifetimeContractSet{ContractVariable::nullVal()};
+ else if (Name == "Static")
+ return LifetimeContractSet{ContractVariable::staticVal()};
+ else if (Name == "Invalid")
+ return LifetimeContractSet{ContractVariable::invalid()};
+ else if (Name == "Return") // TODO: function name, but overloads?
+ return LifetimeContractSet{ContractVariable::returnVal()};
+ else {
+ const auto *PVD = dyn_cast<ParmVarDecl>(VD);
+ if (!PVD) {
+ *FailRange = DRE->getSourceRange();
+ return LifetimeContractSet{};
+ }
+ if (Lookup) {
+ auto it = Lookup->find(ContractVariable(PVD));
+ if (it != Lookup->end())
+ return it->second;
+ }
+ return LifetimeContractSet{ContractVariable{PVD}};
+ }
+ *FailRange = DRE->getSourceRange();
+ return LifetimeContractSet{};
+ }
+ if (const auto *CE = dyn_cast<CallExpr>(E)) {
+ const FunctionDecl *FD = CE->getDirectCallee();
+ if (!FD || !FD->getIdentifier() || FD->getName() != "deref") {
+ *FailRange = CE->getSourceRange();
+ return LifetimeContractSet{};
+ }
+ LifetimeContractSet Result =
+ collectPSet(ignoreReturnValues(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) {
+ LifetimeContractSet Result;
+ for (const auto *Arg : Args) {
+ LifetimeContractSet Elem =
+ collectPSet(ignoreReturnValues(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 LifetimeContractSet{};
+}
+
+SourceRange fillContractFromExpr(const Expr *E, LifetimeContractMap &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 = ignoreReturnValues(CE->getArg(0));
+ if (!LHS)
+ return CE->getArg(0)->getSourceRange();
+ const Expr *RHS = ignoreReturnValues(CE->getArg(1));
+ if (!RHS)
+ return CE->getArg(1)->getSourceRange();
+
+ SourceRange ErrorRange;
+ LifetimeContractSet LhsPSet = collectPSet(LHS, nullptr, &ErrorRange);
+ if (LhsPSet.size() != 1)
+ return LHS->getSourceRange();
+ if (ErrorRange.isValid())
+ return ErrorRange;
+
+ ContractVariable VD = *LhsPSet.begin();
+ LifetimeContractSet RhsPSet = collectPSet(RHS, &Fill, &ErrorRange);
+ if (ErrorRange.isValid())
+ return ErrorRange;
+ Fill[VD] = RhsPSet;
+ return SourceRange();
+}
+} // namespace process_lifetime_contracts
+} // namespace clang
Index: clang/lib/AST/CMakeLists.txt
===================================================================
--- clang/lib/AST/CMakeLists.txt
+++ clang/lib/AST/CMakeLists.txt
@@ -77,6 +77,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
@@ -3336,6 +3336,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
@@ -2889,6 +2889,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<"Expr">];
+ let LateParsed = 1;
+ let TemplateDependent = 1;
+ let ParseArgumentsAsUnevaluated = 1;
+ let AdditionalMembers = [{
+public:
+ LifetimeContractMap *PreContracts;
+ LifetimeContractMap *PostContracts;
+
+ static std::string dumpSet(const LifetimeContractSet &PSet,
+ const FunctionDecl *FD) {
+ std::string Buffer;
+ llvm::raw_string_ostream OS(Buffer);
+ OS << "{ ";
+ for (const auto &CV : PSet)
+ 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,159 @@
+//===--- 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 <set>
+
+namespace clang {
+
+/// This represents an abstract memory location that is used in the lifetime
+/// contract representation.
+class ContractVariable {
+public:
+ ContractVariable(const ParmVarDecl *PVD, int Deref = 0)
+ : ParamIdx(PVD->getFunctionScopeIndex()), Tag(Param) {
+ deref(Deref);
+ }
+
+ ContractVariable(const RecordDecl *RD) : RD(RD), Tag(This) {}
+
+ static ContractVariable returnVal() { return ContractVariable(Return); }
+ static ContractVariable staticVal() { return ContractVariable(Static); }
+ static ContractVariable nullVal() { return ContractVariable(Null); }
+ static ContractVariable invalid() { return ContractVariable(Invalid); }
+
+ bool operator==(const ContractVariable &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 ContractVariable &O) const { return !(*this == O); }
+
+ bool operator<(const ContractVariable &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; }
+
+ const ParmVarDecl *asParmVarDecl(const FunctionDecl *FD) const {
+ return Tag == Param ? FD->getParamDecl(ParamIdx) : nullptr;
+ }
+
+ bool isReturnVal() const { return Tag == Return; }
+
+ // Chain of field accesses starting from VD. Types must match.
+ void addFieldRef(const FieldDecl *FD) { FDs.push_back(FD); }
+
+ ContractVariable &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 Static:
+ return "Static";
+ 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 {
+ Static,
+ Null,
+ Invalid,
+ This,
+ Return,
+ Param,
+ } Tag;
+
+ ContractVariable(TagType T) : Tag(T) {}
+
+ /// 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;
+};
+
+using LifetimeContractSet = std::set<ContractVariable>;
+using LifetimeContractMap = std::map<ContractVariable, LifetimeContractSet>;
+
+namespace process_lifetime_contracts {
+// This function and the callees are have the sole purpose of matching the
+// AST that describes the contracts. 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
+SourceRange fillContractFromExpr(const Expr *E, LifetimeContractMap &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