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" 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 + +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()(RD, O.RD); + + for (auto I = FDs.begin(), J = O.FDs.begin(); I != FDs.end(); ++I, ++J) { + if (*I != *J) + return std::less()(*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 FDs; +}; + +using LifetimeContractSet = std::set; +using LifetimeContractMap = std::map; + +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/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 { 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/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, DefaultIgnore; +// Lifetime Analysis +def warn_unsupported_expression : Warning<"this pre/postcondition is not supported">, + InGroup, DefaultIgnore; +def warn_dump_lifetime_contracts : Warning<"%0">, InGroup, DefaultIgnore; + // no_sanitize attribute def warn_unknown_sanitizer_ignored : Warning< "unknown sanitizer '%0' ignored">, InGroup; 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/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(E)) { + const auto *Ctor = CE->getConstructor(); + if (Ctor->getParent()->getName() == "PSet") + return CE; + E = CE->getArg(0); + } + if (const auto *MCE = dyn_cast(E)) { + if (llvm::isa_and_nonnull(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(E)) + return LifetimeContractSet{ + ContractVariable(TE->getType()->getPointeeCXXRecordDecl())}; + if (const auto *DRE = dyn_cast(E)) { + const auto *VD = dyn_cast(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(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(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 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(E)) + return processArgs({CE->getArgs(), CE->getNumArgs()}); + if (const auto *IE = dyn_cast(E)) + return processArgs(IE->inits()); + *FailRange = E->getSourceRange(); + return LifetimeContractSet{}; +} + +SourceRange fillContractFromExpr(const Expr *E, LifetimeContractMap &Fill) { + const auto *CE = dyn_cast(E); + if (!CE) + return E->getSourceRange(); + do { + if (const auto *ULE = dyn_cast(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/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()) + 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(S, D, AL); break; @@ -7440,6 +7469,15 @@ return; } + if (const auto *LCAttr = D->getAttr()) { + if (!getDiagnostics().isIgnored(diag::warn_dump_lifetime_contracts, + D->getBeginLoc())) { + if (const auto *FD = dyn_cast(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/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()) { + // The actual information is stored at primary templates for + // specializations. + if (const auto *FTD = FD->getPrimaryTemplate()) { + assert(FTD->getTemplatedDecl()->hasAttr()); + return FTD->getTemplatedDecl()->getAttr(); + } + return LCAttr; + } + return nullptr; +} + +namespace { +struct CallInfo { + FunctionDecl *Callee = nullptr; + ArrayRef Args; + Expr *ObjectArg = nullptr; +}; +} // namespace + +static CallInfo getCallInfo(Expr *Call) { + CallInfo Info; + if (auto *CE = dyn_cast(Call)) { + Info.Callee = CE->getDirectCallee(); + Info.Args = llvm::makeArrayRef(CE->getArgs(), CE->getNumArgs()); + } else { + auto *CCE = cast(Call); + Info.Callee = CCE->getConstructor(); + Info.Args = llvm::makeArrayRef(CCE->getArgs(), CCE->getNumArgs()); + } + if (isa(Call) && Info.Callee->isCXXInstanceMember()) { + Info.ObjectArg = Info.Args[0]; + Info.Args = Info.Args.slice(1); + } else if (auto *MCE = dyn_cast(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(Call)) { - const auto *MD = cast_or_null(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(Call)) { - FunctionDecl *Callee = OCE->getDirectCallee(); - if (Callee && Callee->isCXXInstanceMember() && - shouldTrackImplicitObjectArg(cast(Callee))) - VisitPointerArg(Callee, OCE->getArg(0), - !Callee->getReturnType()->isReferenceType()); + + bool ReturnsRef = CI.Callee->getReturnType()->isReferenceType(); + + if (auto *CCE = dyn_cast(Call)) { + const CXXRecordDecl *RD = CCE->getConstructor()->getParent(); + if (CI.Args.size() > 0 && RD->hasAttr()) + VisitPointerArg(CI.Callee->getParamDecl(0), CI.Args[0], true); return; - } else if (auto *CE = dyn_cast(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(CI.Callee)) { + if (shouldTrackImplicitObjectArg(MD) || + shouldTrackContract(LCAttr, CI.Callee, MD->getParent())) + VisitPointerArg(MD, CI.ObjectArg, !ReturnsRef); return; } - if (auto *CCE = dyn_cast(Call)) { - const auto *Ctor = CCE->getConstructor(); - const CXXRecordDecl *RD = Ctor->getParent(); - if (CCE->getNumArgs() > 0 && RD->hasAttr()) - VisitPointerArg(Ctor->getParamDecl(0), CCE->getArgs()[0], true); + if (auto *CE = dyn_cast(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 Args; - - if (auto *CE = dyn_cast(Call)) { - Callee = CE->getDirectCallee(); - Args = llvm::makeArrayRef(CE->getArgs(), CE->getNumArgs()); - } else { - auto *CCE = cast(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(Call) && Callee->isCXXInstanceMember()) { - ObjectArg = Args[0]; - Args = Args.slice(1); - } else if (auto *MCE = dyn_cast(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(Callee->getNumParams(), Args.size()); + for (unsigned I = 0, N = std::min(CI.Callee->getNumParams(), + CI.Args.size()); I != N; ++I) { - if (Callee->getParamDecl(I)->hasAttr()) - VisitLifetimeBoundArg(Callee->getParamDecl(I), Args[I]); + if (CI.Callee->getParamDecl(I)->hasAttr()) + VisitLifetimeBoundArg(CI.Callee->getParamDecl(I), CI.Args[I]); } } 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(Ctx, attr), 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 + operator T() const; +} Null; +struct static_t { + template + operator T() const; +} Static; +struct invalid_t { + template + operator T() const; +} Invalid; + +struct return_t { + template + operator T() const; +} Return; + +template +struct PointerTraits { + static auto deref(const T &t) -> decltype(*t); +}; + +template +struct PointerTraits { + static const T &deref(const T *t); +}; + +template +struct PointerTraits { + static const T &deref(const T &t); +}; + +struct PSet { + PSet(...); +}; + +template +auto deref(const T &t) -> decltype(PointerTraits::deref(t)); + +template +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 +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; +}