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,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 + +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()(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; } + 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 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; + +/// 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; + +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 fillContractFromExpr(const Expr *E, + LifetimeContracts &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 @@ -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 { 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 @@ -3408,6 +3408,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 @@ -78,6 +78,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,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(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 ObjectLifetimeSet collectPSet(const Expr *E, + const LifetimeContracts *Lookup, + SourceRange *FailRange) { + if (const auto *TE = dyn_cast(E)) + return ObjectLifetimeSet{LifetimeContractVariable::thisVal( + TE->getType()->getPointeeCXXRecordDecl())}; + if (const auto *DRE = dyn_cast(E)) { + const auto *VD = dyn_cast(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(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(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 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(E)) + return processArgs({CE->getArgs(), CE->getNumArgs()}); + if (const auto *IE = dyn_cast(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 fillContractFromExpr(const Expr *E, + LifetimeContracts &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 = 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/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()) + 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 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(); + } +} + 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(S, D, AL); break; @@ -7480,6 +7511,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" @@ -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()) { + // 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) { @@ -6800,32 +6853,38 @@ 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, + LifetimeContractVariable::paramBasedVal( + 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, + LifetimeContractVariable::thisVal(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; } } @@ -6848,28 +6907,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()) @@ -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(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 @@ -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(Ctx, attr), 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 + operator T() const; +} null; + +struct global_t { + template + operator T() const; +} global; + +struct invalid_t { + template + operator T() const; +} invalid; + +struct return_t { + template + operator T() const; +} Return; + +template T &&declval(); + +template +struct PointerTraits { + using DerefType = decltype(*declval()); +}; + +struct PSet { + template + PSet(const T&...); +}; + +template +auto deref(const T &t) -> typename PointerTraits::DerefType; + +template +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 +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)}}