Index: include/clang/AST/AttrContract.h =================================================================== --- /dev/null +++ include/clang/AST/AttrContract.h @@ -0,0 +1,125 @@ +//===--- AttrContract.h - Classes for representing contracts ----*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file defines the ContractAttr interface and subclasses. +// Note that these are entirely independent of the Attr interface; their only +// real resemblance is in the name. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_AST_ATTRCONTRACT_H +#define LLVM_CLANG_AST_ATTRCONTRACT_H + +#include "clang/Basic/Contract.h" +#include "clang/Basic/SourceLocation.h" + +namespace clang { + class Expr; + class VarDecl; + struct PrintingPolicy; + +/// Common base class for all contract attributes. +class ContractAttr { + /// Predicate that is intended to hold on evaluation of the contract. + Expr *Predicate; + + /// Range that covers the entire attribute, including the square brackets. + SourceRange Range; + + /// Location of the contract kind token. + SourceLocation KindLoc; + + /// Location of the (optional) contract level token. + /// This will be invalid if a level wasn't specified. + SourceLocation LevelLoc; + + /// Location of the colon that denotes the start of the predicate. + SourceLocation ColonLoc; + + ContractKind Kind; + ContractLevel Level; + +protected: + ContractAttr(SourceRange Range, ContractKind Kind, SourceLocation KindLoc, + ContractLevel Level, SourceLocation LevelLoc, + SourceLocation ColonLoc, Expr *Predicate) + : Predicate(Predicate), Range(Range), KindLoc(KindLoc), + LevelLoc(LevelLoc), ColonLoc(ColonLoc), Kind(Kind), Level(Level) {} + +public: + ContractKind getKind() const { return Kind; } + ContractLevel getLevel() const { return Level; } + + Expr *getPredicate() const { return Predicate; } + void setPredicate(Expr *NewPredicate) { Predicate = NewPredicate; } + + SourceRange getRange() const { return Range; } + SourceLocation getBeginLoc() const { return Range.getBegin(); } + SourceLocation getEndLoc() const { return Range.getEnd(); } + SourceLocation getKindLoc() const { return KindLoc; } + SourceLocation getLevelLoc() const { return LevelLoc; } + SourceLocation getColonLoc() const { return ColonLoc; } + + /// Pretty print the contract into its source language syntax. + void printPretty(raw_ostream &OS, const PrintingPolicy &Policy) const; +}; + +class ExpectsContract final : public ContractAttr { +public: + ExpectsContract(SourceRange Range, SourceLocation KindLoc, + ContractLevel Level, SourceLocation LevelLoc, + SourceLocation ColonLoc, Expr *Predicate) + : ContractAttr(Range, ContractKind::Expects, KindLoc, Level, LevelLoc, + ColonLoc, Predicate) {} + + static bool classof(const ContractAttr *C) { + return classofKind(C->getKind()); + } + static bool classofKind(ContractKind K) { return K == ContractKind::Expects; } +}; + +class EnsuresContract final : public ContractAttr { + /// Declaration of the variable that represents the function result. + /// This will be null if the optional identifier wasn't specified. + VarDecl *ResultVar; + +public: + EnsuresContract(SourceRange Range, SourceLocation KindLoc, + ContractLevel Level, SourceLocation LevelLoc, + VarDecl *ResultVar, SourceLocation ColonLoc, Expr *Predicate) + : ContractAttr(Range, ContractKind::Ensures, KindLoc, Level, LevelLoc, + ColonLoc, Predicate), + ResultVar(ResultVar) {} + + VarDecl *getResultVar() const { return ResultVar; } + void setResultVar(VarDecl *NewResultVar) { ResultVar = NewResultVar; } + + static bool classof(const ContractAttr *C) { + return classofKind(C->getKind()); + } + static bool classofKind(ContractKind K) { return K == ContractKind::Ensures; } +}; + +class AssertContract final : public ContractAttr { +public: + AssertContract(SourceRange Range, SourceLocation KindLoc, + ContractLevel Level, SourceLocation LevelLoc, + SourceLocation ColonLoc, Expr *Predicate) + : ContractAttr(Range, ContractKind::Assert, KindLoc, Level, LevelLoc, + ColonLoc, Predicate) {} + + static bool classof(const ContractAttr *C) { + return classofKind(C->getKind()); + } + static bool classofKind(ContractKind K) { return K == ContractKind::Assert; } +}; + +} // end namespace clang + +#endif // LLVM_CLANG_AST_ATTRCONTRACT_H Index: include/clang/AST/Decl.h =================================================================== --- include/clang/AST/Decl.h +++ include/clang/AST/Decl.h @@ -54,8 +54,11 @@ struct ASTTemplateArgumentListInfo; class Attr; class CompoundStmt; +class ContractAttr; class DependentFunctionTemplateSpecializationInfo; +class EnsuresContract; class EnumDecl; +class ExpectsContract; class Expr; class FunctionTemplateDecl; class FunctionTemplateSpecializationInfo; @@ -965,6 +968,10 @@ /// init-capture. unsigned IsInitCapture : 1; + /// Whether this variable represents the result of a function call for use + /// in a postcondition contract. + unsigned IsContractFunctionResult : 1; + /// Whether this local extern variable's previous declaration was /// declared in the same block scope. This controls whether we should merge /// the type of this declaration with its previous declaration. @@ -1403,6 +1410,18 @@ NonParmVarDeclBits.IsInitCapture = IC; } + /// Whether this variable represents the result of a function call for use in + /// a postcondition contract. + bool isContractFunctionResult() const { + return isa(this) + ? false + : NonParmVarDeclBits.IsContractFunctionResult; + } + void setContractFunctionResult(bool IsRes) { + assert(!isa(this)); + NonParmVarDeclBits.IsContractFunctionResult = IsRes; + } + /// Whether this local extern variable declaration's previous declaration /// was declared in the same block scope. Only correct in C++. bool isPreviousDeclInSameBlockScope() const { @@ -1742,6 +1761,9 @@ LazyDeclStmtPtr Body; + ContractAttr **Contracts = nullptr; + unsigned NumContracts = 0; + unsigned ODRHash; /// End part of this FunctionDecl's source range. @@ -1810,6 +1832,7 @@ TemplateSpecializationKind TSK); void setParams(ASTContext &C, ArrayRef NewParamInfo); + void setContracts(ASTContext &C, ArrayRef NewContracts); // This is unfortunately needed because ASTDeclWriter::VisitFunctionDecl // need to access this bit but we want to avoid making ASTDeclWriter @@ -2273,6 +2296,33 @@ /// parameters have default arguments (in C++). unsigned getMinRequiredArguments() const; + /// Get the contract attributes attached to this function. + /// @{ + ArrayRef contracts() const { + return {Contracts, NumContracts}; + } + MutableArrayRef contracts() { + return {Contracts, NumContracts}; + } + /// @} + + bool hasInheritedContracts() const { + return FunctionDeclBits.HasInheritedContracts; + } + void setInheritedContracts(bool Inherited = true) { + FunctionDeclBits.HasInheritedContracts = Inherited; + } + + /// Set the list of contracts attributes attached to this function. + /// This should be called at most once per FunctionDecl. + /// + /// \pre contracts() == None; + /// \pre none_of(NewContracts, isa); + /// \post contracts() == NewContracts; + void setContracts(ArrayRef NewContracts) { + setContracts(getASTContext(), NewContracts); + } + QualType getReturnType() const { assert(getType()->getAs() && "Expected a FunctionType!"); return getType()->getAs()->getReturnType(); Index: include/clang/AST/DeclBase.h =================================================================== --- include/clang/AST/DeclBase.h +++ include/clang/AST/DeclBase.h @@ -1530,10 +1530,14 @@ /// Store the ODRHash after first calculation. uint64_t HasODRHash : 1; + + /// Indicates if the function's contract conditions were inherited from a + /// previous declaration as opposed to explicitly written in the source. + uint64_t HasInheritedContracts : 1; }; /// Number of non-inherited bits in FunctionDeclBitfields. - enum { NumFunctionDeclBits = 25 }; + enum { NumFunctionDeclBits = 26 }; /// Stores the bits used by CXXConstructorDecl. If modified /// NumCXXConstructorDeclBits and the accessor @@ -1545,12 +1549,12 @@ /// For the bits in FunctionDeclBitfields. uint64_t : NumFunctionDeclBits; - /// 25 bits to fit in the remaining availible space. + /// 24 bits to fit in the remaining availible space. /// Note that this makes CXXConstructorDeclBitfields take /// exactly 64 bits and thus the width of NumCtorInitializers /// will need to be shrunk if some bit is added to NumDeclContextBitfields, /// NumFunctionDeclBitfields or CXXConstructorDeclBitfields. - uint64_t NumCtorInitializers : 25; + uint64_t NumCtorInitializers : 24; uint64_t IsInheritingConstructor : 1; }; Index: include/clang/AST/Stmt.h =================================================================== --- include/clang/AST/Stmt.h +++ include/clang/AST/Stmt.h @@ -42,6 +42,7 @@ namespace clang { +class AssertContract; class ASTContext; class Attr; class CapturedDecl; @@ -587,7 +588,10 @@ /// NullStmt - This is the null statement ";": C99 6.8.3p3. /// -class NullStmt final : public Stmt { +class NullStmt final + : public Stmt, + private llvm::TrailingObjects { + SourceLocation SemiLoc; /// True if the null statement was preceded by an empty macro, e.g: @@ -595,27 +599,39 @@ /// #define CALL(x) /// CALL(0); /// @endcode - bool HasLeadingEmptyMacro; + unsigned HasLeadingEmptyMacro : 1; + + /// Number of assertion contracts attached to this statement. + unsigned NumAssertions : 31; - NullStmt(SourceLocation L, bool hasLeadingEmptyMacro) - : Stmt(NullStmtClass), SemiLoc(L), - HasLeadingEmptyMacro(hasLeadingEmptyMacro) {} + NullStmt(SourceLocation L, bool hasLeadingEmptyMacro, + ArrayRef Assertions); - explicit NullStmt(EmptyShell Empty) - : Stmt(NullStmtClass, Empty), HasLeadingEmptyMacro(false) {} + NullStmt(EmptyShell Empty, unsigned NumAssertions); public: friend class ASTStmtReader; friend class ASTStmtWriter; + friend TrailingObjects; static NullStmt *Create(const ASTContext &C, SourceLocation L, - bool hasLeadingEmptyMacro = false) { - return new (C) NullStmt(L, hasLeadingEmptyMacro); - } + bool hasLeadingEmptyMacro = false, + ArrayRef Assertions = None); /// Build an empty null statement. - static NullStmt *CreateEmpty(const ASTContext &C) { - return new (C) NullStmt(EmptyShell{}); + static NullStmt *CreateEmpty(const ASTContext &C, unsigned NumAssertions = 0); + + using assertion_iterator = AssertContract *const *; + using assertion_range = llvm::iterator_range; + + assertion_iterator assertions_begin() const { + return getTrailingObjects(); + } + assertion_iterator assertions_end() const { + return assertions_begin() + NumAssertions; + } + assertion_range assertions() const { + return {assertions_begin(), assertions_end()}; } SourceLocation getSemiLoc() const { return SemiLoc; } @@ -627,7 +643,7 @@ "Use getBeginLoc instead") { return getBeginLoc(); } - SourceLocation getBeginLoc() const LLVM_READONLY { return SemiLoc; } + SourceLocation getBeginLoc() const LLVM_READONLY; LLVM_ATTRIBUTE_DEPRECATED(SourceLocation getLocEnd() const LLVM_READONLY, "Use getEndLoc instead") { return getEndLoc(); Index: include/clang/Basic/Contract.h =================================================================== --- /dev/null +++ include/clang/Basic/Contract.h @@ -0,0 +1,38 @@ +//===--- Contract.h - Types for C++ Contracts ------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_BASIC_CONTRACT_H +#define LLVM_CLANG_BASIC_CONTRACT_H + +#include + +namespace clang { + +enum class ContractKind : std::uint8_t { + Expects, ///< Precondition + Ensures, ///< Postcondition + Assert, ///< Assertion +}; + +enum class ContractLevel : std::uint8_t { + /// The cost of run-time checking is assumed to be small (or at least not + /// expensive) compared to the cost of executing the function. + Default, + + /// The cost of run-time checking is assumed to be large (or at least + /// significant) compared to the cost of executing the function. + Audit, + + /// Formal comments that are not evaluated at run-time. + Axiom, +}; + +} // end namespace clang + +#endif // LLVM_CLANG_BASIC_CONTRACT_H Index: include/clang/Basic/DiagnosticASTKinds.td =================================================================== --- include/clang/Basic/DiagnosticASTKinds.td +++ include/clang/Basic/DiagnosticASTKinds.td @@ -177,6 +177,8 @@ "size to copy (%4) is not a multiple of size of element type %3 (%5)|" "source is not a contiguous array of at least %4 elements of type %3|" "destination is not a contiguous array of at least %4 elements of type %3}2">; +def note_constexpr_contract_failed : Note< + "%select{precondition|postcondition|assertion}0 %select{failed|is unsatisfiable}1">; def warn_integer_constant_overflow : Warning< "overflow in expression; result is %0 with type %1">, Index: include/clang/Basic/DiagnosticParseKinds.td =================================================================== --- include/clang/Basic/DiagnosticParseKinds.td +++ include/clang/Basic/DiagnosticParseKinds.td @@ -202,6 +202,8 @@ def err_expected_semi_after_expr : Error<"expected ';' after expression">; def err_extraneous_token_before_semi : Error<"extraneous '%0' before ';'">; +def err_expected_semi_after_assert_contract : Error< + "expected ';' after assertion">; def err_expected_semi_after_method_proto : Error< "expected ';' after method prototype">; def err_expected_semi_after_namespace_name : Error< @@ -620,6 +622,22 @@ def err_ms_property_initializer : Error< "property declaration cannot have an in-class initializer">; +// Contract Attributes +def warn_cxx17_compat_contract_attributes: Warning< + "contract attributes are incompatible with C++ standards before C++2a">, + InGroup, DefaultIgnore; +def ext_contract_attributes: ExtWarn< + "contract attributes are a C++2a extension">, InGroup; +def err_contract_attribute_needs_separate_list : Error< + "contract attributes must be the only member of their attribute list">; +def err_contract_attribute_misplaced : Error< + "'%select{expects|ensures|assert}0' attribute is only allowed on " + "%select{a function type|a function type|an empty statement}0">; +def err_unknown_contract_level : Error< + "unknown contract level %0">; +def err_unknown_contract_level_suggest : Error< + "unknown contract level %0; did you mean %1?">; + /// C++ Templates def err_expected_template : Error<"expected template">; def err_unknown_template_name : Error< Index: include/clang/Basic/DiagnosticSemaKinds.td =================================================================== --- include/clang/Basic/DiagnosticSemaKinds.td +++ include/clang/Basic/DiagnosticSemaKinds.td @@ -9440,4 +9440,31 @@ "member '%2' is missing|" "the type is not trivially copyable|" "the type does not have the expected form}1">; + +// Contract Attributes +def err_friend_decl_with_contracts_redeclared : Error< + "friend declaration specifying contract conditions must be the only declaration">; +def err_friend_decl_with_contracts_must_be_def : Error< + "friend declaration specifying contract conditions must be a definition">; +def err_contract_attribute_nonfunc : Error< + "contract conditions can only be specified on a function declaration">; +def err_contract_result_void : Error< + "cannot use a result identifier on a function that doesn't return a value">; +def err_incompatible_contracts_on_redecl : Error< + "redeclaration of %0 with different contract conditions; either specify the " + "same contracts or omit them entirely">; +def select_contract_type : TextSubstitution< + "%select{a regular|an auditing|an axiomatic}1 %select{pre|post}0condition">; +def note_redecl_contract_type : Note< + "the %ordinal0 contract is %sub{select_contract_type}1,2">; +def note_orig_contract_type : Note< + "whereas on the original declaration it's %sub{select_contract_type}0,1">; +def note_redecl_contract_pred : Note< + "the predicate of the %ordinal0 contract">; +def note_orig_contract_pred : Note< + "is different from the original declaration here">; +def note_missing_contracts : Note< + "%select{original |re}0declaration has %plural{1:this|:these %1}1 " + "additional contract%s1">; + } // end of sema component. Index: include/clang/Parse/Parser.h =================================================================== --- include/clang/Parse/Parser.h +++ include/clang/Parse/Parser.h @@ -16,6 +16,7 @@ #include "clang/AST/Availability.h" #include "clang/Basic/BitmaskEnum.h" +#include "clang/Basic/Contract.h" #include "clang/Basic/OpenMPKinds.h" #include "clang/Basic/OperatorPrecedence.h" #include "clang/Basic/Specifiers.h" @@ -151,6 +152,16 @@ mutable IdentifierInfo *Ident_GNU_final; mutable IdentifierInfo *Ident_override; + // Contract kind identifiers. + mutable IdentifierInfo *Ident_expects; + mutable IdentifierInfo *Ident_ensures; + mutable IdentifierInfo *Ident_assert; + + // Contract level identifiers. + // "default" is a regular keyword, so that isn't needed here. + mutable IdentifierInfo *Ident_audit; + mutable IdentifierInfo *Ident_axiom; + // C++ type trait keywords that can be reverted to identifiers and still be // used as type traits. llvm::SmallDenseMap RevertibleTypeTraits; @@ -1064,6 +1075,8 @@ void SkipMalformedDecl(); private: + class ContractsParseState; + //===--------------------------------------------------------------------===// // Lexing and parsing of C++ inline methods. @@ -1205,6 +1218,10 @@ /// The set of tokens that make up an exception-specification that /// has not yet been parsed. std::unique_ptr ExceptionSpecTokens; + + /// The sequence of tokens that make up the contract attributes that have + /// not yet been parsed. + std::unique_ptr ContractTokens; }; /// LateParsedMemberInitializer - An initializer for a non-static class data @@ -1870,7 +1887,9 @@ StmtVector &Stmts, AllowedConstructsKind Allowed, SourceLocation *TrailingElseLoc, - ParsedAttributesWithRange &Attrs); + ParsedAttributesWithRange &Attrs, + ContractsParseState *Contracts = + nullptr); StmtResult ParseExprStatement(); StmtResult ParseLabeledStatement(ParsedAttributesWithRange &attrs); StmtResult ParseCaseStatement(bool MissingCase = false, @@ -2402,34 +2421,40 @@ IdentifierInfo *ScopeName, SourceLocation ScopeLoc, ParsedAttr::Syntax Syntax); - void MaybeParseCXX11Attributes(Declarator &D) { + void MaybeParseCXX11Attributes(Declarator &D, + ContractsParseState *Contracts = nullptr) { if (standardAttributesAllowed() && isCXX11AttributeSpecifier()) { ParsedAttributesWithRange attrs(AttrFactory); SourceLocation endLoc; - ParseCXX11Attributes(attrs, &endLoc); + ParseCXX11Attributes(attrs, &endLoc, Contracts); D.takeAttributes(attrs, endLoc); } } void MaybeParseCXX11Attributes(ParsedAttributes &attrs, - SourceLocation *endLoc = nullptr) { + SourceLocation *endLoc = nullptr, + ContractsParseState *Contracts = nullptr) { if (standardAttributesAllowed() && isCXX11AttributeSpecifier()) { ParsedAttributesWithRange attrsWithRange(AttrFactory); - ParseCXX11Attributes(attrsWithRange, endLoc); + ParseCXX11Attributes(attrsWithRange, endLoc, Contracts); attrs.takeAllFrom(attrsWithRange); } } void MaybeParseCXX11Attributes(ParsedAttributesWithRange &attrs, SourceLocation *endLoc = nullptr, - bool OuterMightBeMessageSend = false) { + bool OuterMightBeMessageSend = false, + ContractsParseState *Contracts = nullptr) { if (standardAttributesAllowed() && - isCXX11AttributeSpecifier(false, OuterMightBeMessageSend)) - ParseCXX11Attributes(attrs, endLoc); + isCXX11AttributeSpecifier(false, OuterMightBeMessageSend)) + ParseCXX11Attributes(attrs, endLoc, Contracts); } void ParseCXX11AttributeSpecifier(ParsedAttributes &attrs, - SourceLocation *EndLoc = nullptr); + SourceLocation *EndLoc = nullptr, + ContractsParseState *Contracts = nullptr); void ParseCXX11Attributes(ParsedAttributesWithRange &attrs, - SourceLocation *EndLoc = nullptr); + SourceLocation *EndLoc = nullptr, + ContractsParseState *Contracts = nullptr); + /// Parses a C++11 (or C2x)-style attribute argument list. Returns true /// if this results in adding an attribute to the ParsedAttributes list. bool ParseCXX11AttributeArgs(IdentifierInfo *AttrName, @@ -2440,6 +2465,78 @@ IdentifierInfo *TryParseCXX11AttributeIdentifier(SourceLocation &Loc); + class ContractsParseState { + union { + /// List of successfully parsed contracts. + SmallVector Parsed; + + /// Tokens that make up the contract attributes. + CachedTokens CachedToks; + }; + + /// Return type of the function that the contract applies to, if known. + QualType ResultType; + + /// Whether the contracts we're parsing apply to a function or a statement. + bool IsParsingFunctionDecl; + + /// Whether parsing of the contracts should be delayed. + /// This corresponds to the active member of the union. + bool Delayed; + + public: + // Tags that can be used to select a constructor. + struct ParsingFunctionDecl {}; + struct ParsingStmt {}; + + ContractsParseState(ParsingFunctionDecl, bool Delayed = false, + QualType ResultType = QualType()) + : ResultType(ResultType), IsParsingFunctionDecl(true), + Delayed(Delayed) { + // Construct the appropriate union member. + if (Delayed) + new (&CachedToks) decltype(CachedToks)(); + else + new (&Parsed) decltype(Parsed)(); + } + + ContractsParseState(ParsingStmt) + : Parsed(), IsParsingFunctionDecl(false), Delayed(false) {} + + ~ContractsParseState() { + // Destruct the appropriate union member. + if (Delayed) + CachedToks.~SmallVector(); + else + Parsed.~SmallVector(); + } + + bool allowsKind(ContractKind Kind) const { + switch (Kind) { + case ContractKind::Expects: return IsParsingFunctionDecl; + case ContractKind::Ensures: return IsParsingFunctionDecl; + case ContractKind::Assert: return !IsParsingFunctionDecl; + } + } + + QualType getResultType() const { return ResultType; } + bool delayParsing() const { return Delayed; } + + decltype(Parsed) &getParsedList() & { + assert(!delayParsing() && "Wrong list!"); + return Parsed; + } + decltype(CachedToks) &getCachedTokens() & { + assert(delayParsing() && "Wrong list!"); + return CachedToks; + } + }; + Optional isContractKind(const Token &Tok) const; + Optional isContractLevel(const Token &Tok) const; + bool UserProbablyMeantContractLevel(ContractKind Kind); + void ParseContractAttributeSpecifier(ContractsParseState *Contracts); + bool MaybeDiagnoseAssertWithoutSemi(ContractsParseState *Contracts); + void MaybeParseMicrosoftAttributes(ParsedAttributes &attrs, SourceLocation *endLoc = nullptr) { if (getLangOpts().MicrosoftExt && Tok.is(tok::l_square)) Index: include/clang/Sema/DeclSpec.h =================================================================== --- include/clang/Sema/DeclSpec.h +++ include/clang/Sema/DeclSpec.h @@ -1298,6 +1298,11 @@ /// number of declarations in the function prototype. unsigned NumExceptionsOrDecls; + /// Number of parsed contract attributes attached to this declarator. + /// If this is zero, then we \e might be storing cached tokens for delayed + /// parsing; the cache pointer may be null if there're no contracts at all. + unsigned NumParsedContracts; + /// The location of the ref-qualifier, if any. /// /// If this is an invalid location, there is no ref-qualifier. @@ -1353,6 +1358,14 @@ NamedDecl **DeclsInPrototype; }; + union { + /// Pointer to a new[]'d array of contracts. + ContractAttr **ParsedContracts; + + /// Pointer to a new'd cache of contract tokens for late parsing. + CachedTokens *ContractTokens; + }; + /// If HasTrailingReturnType is true, this is the trailing return /// type specified. UnionParsedType TrailingReturnType; @@ -1386,6 +1399,10 @@ delete[] DeclsInPrototype; break; } + if (NumParsedContracts) + delete[] ParsedContracts; + else if (ContractTokens) + delete ContractTokens; } /// isKNRPrototype - Return true if this is a K&R style identifier list, @@ -1609,7 +1626,10 @@ SourceLocation LocalRangeEnd, Declarator &TheDeclarator, TypeResult TrailingReturnType = - TypeResult()); + TypeResult(), + ArrayRef ParsedContracts = + None, + CachedTokens *ContractTokens = nullptr); /// Return a DeclaratorChunk for a block. static DeclaratorChunk getBlockPointer(unsigned TypeQuals, Index: include/clang/Sema/DelayedDiagnostic.h =================================================================== --- include/clang/Sema/DelayedDiagnostic.h +++ include/clang/Sema/DelayedDiagnostic.h @@ -148,12 +148,14 @@ bool ObjCPropertyAccess); static DelayedDiagnostic makeAccess(SourceLocation Loc, - const AccessedEntity &Entity) { + const AccessedEntity &Entity, + bool FromContractCondition) { DelayedDiagnostic DD; DD.Kind = Access; DD.Triggered = false; DD.Loc = Loc; - new (&DD.getAccessData()) AccessedEntity(Entity); + new (DD.AccessControlData.AccessData) AccessedEntity(Entity); + DD.AccessControlData.FromContractCondition = FromContractCondition; return DD; } @@ -172,12 +174,20 @@ } AccessedEntity &getAccessData() { - assert(Kind == Access && "Not an access diagnostic."); - return *reinterpret_cast(AccessData); + return const_cast( + const_cast(this)->getAccessData()); } const AccessedEntity &getAccessData() const { assert(Kind == Access && "Not an access diagnostic."); - return *reinterpret_cast(AccessData); + return *reinterpret_cast( + AccessControlData.AccessData); + } + + /// Whether the entity is being accessed from the predicate of a contract + /// condition. + bool isFromContractCondition() const { + assert(Kind == Access && "Not an access diagnostic."); + return AccessControlData.FromContractCondition; } const NamedDecl *getAvailabilityReferringDecl() const { @@ -237,6 +247,11 @@ } private: + struct ACD { + alignas(AccessedEntity) char AccessData[sizeof(AccessedEntity)]; + bool FromContractCondition; + }; + struct AD { const NamedDecl *ReferringDecl; const NamedDecl *OffendingDecl; @@ -257,11 +272,9 @@ }; union { + struct ACD AccessControlData; struct AD AvailabilityData; struct FTD ForbiddenTypeData; - - /// Access control. - char AccessData[sizeof(AccessedEntity)]; }; }; Index: include/clang/Sema/Ownership.h =================================================================== --- include/clang/Sema/Ownership.h +++ include/clang/Sema/Ownership.h @@ -29,6 +29,7 @@ namespace clang { +class ContractAttr; class CXXBaseSpecifier; class CXXCtorInitializer; class Decl; @@ -263,12 +264,16 @@ template<> struct IsResultPtrLowBitFree { static const bool value = true; }; + template<> struct IsResultPtrLowBitFree { + static const bool value = true; + }; using ExprResult = ActionResult; using StmtResult = ActionResult; using TypeResult = ActionResult; using BaseResult = ActionResult; using MemInitResult = ActionResult; + using ContractResult = ActionResult; using DeclResult = ActionResult; using ParsedTemplateTy = OpaquePtr; Index: include/clang/Sema/Scope.h =================================================================== --- include/clang/Sema/Scope.h +++ include/clang/Sema/Scope.h @@ -131,6 +131,9 @@ /// We are between inheritance colon and the real class/struct definition scope. ClassInheritanceScope = 0x800000, + + /// We are between the opening and closing bracket of a contract attribute. + ContractAttributeScope = 0x1000000, }; private: @@ -444,6 +447,11 @@ return getFlags() & Scope::CompoundStmtScope; } + /// Determine whether this scope is a contract attribute scope. + bool isContractAttributeScope() const { + return getFlags() & Scope::ContractAttributeScope; + } + /// Returns if rhs has a higher scope depth than this. /// /// The caller is responsible for calling this only if one of the two scopes Index: include/clang/Sema/Sema.h =================================================================== --- include/clang/Sema/Sema.h +++ include/clang/Sema/Sema.h @@ -31,6 +31,7 @@ #include "clang/AST/StmtCXX.h" #include "clang/AST/TypeLoc.h" #include "clang/AST/TypeOrdering.h" +#include "clang/Basic/Contract.h" #include "clang/Basic/ExpressionTraits.h" #include "clang/Basic/Module.h" #include "clang/Basic/OpenMPKinds.h" @@ -76,10 +77,12 @@ class ASTReader; class ASTWriter; class ArrayType; + class AssertContract; class ParsedAttr; class BindingDecl; class BlockDecl; class CapturedDecl; + class ContractAttr; class CXXBasePath; class CXXBasePaths; class CXXBindTemporaryExpr; @@ -139,6 +142,7 @@ class ModuleLoader; class MultiLevelTemplateArgumentList; class NamedDecl; + class NullStmt; class ObjCCategoryDecl; class ObjCCategoryImplDecl; class ObjCCompatibleAliasDecl; @@ -1027,6 +1031,28 @@ const DeclContext *DC, Decl *&ManglingContextDecl); + /// Whether we're currently analysing the predicate of a contract condition + /// (i.e. a precondition or postcondition). We need to keep track of this + /// because they have additional access restrictions that we need to enforce. + /// + /// \warning + /// Don't modify this directly; use \c EnterContractPredicateRAII to ensure + /// that the state is tracked correctly. + bool InContractConditionPredicate = false; + + /// Note that we're analysing a contract predicate. + class EnterContractPredicateRAII { + Sema &S; + bool Entered; + + /// Previous value of \c Sema::InContractConditionPredicate. + bool OldInContractConditionPredicate; + + public: + EnterContractPredicateRAII(Sema &S, ContractKind K); + void Exit(); + ~EnterContractPredicateRAII() { Exit(); } + }; /// SpecialMemberOverloadResult - The overloading result for a special member /// function. @@ -2978,7 +3004,7 @@ bool CheckParmsForFunctionDef(ArrayRef Parameters, bool CheckParameterNames); void CheckCXXDefaultArguments(FunctionDecl *FD); - void CheckExtraCXXDefaultArguments(Declarator &D); + void CheckCXXNonDeclFunctionRestrictions(Declarator &D); Scope *getNonFieldDeclScope(Scope *S); /// \name Name lookup @@ -3688,7 +3714,11 @@ StmtResult ActOnExprStmtError(); StmtResult ActOnNullStmt(SourceLocation SemiLoc, - bool HasLeadingEmptyMacro = false); + bool HasLeadingEmptyMacro = false, + ArrayRef Assertions = None); + + StmtResult BuildNullStmt(SourceLocation SemiLoc, bool HasLeadingEmptyMacro, + ArrayRef Assertions); void ActOnStartOfCompoundStmt(bool IsStmtExpr); void ActOnFinishOfCompoundStmt(); @@ -7059,6 +7089,8 @@ bool DeduceReturnType(FunctionDecl *FD, SourceLocation Loc, bool Diagnose = true); + void AdjustDeducedFunctionResultType(FunctionDecl *FD, QualType ResultType); + /// Declare implicit deduction guides for a class template if we've /// not already done so. void DeclareImplicitDeductionGuides(TemplateDecl *Template, @@ -7756,6 +7788,8 @@ ExtParameterInfoBuilder &ParamInfos); ExprResult SubstExpr(Expr *E, const MultiLevelTemplateArgumentList &TemplateArgs); + bool SubstContracts(FunctionDecl *New, const FunctionDecl *Tmpl, + const MultiLevelTemplateArgumentList &TemplateArgs); /// Substitute the given template arguments into a list of /// expressions, expanding pack expansions if required. @@ -8573,6 +8607,47 @@ ClassTemplateDecl *lookupCoroutineTraits(SourceLocation KwLoc, SourceLocation FuncLoc); + //===--------------------------------------------------------------------===// + // C++ Contract Attributes + // + + TypoCorrection CorrectTypoInContractLevel(IdentifierInfo *GivenIdent, + SourceLocation GivenLoc); + + DeclResult ActOnContractResultIdentifier(IdentifierInfo *Id, + SourceLocation IdLoc, + QualType ResultType = QualType()); + + ContractResult ActOnContractAttribute(SourceRange Range, + ContractKind Kind, + SourceLocation KindLoc, + ContractLevel Level, + SourceLocation LevelLoc, + DeclResult ResultVar, + SourceLocation ColonLoc, + ConditionResult Predicate); + + ContractResult BuildContractAttribute(SourceRange Range, + ContractKind Kind, + SourceLocation KindLoc, + ContractLevel Level, + SourceLocation LevelLoc, + VarDecl *ResultVar, + SourceLocation ColonLoc, + Expr *Predicate); + + void ActOnDelayedContractAttributes(ArrayRef Contracts, + FunctionDecl *Method); + + bool ValidateContractResultVariable(const VarDecl *VD); + + void AttachContractsToFunctionDecl(ArrayRef Contracts, + FunctionDecl *FD); + + bool UpdateFunctionContractsWithNewResultType(FunctionDecl *FD); + + bool MergeContractsOnFunctionRedecl(FunctionDecl *New, FunctionDecl *Old); + //===--------------------------------------------------------------------===// // OpenCL extensions. // Index: lib/AST/AttrContract.cpp =================================================================== --- /dev/null +++ lib/AST/AttrContract.cpp @@ -0,0 +1,56 @@ +//===--- AttrContract.cpp - Classes for representing contracts --*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "clang/AST/AttrContract.h" +#include "clang/AST/Decl.h" +#include "clang/AST/Expr.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/raw_ostream.h" + +using namespace clang; + +static StringRef toString(ContractKind K) { + switch (K) { + case ContractKind::Expects: return "expects"; + case ContractKind::Ensures: return "ensures"; + case ContractKind::Assert: return "assert"; + } +} + +static StringRef toString(ContractLevel L) { + switch (L) { + case ContractLevel::Default: return "default"; + case ContractLevel::Audit: return "audit"; + case ContractLevel::Axiom: return "axiom"; + } +} + +void ContractAttr::printPretty(raw_ostream &OS, + const PrintingPolicy &Policy) const { + OS << "[["; + + OS << toString(getKind()); + + if (getLevelLoc().isValid()) { + OS << ' ' << toString(getLevel()); + } else { + assert(getLevel() == ContractLevel::Default && + "Non-default contract level should have a source location"); + } + + if (auto *Ensures = dyn_cast(this)) { + if (Ensures->getResultVar()) + OS << ' ' << Ensures->getResultVar()->getName(); + } + + OS << ": "; + getPredicate()->printPretty(OS, nullptr, Policy); + + OS << "]]"; +} Index: lib/AST/CMakeLists.txt =================================================================== --- lib/AST/CMakeLists.txt +++ lib/AST/CMakeLists.txt @@ -12,6 +12,7 @@ ASTImporter.cpp ASTStructuralEquivalence.cpp ASTTypeTraits.cpp + AttrContract.cpp AttrImpl.cpp CXXInheritance.cpp Comment.cpp Index: lib/AST/Decl.cpp =================================================================== --- lib/AST/Decl.cpp +++ lib/AST/Decl.cpp @@ -16,6 +16,7 @@ #include "clang/AST/ASTContext.h" #include "clang/AST/ASTLambda.h" #include "clang/AST/ASTMutationListener.h" +#include "clang/AST/AttrContract.h" #include "clang/AST/CanonicalType.h" #include "clang/AST/DeclBase.h" #include "clang/AST/DeclCXX.h" @@ -3017,6 +3018,19 @@ } } +void FunctionDecl::setContracts(ASTContext &C, + ArrayRef NewContracts) { + assert(!Contracts && "Already have contracts!"); + assert(llvm::none_of(NewContracts, isa) && + "Cannot apply assertion contracts to a function"); + + if (!NewContracts.empty()) { + Contracts = new (C) ContractAttr*[NewContracts.size()]; + NumContracts = NewContracts.size(); + llvm::copy(NewContracts, Contracts); + } +} + /// getMinRequiredArguments - Returns the minimum number of arguments /// needed to call this function. This may be fewer than the number of /// function parameters, if some of the parameters have default Index: lib/AST/DeclPrinter.cpp =================================================================== --- lib/AST/DeclPrinter.cpp +++ lib/AST/DeclPrinter.cpp @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// #include "clang/AST/ASTContext.h" #include "clang/AST/Attr.h" +#include "clang/AST/AttrContract.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclCXX.h" #include "clang/AST/DeclObjC.h" @@ -707,6 +708,18 @@ prettyPrintAttributes(D); + const bool PrintContracts = !D->hasInheritedContracts() && + !D->contracts().empty(); + if (PrintContracts) { + Indentation += Policy.Indentation; + for (const ContractAttr *Contract : D->contracts()) { + Out << '\n'; + Indent(); + Contract->printPretty(Out, Policy); + } + Indentation -= Policy.Indentation; + } + if (D->isPure()) Out << " = 0"; else if (D->isDeletedAsWritten()) @@ -727,6 +740,9 @@ Out << ";\n"; } Indentation -= Policy.Indentation; + } else if (PrintContracts) { + // Newline between the last contract and the opening brace. + Out << '\n'; } else Out << ' '; Index: lib/AST/ExprConstant.cpp =================================================================== --- lib/AST/ExprConstant.cpp +++ lib/AST/ExprConstant.cpp @@ -37,6 +37,7 @@ #include "clang/AST/ASTContext.h" #include "clang/AST/ASTDiagnostic.h" #include "clang/AST/ASTLambda.h" +#include "clang/AST/AttrContract.h" #include "clang/AST/CharUnits.h" #include "clang/AST/Expr.h" #include "clang/AST/RecordLayout.h" @@ -44,6 +45,7 @@ #include "clang/AST/TypeLoc.h" #include "clang/Basic/Builtins.h" #include "clang/Basic/TargetInfo.h" +#include "llvm/Support/CastingIterators.h" #include "llvm/Support/raw_ostream.h" #include #include @@ -477,6 +479,10 @@ /// parameters' function scope indices. APValue *Arguments; + /// Result of this function call. + /// This should be set after a successful return. + APValue *ResultValue = nullptr; + // Note that we intentionally use std::map here so that references to // values are stable. typedef std::pair MapKeyTy; @@ -2539,6 +2545,18 @@ return true; } + // If this is result identifier introduced by a postcondition contract, get + // the result value of the current function call. + if (VD->isContractFunctionResult()) { + assert(Frame && Frame->ResultValue && + "Trying to evaluate a postcondition variable but the current frame " + "doesn't have a result. Either we're trying to check the result of " + "a void function or the postcondtions are being executed before the " + "function has returned"); + Result = Frame->ResultValue; + return true; + } + // If this is a local variable, dig out its value. if (Frame) { Result = LVal ? Frame->getTemporary(VD, LVal->getLValueVersion()) @@ -3888,6 +3906,30 @@ return EvaluateAsBooleanCondition(Cond, Result, Info); } +/// Evaluate a contract attribute. +/// \return true if the evaluation succeeded and the predicate returned true. +static bool EvaluateContract(EvalInfo &Info, const ContractAttr *C) { + // TODO: Skip contracts that aren't checked in the current build level. + + // Evaluate the predicate. + bool PredResult; + if (!EvaluateCond(Info, nullptr, C->getPredicate(), PredResult)) + return false; + + // C++2a [expr.const]p2: + // An expression e is a core constant expression unless [...]: + // [...] + // - a checked contract whose predicate evaluates to false; + // [...] + if (!PredResult) { + Info.CCEDiag(C->getPredicate(), diag::note_constexpr_contract_failed) + << static_cast(C->getKind()) + << Info.checkingPotentialConstantExpression(); + } + + return PredResult; +} + namespace { /// A location where the result (returned value) of evaluating a /// statement should be stored. @@ -4086,8 +4128,13 @@ Info.FFDiag(S->getBeginLoc()); return ESR_Failed; - case Stmt::NullStmtClass: - return ESR_Succeeded; + case Stmt::NullStmtClass: { + const bool Ok = llvm::all_of(cast(S)->assertions(), + [&Info](const AssertContract *C) { + return EvaluateContract(Info, C); + }); + return Ok ? ESR_Succeeded : ESR_Failed; + } case Stmt::DeclStmtClass: { const DeclStmt *DS = cast(S); @@ -4398,6 +4445,22 @@ return Success; } +static bool EvaluatePreconditions(ArrayRef Contracts, + EvalInfo &Info) { + return llvm::all_of(llvm::make_dyn_cast_range(Contracts), + [&Info](const ExpectsContract *C) { + return EvaluateContract(Info, C); + }); +} + +static bool EvaluatePostconditions(ArrayRef Contracts, + EvalInfo &Info) { + return llvm::all_of(llvm::make_dyn_cast_range(Contracts), + [&Info](const EnsuresContract *C) { + return EvaluateContract(Info, C); + }); +} + /// Evaluate a function call. static bool HandleFunctionCall(SourceLocation CallLoc, const FunctionDecl *Callee, const LValue *This, @@ -4413,6 +4476,9 @@ CallStackFrame Frame(Info, CallLoc, Callee, This, ArgValues.data()); + if (!EvaluatePreconditions(Callee->contracts(), Info)) + return false; + // For a trivial copy or move assignment, perform an APValue copy. This is // essential for unions, where the operations performed by the assignment // operator cannot be represented as statements. @@ -4449,13 +4515,21 @@ } StmtResult Ret = {Result, ResultSlot}; - EvalStmtResult ESR = EvaluateStmt(Ret, Info, Body); - if (ESR == ESR_Succeeded) { - if (Callee->getReturnType()->isVoidType()) - return true; - Info.FFDiag(Callee->getEndLoc(), diag::note_constexpr_no_return); + switch (EvaluateStmt(Ret, Info, Body)) { + case ESR_Returned: + Frame.ResultValue = &Result; + return EvaluatePostconditions(Callee->contracts(), Info); + + case ESR_Succeeded: + if (!Callee->getReturnType()->isVoidType()) { + Info.FFDiag(Callee->getEndLoc(), diag::note_constexpr_no_return); + return false; + } + return EvaluatePostconditions(Callee->contracts(), Info); + + default: + return false; } - return ESR == ESR_Returned; } /// Evaluate a constructor call. @@ -4478,6 +4552,9 @@ {This.getLValueCallIndex(), This.getLValueVersion()}}); CallStackFrame Frame(Info, CallLoc, Definition, &This, ArgValues); + if (!EvaluatePreconditions(Definition->contracts(), Info)) + return false; + // FIXME: Creating an APValue just to hold a nonexistent return value is // wasteful. APValue RetVal; @@ -4612,7 +4689,8 @@ } return Success && - EvaluateStmt(Ret, Info, Definition->getBody()) != ESR_Failed; + EvaluateStmt(Ret, Info, Definition->getBody()) != ESR_Failed && + EvaluatePostconditions(Definition->contracts(), Info); } static bool HandleConstructorCall(const Expr *E, const LValue &This, Index: lib/AST/Stmt.cpp =================================================================== --- lib/AST/Stmt.cpp +++ lib/AST/Stmt.cpp @@ -14,6 +14,7 @@ #include "clang/AST/Stmt.h" #include "clang/AST/ASTContext.h" #include "clang/AST/ASTDiagnostic.h" +#include "clang/AST/AttrContract.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclGroup.h" #include "clang/AST/Expr.h" @@ -353,6 +354,44 @@ return new (Mem) AttributedStmt(EmptyShell(), NumAttrs); } +NullStmt *NullStmt::Create(const ASTContext &C, SourceLocation L, + bool hasLeadingEmptyMacro, + ArrayRef Assertions) { + void *Mem = C.Allocate(totalSizeToAlloc(Assertions.size()), + alignof(NullStmt)); + return new (Mem) NullStmt(L, hasLeadingEmptyMacro, Assertions); +} + +NullStmt *NullStmt::CreateEmpty(const ASTContext &C, unsigned NumAssertions) { + void *Mem = C.Allocate(totalSizeToAlloc(NumAssertions), + alignof(NullStmt)); + return new (Mem) NullStmt(EmptyShell{}, NumAssertions); +} + +NullStmt::NullStmt(SourceLocation L, bool hasLeadingEmptyMacro, + ArrayRef Assertions) + : Stmt(NullStmtClass), SemiLoc(L), + HasLeadingEmptyMacro(hasLeadingEmptyMacro), + NumAssertions(Assertions.size()) { + assert(this->NumAssertions == Assertions.size() && "Contract overflow!"); + llvm::copy(Assertions, getTrailingObjects()); +} + +NullStmt::NullStmt(EmptyShell Empty, unsigned NumAssertions) + : Stmt(NullStmtClass, Empty), HasLeadingEmptyMacro(false), + NumAssertions(NumAssertions) { + assert(this->NumAssertions == NumAssertions && "Contract overflow!"); + std::fill_n(getTrailingObjects(), NumAssertions, nullptr); +} + +SourceLocation NullStmt::getBeginLoc() const { + // Use the start location of the first assertion contract if we have any. + if (assertions_begin() != assertions_end()) + return (*assertions_begin())->getBeginLoc(); + + return SemiLoc; +} + std::string AsmStmt::generateAsmString(const ASTContext &C) const { if (const auto *gccAsmStmt = dyn_cast(this)) return gccAsmStmt->generateAsmString(C); Index: lib/AST/StmtPrinter.cpp =================================================================== --- lib/AST/StmtPrinter.cpp +++ lib/AST/StmtPrinter.cpp @@ -14,6 +14,7 @@ #include "clang/AST/ASTContext.h" #include "clang/AST/Attr.h" +#include "clang/AST/AttrContract.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclBase.h" #include "clang/AST/DeclCXX.h" @@ -169,7 +170,17 @@ } void StmtPrinter::VisitNullStmt(NullStmt *Node) { - Indent() << ";\n"; + Indent(); + if (Node->assertions_begin() != Node->assertions_end()) { + auto It = Node->assertions_begin(); + const auto End = Node->assertions_end(); + (*It++)->printPretty(OS, Policy); + while (It != End) { + OS << ' '; + (*It++)->printPretty(OS, Policy); + } + } + OS << ";\n"; } void StmtPrinter::VisitDeclStmt(DeclStmt *Node) { Index: lib/AST/StmtProfile.cpp =================================================================== --- lib/AST/StmtProfile.cpp +++ lib/AST/StmtProfile.cpp @@ -128,6 +128,9 @@ ID.AddBoolean(TTP->isParameterPack()); return; } + + if (isa(D) && cast(D)->isContractFunctionResult()) + return; } ID.AddPointer(D ? D->getCanonicalDecl() : nullptr); Index: lib/CodeGen/CGStmt.cpp =================================================================== --- lib/CodeGen/CGStmt.cpp +++ lib/CodeGen/CGStmt.cpp @@ -349,7 +349,7 @@ bool CodeGenFunction::EmitSimpleStmt(const Stmt *S) { switch (S->getStmtClass()) { default: return false; - case Stmt::NullStmtClass: break; + case Stmt::NullStmtClass: EmitNullStmt(cast(*S)); break; case Stmt::CompoundStmtClass: EmitCompoundStmt(cast(*S)); break; case Stmt::DeclStmtClass: EmitDeclStmt(cast(*S)); break; case Stmt::LabelStmtClass: EmitLabelStmt(cast(*S)); break; @@ -558,6 +558,13 @@ } +void CodeGenFunction::EmitNullStmt(const NullStmt &S) { + if (S.assertions_begin() == S.assertions_end()) + return; + + ErrorUnsupported(&S, "assertion contract"); +} + void CodeGenFunction::EmitLabelStmt(const LabelStmt &S) { EmitLabel(S.getDecl()); EmitStmt(S.getSubStmt()); Index: lib/CodeGen/CodeGenFunction.h =================================================================== --- lib/CodeGen/CodeGenFunction.h +++ lib/CodeGen/CodeGenFunction.h @@ -2839,6 +2839,7 @@ /// function even if there is no current insertion point. void EmitLabel(const LabelDecl *D); // helper for EmitLabelStmt. + void EmitNullStmt(const NullStmt &S); void EmitLabelStmt(const LabelStmt &S); void EmitAttributedStmt(const AttributedStmt &S); void EmitGotoStmt(const GotoStmt &S); Index: lib/CodeGen/CodeGenFunction.cpp =================================================================== --- lib/CodeGen/CodeGenFunction.cpp +++ lib/CodeGen/CodeGenFunction.cpp @@ -1328,6 +1328,9 @@ // Emit the standard function prologue. StartFunction(GD, ResTy, Fn, FnInfo, Args, Loc, BodyRange.getBegin()); + if (!FD->contracts().empty()) + CGM.ErrorUnsupported(FD, "function with contract conditions"); + // Generate the body of the function. PGO.assignRegionCounters(GD, CurFn); if (isa(FD)) Index: lib/Parse/ParseCXXInlineMethods.cpp =================================================================== --- lib/Parse/ParseCXXInlineMethods.cpp +++ lib/Parse/ParseCXXInlineMethods.cpp @@ -458,6 +458,25 @@ NoexceptExpr.get() : nullptr); } + // Parse a delayed sequence of contract-attribute-specifier, if there is one. + if (std::unique_ptr Toks = std::move(LM.ContractTokens)) { + ParenBraceBracketBalancer BalancerRAIIObj(*this); + UseCachedTokensRAII CachedTokStream(*this, *Toks, LM.Method); + Sema::ContextRAII FuncCtx(Actions, Func, false); + + ContractsParseState Contracts(ContractsParseState::ParsingFunctionDecl{}, + /* Delayed */false, Func->getReturnType()); + + // Parse as an attribute-specifier-seq. + // We still need a factory even though we should only have contracts. + AttributeFactory DummyFactory; + ParsedAttributesWithRange Attrs(DummyFactory); + ParseCXX11Attributes(Attrs, nullptr, &Contracts); + assert(Attrs.empty() && "Expecting only contracts at this point"); + + Actions.ActOnDelayedContractAttributes(Contracts.getParsedList(), Func); + } + ThisScope.Exit(); PrototypeScope.Exit(); Index: lib/Parse/ParseDecl.cpp =================================================================== --- lib/Parse/ParseDecl.cpp +++ lib/Parse/ParseDecl.cpp @@ -6061,6 +6061,9 @@ ExprResult NoexceptExpr; CachedTokens *ExceptionSpecTokens = nullptr; ParsedAttributesWithRange FnAttrs(AttrFactory); + ContractsParseState Contracts( + ContractsParseState::ParsingFunctionDecl{}, + /* Delayed */D.getContext() == DeclaratorContext::MemberContext); TypeResult TrailingReturnType; /* LocalEndLoc is the end location for the local FunctionTypeLoc. @@ -6179,7 +6182,7 @@ // Parse attribute-specifier-seq[opt]. Per DR 979 and DR 1297, this goes // after the exception-specification. - MaybeParseCXX11Attributes(FnAttrs); + MaybeParseCXX11Attributes(FnAttrs, nullptr, false, &Contracts); // Parse trailing-return-type[opt]. LocalEndLoc = EndLoc; @@ -6225,7 +6228,13 @@ DynamicExceptions.size(), NoexceptExpr.isUsable() ? NoexceptExpr.get() : nullptr, ExceptionSpecTokens, DeclsInPrototype, StartLoc, - LocalEndLoc, D, TrailingReturnType), + LocalEndLoc, D, TrailingReturnType, + Contracts.delayParsing() + ? ArrayRef() + : Contracts.getParsedList(), + Contracts.delayParsing() + ? &Contracts.getCachedTokens() + : nullptr), std::move(FnAttrs), EndLoc); } Index: lib/Parse/ParseDeclCXX.cpp =================================================================== --- lib/Parse/ParseDeclCXX.cpp +++ lib/Parse/ParseDeclCXX.cpp @@ -2110,8 +2110,11 @@ const bool LateParseExceptionSpec = FTI.getExceptionSpecType() == EST_Unparsed; + const bool LateParseContracts = + FTI.NumParsedContracts == 0 && FTI.ContractTokens; + // If we didn't delay parsing for any parts of this function, then we're done. - if (!LateParseDefaultArgs && !LateParseExceptionSpec) + if (!LateParseDefaultArgs && !LateParseExceptionSpec && !LateParseContracts) return; // Push this method onto the stack of late-parsed method declarations. @@ -2134,6 +2137,12 @@ LateMethod->ExceptionSpecTokens.reset(FTI.ExceptionSpecTokens); FTI.ExceptionSpecTokens = nullptr; } + + if (LateParseContracts) { + // Stash the contract-attribute-specifier tokens. + LateMethod->ContractTokens.reset(FTI.ContractTokens); + FTI.ContractTokens = nullptr; + } } /// isCXX11VirtSpecifier - Determine whether the given token is a C++11 @@ -3901,6 +3910,7 @@ /// /// [C++11] attribute-specifier: /// '[' '[' attribute-list ']' ']' +/// [C++2a] contract-attribute-specifier /// alignment-specifier /// /// [C++11] attribute-list: @@ -3922,7 +3932,8 @@ /// [C++11] attribute-namespace: /// identifier void Parser::ParseCXX11AttributeSpecifier(ParsedAttributes &attrs, - SourceLocation *endLoc) { + SourceLocation *endLoc, + ContractsParseState *Contracts) { if (Tok.is(tok::kw_alignas)) { Diag(Tok.getLocation(), diag::warn_cxx98_compat_alignas); ParseAlignmentSpecifier(attrs, endLoc); @@ -3932,6 +3943,12 @@ assert(Tok.is(tok::l_square) && NextToken().is(tok::l_square) && "Not a double square bracket attribute list"); + // Handle C++ contract attributes separately. + if (getLangOpts().CPlusPlus && + isContractKind(GetLookAheadToken(2/* brackets */)) && + GetLookAheadToken(3).isNot(tok::coloncolon)) + return ParseContractAttributeSpecifier(Contracts); + Diag(Tok.getLocation(), diag::warn_cxx98_compat_attribute); ConsumeBracket(); @@ -3964,6 +3981,15 @@ SourceLocation ScopeLoc, AttrLoc; IdentifierInfo *ScopeName = nullptr, *AttrName = nullptr; + if (getLangOpts().CPlusPlus && !CommonScopeName && + NextToken().isNot(tok::coloncolon) && isContractKind(Tok)) { + // Contract attributes can't appear in a regular attribute-list; it's + // always one contract per attribute-specifier. + Diag(Tok.getLocation(), diag::err_contract_attribute_needs_separate_list); + SkipUntil(tok::r_square, StopBeforeMatch); + break; + } + AttrName = TryParseCXX11AttributeIdentifier(AttrLoc); if (!AttrName) // Break out to the "expected ']'" diagnostic. @@ -4030,7 +4056,8 @@ /// attribute-specifier-seq: /// attribute-specifier-seq[opt] attribute-specifier void Parser::ParseCXX11Attributes(ParsedAttributesWithRange &attrs, - SourceLocation *endLoc) { + SourceLocation *endLoc, + ContractsParseState *Contracts) { assert(standardAttributesAllowed()); SourceLocation StartLoc = Tok.getLocation(), Loc; @@ -4038,7 +4065,7 @@ endLoc = &Loc; do { - ParseCXX11AttributeSpecifier(attrs, endLoc); + ParseCXX11AttributeSpecifier(attrs, endLoc, Contracts); } while (isCXX11AttributeSpecifier()); attrs.Range = SourceRange(StartLoc, *endLoc); @@ -4081,6 +4108,199 @@ return EndLoc; } +/// Try to interpret the given token as a contract attribute kind. +Optional Parser::isContractKind(const Token &Tok) const { + if (Tok.is(tok::identifier)) { + const IdentifierInfo *Ident = Tok.getIdentifierInfo(); + + if (!Ident_expects) { + Ident_expects = PP.getIdentifierInfo("expects"); + Ident_ensures = PP.getIdentifierInfo("ensures"); + Ident_assert = PP.getIdentifierInfo("assert"); + } + + if (Ident == Ident_expects) + return ContractKind::Expects; + if (Ident == Ident_ensures) + return ContractKind::Ensures; + if (Ident == Ident_assert) + return ContractKind::Assert; + } + return None; +} + +/// Try to interpret the given token as a C++2a contract-level. +/// +/// contract-level: +/// 'default' +/// 'audit' +/// 'axiom' +Optional Parser::isContractLevel(const Token &Tok) const { + switch (Tok.getKind()) { + case tok::kw_default: + return ContractLevel::Default; + + case tok::identifier: { + const IdentifierInfo *Ident = Tok.getIdentifierInfo(); + + if (!Ident_audit) { + Ident_audit = PP.getIdentifierInfo("audit"); + Ident_axiom = PP.getIdentifierInfo("axiom"); + } + + if (Ident == Ident_audit) + return ContractLevel::Audit; + if (Ident == Ident_axiom) + return ContractLevel::Axiom; + + LLVM_FALLTHROUGH; + } + + default: + return None; + } +} + +/// After failing to parse a contract-level, determine whether the current token +/// was likely intended as a contract level. +bool Parser::UserProbablyMeantContractLevel(ContractKind Kind) { + // Gauge the structure of the attribute by peeking at the subsequent tokens. + switch (Kind) { + case ContractKind::Expects: + case ContractKind::Assert: + return Tok.getIdentifierInfo() && NextToken().is(tok::colon); + + case ContractKind::Ensures: + return Tok.getIdentifierInfo() && NextToken().getIdentifierInfo() && + GetLookAheadToken(2).is(tok::colon); + } +} + +/// Parse a C++2a contract-attribute-specifier. +/// +/// contract-attribute-specifier: +/// '[' '[' 'expects' contract-level[opt] ':' conditional-expression ']' ']' +/// '[' '[' 'ensures' contract-level[opt] identifier[opt] ':' conditional-expression ']' ']' +/// '[' '[' 'assert' contract-level[opt] ':' conditional-expression ']' ']' +void Parser::ParseContractAttributeSpecifier(ContractsParseState *Contracts) { + assert(Tok.is(tok::l_square) && NextToken().is(tok::l_square) && + isContractKind(GetLookAheadToken(2/* brackets */)) && + "Not a contract-attribute-specifier"); + + if (Contracts && Contracts->delayParsing()) { + // Cache the tokens that make up this contract; we'll parse it later. + // The first bracket is pushed manually because the consume function + // automatically skips over balanced delimiters. + CachedTokens &Cache = Contracts->getCachedTokens(); + Cache.push_back(Tok); + ConsumeBracket(); + ConsumeAndStoreUntil(tok::r_square, Cache); + return; + } + + BalancedDelimiterTracker Outer(*this, tok::l_square); + BalancedDelimiterTracker Inner(*this, tok::l_square); + + Outer.consumeOpen(); + Inner.consumeOpen(); + + ParseScope ContractScope(this, getCurScope()->getFlags() | + Scope::ContractAttributeScope); + + ContractKind Kind = *isContractKind(Tok); + SourceLocation KindLoc = ConsumeToken(); + + Diag(KindLoc, getLangOpts().CPlusPlus2a + ? diag::warn_cxx17_compat_contract_attributes + : diag::ext_contract_attributes); + + const bool Allowed = Contracts && Contracts->allowsKind(Kind); + if (!Allowed) { + Diag(KindLoc, diag::err_contract_attribute_misplaced) + << static_cast(Kind); + } + + ContractLevel Level; + SourceLocation LevelLoc; + if (auto LevelOpt = isContractLevel(Tok)) { + // Greedily take the level, even if it could be a return identifier. + // + // C++2a [dcl.attr.contract.syn]p1: + // An ambiguity between a contract-level and an identifier is resolved in + // favor of contract-level. + Level = *LevelOpt; + LevelLoc = ConsumeToken(); + } else { + // C++2a [dcl.attr.contract.check]p1: + // If the contract-level of a contract-attribute-specifier is absent, it + // is assumed to be default. + Level = ContractLevel::Default; + + // The contract-level is optional, so we could just continue as normal at + // this point. However we can provide nicer diagnostics in cases where the + // intention is clear. + if (UserProbablyMeantContractLevel(Kind)) { + // Prefer to present a correction if possible. + if (const TypoCorrection Correction = + Actions.CorrectTypoInContractLevel(Tok.getIdentifierInfo(), + Tok.getLocation())) { + Diag(Tok.getLocation(), diag::err_unknown_contract_level_suggest) + << Tok.getIdentifierInfo() + << Correction.getCorrectionAsIdentifierInfo(); + } else { + Diag(Tok.getLocation(), diag::err_unknown_contract_level) + << Tok.getIdentifierInfo(); + } + + // Consume the erroneous token for recovery purposes. + ConsumeToken(); + } + } + + DeclResult ResultVar; + if (Kind == ContractKind::Ensures && Tok.is(tok::identifier)) { + IdentifierInfo *II = Tok.getIdentifierInfo(); + SourceLocation Loc = ConsumeToken(); + + ResultVar = Actions.ActOnContractResultIdentifier( + II, Loc, + Contracts ? Contracts->getResultType() : QualType()); + } + + SourceLocation ColonLoc = Tok.getLocation(); + ExpectAndConsume(tok::colon); + + Sema::ConditionResult Predicate; + { + Sema::EnterContractPredicateRAII _(Actions, Kind); + + ExprResult CondExpr = ParseExpression(); + if (CondExpr.isInvalid()) { + Inner.skipToEnd(); + Outer.skipToEnd(); + return; + } + + // C++2a [dcl.attr.contract.syn]p5: + // The conditional-expression in a contract is contextually converted to + // bool; the converted expression is called the predicate of the contract. + Predicate = Actions.ActOnCondition(getCurScope(), KindLoc, CondExpr.get(), + Sema::ConditionKind::Boolean); + } + + ContractScope.Exit(); + + Inner.consumeClose(); + Outer.consumeClose(); + + if (Allowed && !Predicate.isInvalid() && Predicate.get().second) { + Contracts->getParsedList().push_back( + Actions.ActOnContractAttribute( + Outer.getRange(), Kind, KindLoc, Level, LevelLoc, ResultVar, + ColonLoc, Predicate)); + } +} + /// Parse uuid() attribute when it appears in a [] Microsoft attribute. void Parser::ParseMicrosoftUuidAttributeArgs(ParsedAttributes &Attrs) { assert(Tok.is(tok::identifier) && "Not a Microsoft attribute list"); Index: lib/Parse/ParseStmt.cpp =================================================================== --- lib/Parse/ParseStmt.cpp +++ lib/Parse/ParseStmt.cpp @@ -103,12 +103,15 @@ ParenBraceBracketBalancer BalancerRAIIObj(*this); ParsedAttributesWithRange Attrs(AttrFactory); - MaybeParseCXX11Attributes(Attrs, nullptr, /*MightBeObjCMessageSend*/ true); + ContractsParseState Contracts(ContractsParseState::ParsingStmt{}); + MaybeParseCXX11Attributes(Attrs, nullptr, /*MightBeObjCMessageSend*/ true, + &Contracts); + if (!MaybeParseOpenCLUnrollHintAttribute(Attrs)) return StmtError(); StmtResult Res = ParseStatementOrDeclarationAfterAttributes( - Stmts, Allowed, TrailingElseLoc, Attrs); + Stmts, Allowed, TrailingElseLoc, Attrs, &Contracts); assert((Attrs.empty() || Res.isInvalid() || Res.isUsable()) && "attributes on empty statement"); @@ -148,13 +151,45 @@ }; } +/// Look for (assertion) contracts that aren't attached to a null statement. +/// It's very unlikely that the user legitimately thought that an assertion can +/// be attached to an arbitrary statement (e.g. a for loop), so we'll diagnose +/// it as a missing semicolon. +/// \returns true if we diagnose a missing semicolon. +bool Parser::MaybeDiagnoseAssertWithoutSemi(ContractsParseState *Contracts) { + assert(!Contracts || Contracts->allowsKind(ContractKind::Assert) && + "Checking the wrong kind of contract"); + + // If there're no contracts or the token is a semicolon (i.e. the next + // statement is a null statement), then we don't need to do anything. + if (!Contracts || Contracts->getParsedList().empty() || Tok.is(tok::semi)) + return false; + + // Recover gracefully if there's an extra closing bracket before a semicolon. + if (Tok.is(tok::r_square) && NextToken().is(tok::semi)) { + Diag(Tok, diag::err_extraneous_token_before_semi) + << "]" << FixItHint::CreateRemoval(Tok.getLocation()); + ConsumeBracket(); + return false; + } + + // Otherwise, just emit the expected ';' diagnostic. + // FIXME: Maybe use the end location of the last contract? + const SourceLocation End = getEndOfPreviousToken(); + Diag(End, diag::err_expected_semi_after_assert_contract) + << FixItHint::CreateInsertion(End, ";"); + return true; +} + StmtResult Parser::ParseStatementOrDeclarationAfterAttributes(StmtVector &Stmts, AllowedConstructsKind Allowed, SourceLocation *TrailingElseLoc, - ParsedAttributesWithRange &Attrs) { + ParsedAttributesWithRange &Attrs, ContractsParseState *Contracts) { const char *SemiError = nullptr; StmtResult Res; + const bool IsAssertWithoutSemi = MaybeDiagnoseAssertWithoutSemi(Contracts); + // Cases in this switch statement should fall through if the parser expects // the token to end in a semicolon (in which case SemiError should be set), // or they directly 'return;' if not. @@ -217,7 +252,8 @@ } if (Tok.is(tok::r_brace)) { - Diag(Tok, diag::err_expected_statement); + if (!IsAssertWithoutSemi) + Diag(Tok, diag::err_expected_statement); return StmtError(); } @@ -233,7 +269,11 @@ return ParseCompoundStatement(); case tok::semi: { // C99 6.8.3p3: expression[opt] ';' bool HasLeadingEmptyMacro = Tok.hasLeadingEmptyMacro(); - return Actions.ActOnNullStmt(ConsumeToken(), HasLeadingEmptyMacro); + SourceLocation SemiLoc = ConsumeToken(); + return Actions.ActOnNullStmt(SemiLoc, HasLeadingEmptyMacro, + Contracts + ? Contracts->getParsedList() + : ArrayRef()); } case tok::kw_if: // C99 6.8.4.1: if-statement Index: lib/Parse/Parser.cpp =================================================================== --- lib/Parse/Parser.cpp +++ lib/Parse/Parser.cpp @@ -462,6 +462,12 @@ Ident_override = nullptr; Ident_GNU_final = nullptr; + Ident_expects = nullptr; + Ident_ensures = nullptr; + Ident_assert = nullptr; + Ident_audit = nullptr; + Ident_axiom = nullptr; + Ident_super = &PP.getIdentifierTable().get("super"); Ident_vector = nullptr; Index: lib/Sema/CMakeLists.txt =================================================================== --- lib/Sema/CMakeLists.txt +++ lib/Sema/CMakeLists.txt @@ -27,6 +27,7 @@ SemaChecking.cpp SemaCodeComplete.cpp SemaConsumer.cpp + SemaContract.cpp SemaCoroutine.cpp SemaCUDA.cpp SemaDecl.cpp Index: lib/Sema/DeclSpec.cpp =================================================================== --- lib/Sema/DeclSpec.cpp +++ lib/Sema/DeclSpec.cpp @@ -13,6 +13,7 @@ #include "clang/Sema/DeclSpec.h" #include "clang/AST/ASTContext.h" +#include "clang/AST/AttrContract.h" #include "clang/AST/DeclCXX.h" #include "clang/AST/Expr.h" #include "clang/AST/LocInfoType.h" @@ -178,7 +179,9 @@ SourceLocation LocalRangeBegin, SourceLocation LocalRangeEnd, Declarator &TheDeclarator, - TypeResult TrailingReturnType) { + TypeResult TrailingReturnType, + ArrayRef Contracts, + CachedTokens *ContractTokens) { assert(!(TypeQuals & DeclSpec::TQ_atomic) && "function cannot have _Atomic qualifier"); @@ -206,8 +209,10 @@ I.Fun.ExceptionSpecLocBeg = ESpecRange.getBegin().getRawEncoding(); I.Fun.ExceptionSpecLocEnd = ESpecRange.getEnd().getRawEncoding(); I.Fun.NumExceptionsOrDecls = 0; + I.Fun.NumParsedContracts = 0; I.Fun.Exceptions = nullptr; I.Fun.NoexceptExpr = nullptr; + I.Fun.ContractTokens = nullptr; I.Fun.HasTrailingReturnType = TrailingReturnType.isUsable() || TrailingReturnType.isInvalid(); I.Fun.TrailingReturnType = TrailingReturnType.get(); @@ -261,6 +266,16 @@ break; } + if (!Contracts.empty()) { + assert(!ContractTokens && "Can't mix parsed contracts with delayed parsing"); + I.Fun.NumParsedContracts = Contracts.size(); + I.Fun.ParsedContracts = new ContractAttr *[Contracts.size()]; + llvm::transform(Contracts, I.Fun.ParsedContracts, + std::mem_fn(&ContractResult::get)); + } else if (ContractTokens && !ContractTokens->empty()) { + I.Fun.ContractTokens = new CachedTokens(*ContractTokens); + } + if (!DeclsInPrototype.empty()) { assert(ESpecType == EST_None && NumExceptions == 0 && "cannot have exception specifiers and decls in prototype"); Index: lib/Sema/Scope.cpp =================================================================== --- lib/Sema/Scope.cpp +++ lib/Sema/Scope.cpp @@ -167,7 +167,8 @@ {SEHExceptScope, "SEHExceptScope"}, {SEHFilterScope, "SEHFilterScope"}, {CompoundStmtScope, "CompoundStmtScope"}, - {ClassInheritanceScope, "ClassInheritanceScope"}}; + {ClassInheritanceScope, "ClassInheritanceScope"}, + {ContractAttributeScope, "ContractAttributeScope"}}; for (auto Info : FlagInfo) { if (Flags & Info.first) { Index: lib/Sema/SemaAccess.cpp =================================================================== --- lib/Sema/SemaAccess.cpp +++ lib/Sema/SemaAccess.cpp @@ -79,11 +79,13 @@ namespace { struct EffectiveContext { - EffectiveContext() : Inner(nullptr), Dependent(false) {} + EffectiveContext() : Inner(nullptr), Dependent(false), + InContractCondition(false) {} - explicit EffectiveContext(DeclContext *DC) + EffectiveContext(DeclContext *DC, bool InContractCondition) : Inner(DC), - Dependent(DC->isDependentContext()) { + Dependent(DC->isDependentContext()), + InContractCondition(InContractCondition) { // C++11 [class.access.nest]p1: // A nested class is a member and as such has the same access @@ -124,6 +126,7 @@ } bool isDependent() const { return Dependent; } + bool isInContractCondition() const { return InContractCondition; } bool includesClass(const CXXRecordDecl *R) const { R = R->getCanonicalDecl(); @@ -143,6 +146,7 @@ SmallVector Functions; SmallVector Records; bool Dependent; + bool InContractCondition; }; /// Like sema::AccessedEntity, but kindly lets us scribble all over @@ -572,6 +576,17 @@ const CXXRecordDecl *Class) { AccessResult OnFailure = AR_inaccessible; + // C++2a [dcl.attr.contract.cond]p4: + // [...] + // Additional access restrictions apply to names appearing in a contract + // condition of a member function of class C: + // - Friendship is not considered. + // [...] + // For names appearing in a contract condition of a non-member function, + // friendship is not considered. + if (EC.isInContractCondition()) + return AR_inaccessible; + // Okay, check friends. for (auto *Friend : Class->friends()) { switch (MatchesFriend(S, EC, Friend)) { @@ -737,6 +752,44 @@ if (Access == AS_public) return AR_accessible; assert(Access == AS_private || Access == AS_protected); + if (EC.isInContractCondition() && isa(EC.getInnerContext())) { + // C++2a [dcl.attr.contract.cond]p4: + // The predicate of a contract condition has the same semantic + // restrictions as if it appeared as the first expression-statement in + // the body of the function it applies to. Additional access restrictions + // apply to names appearing in a contract condition of a member function + // of class C: + // - Friendship is not considered. + // - For a contract condition of a public member function, no member of + // C or of an enclosing class of C is accessible unless it is a public + // member of C, or a member of a base class accessible as a public + // member of C. + // - For a contract condition of a protected member function, no member + // of C or of an enclosing class of C is accessible unless it is a + // public or protected member of C, or a member of a base class + // accessible as a public or protected member of C. + // For names appearing in a contract condition of a non-member function, + // friendship is not considered. + auto *Func = cast(EC.getInnerContext())->getCanonicalDecl(); + if (Func->isCXXClassMember()) { + // The above wording lets us take a very interesting shortcut. Consider a + // protected member function in a class C. It's explicitly stated that its + // contract conditions can't access private members or bases of C, but a + // consequence of ignoring friendship is that it can't access private + // members or bases of *any* class (even those entirely unrelated to C). + // This lets us instantly deny access to targets with stronger access + // restrictions without checking anything about the involved classes. + // In cases where the access levels match, we fall back to the regular + // logic (with one extra restriction in GetFriendKind). + if (Func->getAccess() < Access) + return AR_inaccessible; + } else { + // Without friendship, there's no way for a non-member function to access + // private or protected targets in any class. + return AR_inaccessible; + } + } + AccessResult OnFailure = AR_inaccessible; for (EffectiveContext::record_iterator @@ -1453,11 +1506,13 @@ // void foo(A::private_type); // void B::foo(A::private_type); if (S.DelayedDiagnostics.shouldDelayDiagnostics()) { - S.DelayedDiagnostics.add(DelayedDiagnostic::makeAccess(Loc, Entity)); + S.DelayedDiagnostics.add( + DelayedDiagnostic::makeAccess(Loc, Entity, + S.InContractConditionPredicate)); return Sema::AR_delayed; } - EffectiveContext EC(S.CurContext); + EffectiveContext EC(S.CurContext, S.InContractConditionPredicate); switch (CheckEffectiveAccess(S, EC, Loc, Entity)) { case AR_accessible: return Sema::AR_accessible; case AR_inaccessible: return Sema::AR_inaccessible; @@ -1481,7 +1536,7 @@ DC = cast(TD->getTemplatedDecl()); } - EffectiveContext EC(DC); + EffectiveContext EC(DC, DD.isFromContractCondition()); AccessTarget Target(DD.getAccessData()); @@ -1773,7 +1828,7 @@ // We need to bypass delayed-diagnostics because we might be called // while the ParsingDeclarator is active. - EffectiveContext EC(CurContext); + EffectiveContext EC(CurContext, InContractConditionPredicate); switch (CheckEffectiveAccess(*this, EC, target->getLocation(), entity)) { case ::AR_accessible: return Sema::AR_accessible; case ::AR_inaccessible: return Sema::AR_inaccessible; @@ -1877,7 +1932,7 @@ // Decl->getAccess()) when calculating the access. AccessTarget Entity(Context, AccessedEntity::Member, Class, DeclAccessPair::make(Target, AS_none), qType); - EffectiveContext EC(CurContext); + EffectiveContext EC(CurContext, InContractConditionPredicate); return ::IsAccessible(*this, EC, Entity) != ::AR_inaccessible; } Index: lib/Sema/SemaCast.cpp =================================================================== --- lib/Sema/SemaCast.cpp +++ lib/Sema/SemaCast.cpp @@ -243,7 +243,7 @@ if (getLangOpts().CPlusPlus) { // Check that there are no default arguments (C++ only). - CheckExtraCXXDefaultArguments(D); + CheckCXXNonDeclFunctionRestrictions(D); } return BuildCXXNamedCast(OpLoc, Kind, TInfo, E, Index: lib/Sema/SemaContract.cpp =================================================================== --- /dev/null +++ lib/Sema/SemaContract.cpp @@ -0,0 +1,452 @@ +//===--- SemaContract.cpp - Semantic Analysis for Contracts ---------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "clang/AST/AttrContract.h" +#include "clang/AST/Decl.h" +#include "clang/Basic/Contract.h" +#include "clang/Sema/SemaInternal.h" +#include "clang/Sema/TypoCorrection.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/Support/CastingIterators.h" +#include "TreeTransform.h" +#include + +using namespace clang; + +TypoCorrection Sema::CorrectTypoInContractLevel(IdentifierInfo *GivenIdent, + SourceLocation GivenLoc) { + assert(GivenIdent && "Nothing to correct!"); + DeclarationNameInfo Given(GivenIdent, GivenLoc); + + auto Callback = llvm::make_unique(GivenIdent); + Callback->WantCXXNamedCasts = false; + Callback->WantExpressionKeywords = false; + Callback->WantFunctionLikeCasts = false; + Callback->WantObjCSuper = false; + Callback->WantRemainingKeywords = true; + Callback->WantTypeSpecifiers = false; + + TypoCorrectionConsumer Corrector(*this, Given, LookupOrdinaryName, + /* Scope */nullptr, /* ScopeSpec */nullptr, + std::move(Callback), + /* MemberContext */nullptr, + /* EnteringContext */false); + Corrector.addKeywordResult("default"); + Corrector.addKeywordResult("audit"); + Corrector.addKeywordResult("axiom"); + + return Corrector.empty() + ? FailedCorrection(GivenIdent, GivenLoc, false) + : Corrector.getNextCorrection(); +} + +/// Invoked when we've parsed an identifier that represents the function result +/// object in a postcondition contract. +/// \param ResultType Type of the result variable, if known. If this is +/// supplied, the analysis will require one fewer step. +DeclResult Sema::ActOnContractResultIdentifier(IdentifierInfo *Id, + SourceLocation IdLoc, + QualType ResultTy) { + assert(getCurScope()->isContractAttributeScope()); + + if (ResultTy.isNull() || ResultTy->isUndeducedType()) { + // We don't know what the result type is yet. In the meantime, we'll use a + // placeholder dependent type to allow for initial parsing of the predicate. + ResultTy = Context.DependentTy; + } + + // Temporarily add the variable to the translation unit until we have an + // associated function declaration. + auto *VD = VarDecl::Create(Context, Context.getTranslationUnitDecl(), IdLoc, + IdLoc, Id, ResultTy, /* TInfo */nullptr, SC_None); + VD->setContractFunctionResult(true); + if (ValidateContractResultVariable(VD)) + VD->setInvalidDecl(); + + // Add the variable to scope so it can be used in the predicate. + PushOnScopeChains(VD, getCurScope(), /* AddToContext */false); + + return VD; +} + +Sema::EnterContractPredicateRAII::EnterContractPredicateRAII(Sema &S, + ContractKind K) + : S(S), Entered(true), + OldInContractConditionPredicate(S.InContractConditionPredicate) { + // C++2a [dcl.attr.contract.syn]p5: + // [...] + // Note: The predicate of a contract is potentially evaluated. + S.PushExpressionEvaluationContext( + ExpressionEvaluationContext::PotentiallyEvaluated); + + // Note whether this is a contract condition. + switch (K) { + case ContractKind::Expects: + case ContractKind::Ensures: + S.InContractConditionPredicate = true; + break; + + default: + S.InContractConditionPredicate = false; + } +} + +void Sema::EnterContractPredicateRAII::Exit() { + if (!Entered) + return; + + S.InContractConditionPredicate = OldInContractConditionPredicate; + S.PopExpressionEvaluationContext(); + Entered = false; +} + +/// Invoked when we've parsed a complete contract attribute. +/// \param ResultVar If this is a postcondition, this should be the result +/// object returned from \c ActOnContractResultIdentifier. +ContractResult Sema::ActOnContractAttribute(SourceRange Range, + ContractKind Kind, + SourceLocation KindLoc, + ContractLevel Level, + SourceLocation LevelLoc, + DeclResult ResultVar, + SourceLocation ColonLoc, + ConditionResult Predicate) { + assert(!Predicate.get().first && + "Contract predicate shouldn't declare a variable"); + + return BuildContractAttribute(Range, Kind, KindLoc, Level, LevelLoc, + cast_or_null(ResultVar.get()), + ColonLoc, Predicate.get().second); +} + +/// Build an AST node for a contract attribute. +ContractResult Sema::BuildContractAttribute(SourceRange Range, + ContractKind Kind, + SourceLocation KindLoc, + ContractLevel Level, + SourceLocation LevelLoc, + VarDecl *ResultVar, + SourceLocation ColonLoc, + Expr *Predicate) { + assert(Predicate && "Contract must have a predicate"); + + switch (Kind) { + case ContractKind::Expects: + return new (Context) ExpectsContract(Range, KindLoc, Level, LevelLoc, + ColonLoc, Predicate); + case ContractKind::Ensures: + return new (Context) EnsuresContract(Range, KindLoc, Level, LevelLoc, + ResultVar, ColonLoc, Predicate); + case ContractKind::Assert: + return new (Context) AssertContract(Range, KindLoc, Level, LevelLoc, + ColonLoc, Predicate); + } +} + +/// Invoked after parsing contracts that were initially delayed (e.g. because +/// they're lexically contained in a class). +void Sema::ActOnDelayedContractAttributes(ArrayRef Results, + FunctionDecl *Method) { + if (Results.empty()) + return; + + // We only really need to add the contracts to the method declaration, but the + // contracts are coming from the parser so we need to unwrap them first. + SmallVector Contracts; + Contracts.reserve(Results.size()); + llvm::transform(Results, std::back_inserter(Contracts), + std::mem_fn(&ContractResult::get)); + AttachContractsToFunctionDecl(Contracts, Method); +} + +/// Check if the given variable is suitable for use in a postcondition contract. +/// \returns true if an error was found and diagnosed. +bool Sema::ValidateContractResultVariable(const VarDecl *VD) { + assert(VD->isContractFunctionResult()); + + // Silently fail if the variable is already invalid. + if (VD->isInvalidDecl()) + return true; + + // Check for a valid result type. + if (VD->getType()->isVoidType()) { + Diag(VD->getBeginLoc(), diag::err_contract_result_void); + return true; + } + + return false; +} + +/// Finish processing the contracts and install them on the given function decl. +void Sema::AttachContractsToFunctionDecl(ArrayRef Contracts, + FunctionDecl *FD) { + assert(!Contracts.empty()); + for (auto *C : llvm::make_dyn_cast_range(Contracts)) { + // Set the context of the result variable declarations. + // See 'ActOnContractResultIdentifier' for when this was set originally. + if (C->getResultVar()) + C->getResultVar()->setDeclContext(FD); + } + FD->setContracts(Contracts); +} + +namespace { + +/// Transformation that updates the result type for postconditions in-place. +/// \see Sema::UpdateFunctionContractsWithNewResultType. +class ResultTypeUpdater final : private TreeTransform { +public: + friend TreeTransform; + using TreeTransform::TreeTransform; + + bool Update(ArrayRef Contracts, QualType NewType) { + bool Err = false; + for (auto *Post : llvm::make_dyn_cast_range(Contracts)) { + // Postconditions that don't declare a result variable can't depend on the + // return type in any way, so we can skip them entirely. + CurrentResultVar = Post->getResultVar(); + if (!CurrentResultVar) + continue; + + // Update the result variable with the new type. + CurrentResultVar->setType(NewType); + if (getSema().ValidateContractResultVariable(CurrentResultVar)) + CurrentResultVar->setInvalidDecl(); + + // Transform the predicate. + ExprResult Result = TransformContractPredicate(Post); + + // Update the contract with the new predicate. + if (!Result.isInvalid()) + Post->setPredicate(Result.get()); + else + Err = true; + } + return Err; + } + + ExprResult TransformDeclRefExpr(DeclRefExpr *E) { + // We always want to rebuild expressions that reference the result variable + // because we've just updated its type. + if (E->getDecl() == CurrentResultVar) { + assert(!E->hasExplicitTemplateArgs()); + return RebuildDeclRefExpr(E->getQualifierLoc(), CurrentResultVar, + E->getNameInfo(), /* TemplateArgs */nullptr); + } + + return TreeTransform::TransformDeclRefExpr(E); + } + +private: + VarDecl *CurrentResultVar; +}; + +} // end unnamed namespace + +/// Re-analyse postconditions that reference the function result object. +/// This should be used if the return type wasn't known at the time of parsing. +/// \returns true if an error occurred while updating. +bool Sema::UpdateFunctionContractsWithNewResultType(FunctionDecl *FD) { + const QualType RetTy = FD->getReturnType(); + if(RetTy->isUndeducedType()) + return false; + + ContextRAII FuncCtx(*this, FD); + return ResultTypeUpdater(*this).Update(FD->contracts(), RetTy); +} + +namespace { + +/// Transformation that copies contracts from the original declaration over to a +/// redeclaration that was written without any contracts requirements. +/// \see Sema::MergeContractsOnFunctionRedecl. +class RedeclCopier : private TreeTransform { +public: + using TreeTransform::TreeTransform; + friend TreeTransform; + + void Copy(FunctionDecl *New, FunctionDecl *Old) { + assert(New->contracts().empty() && "Trying to overwrite contracts"); + assert(!Old->isInvalidDecl() && + "Trying to copy contracts from an invalid decl"); + + // Transform old template parameter references to the new ones. + assert(Old->getTemplatedKind() == New->getTemplatedKind()); + if (Old->getTemplatedKind() != FunctionDecl::TK_NonTemplate) { + TemplateParameterList *OldTParams = + Old->getDescribedFunctionTemplate()->getTemplateParameters(); + TemplateParameterList *NewTParams = + New->getDescribedFunctionTemplate()->getTemplateParameters(); + + assert(OldTParams->size() == NewTParams->size()); + for (auto T : llvm::zip_first(*OldTParams, *NewTParams)) + transformedLocalDecl(std::get<0>(T), std::get<1>(T)); + } + + // Transform old parameter references to the new ones. + assert(Old->param_size() == New->param_size()); + for (auto P : llvm::zip_first(Old->parameters(), New->parameters())) + transformedLocalDecl(std::get<0>(P), std::get<1>(P)); + + SmallVector NewContracts; + NewContracts.reserve(Old->contracts().size()); + for (ContractAttr *C : Old->contracts()) { + // If this is a postcondition that declares a result variable, transform + // it to a new variable that lives in the context of the new function. + if (VarDecl *ResultVar = isa(C) + ? cast(C)->getResultVar() + : nullptr) { + auto *NewResultVar = VarDecl::Create(getSema().Context, New, + SourceLocation(), SourceLocation(), + ResultVar->getIdentifier(), + ResultVar->getType(), + ResultVar->getTypeSourceInfo(), + ResultVar->getStorageClass()); + NewResultVar->setContractFunctionResult(true); + + transformedLocalDecl(ResultVar, NewResultVar); + } + + ContractResult Result = TransformContractAttr(C); + assert(Result.isUsable() && "Rebuilding shouldn't fail"); + NewContracts.push_back(Result.get()); + } + New->setContracts(NewContracts); + New->setInheritedContracts(); + } + + QualType TransformTemplateTypeParmType(TypeLocBuilder &TLB, + TemplateTypeParmTypeLoc TL) { + auto Found = TransformedLocalDecls.find(TL.getDecl()); + if (Found != TransformedLocalDecls.end()) { + auto *TDecl = cast(Found->second); + + auto TLoc = TLB.push({ + TDecl->getTypeForDecl(), + TL.getType().getQualifiers().getAsOpaqueValue()}); + TLoc.setNameLoc(TDecl->getLocation()); + + return TLoc.getType(); + } + + return TreeTransform::TransformTemplateTypeParmType(TLB, TL); + } +}; + +} // end unnamed namespace + +/// Validate and merge the contracts on a function redeclaration. +/// \returns true if an error occurred. +bool Sema::MergeContractsOnFunctionRedecl(FunctionDecl *New, + FunctionDecl *OldNotFirst) { + // C++2a [dcl.attr.contract.cond]p1: + // The first declaration of a function shall specify all contract conditions + // (if any) of the function. Subsequent declarations shall either specify no + // contract conditions or the same list of contract conditions; [...] + + // Always compare against the first declaration so the diagnostics highlight + // the most relevant locations. If there're any intermediate declarations, we + // assume that their contracts have already been checked and merged. + FunctionDecl *Old = OldNotFirst->getFirstDecl(); + + // Omitting contracts on a redeclaration is never erroneous. + if (New->contracts().empty()) { + if (!Old->contracts().empty()) { + // Copy over the old contracts. + ContextRAII FuncCtx(*this, New); + RedeclCopier(*this).Copy(New, Old); + } + return false; + } + + bool Invalid = false; + + // Helper to ensure that we emit the error at most once; subsequent + // communication is handled through notes. + auto EmitErr = [&] { + if (Invalid) + return; + Diag(New->getLocation(), diag::err_incompatible_contracts_on_redecl) + << New; + Invalid = true; + }; + + const std::size_t NewCount = New->contracts().size(); + const std::size_t OldCount = Old->contracts().size(); + + // C++2a [dcl.attr.contract.cond]p2: + // Two lists of contract conditions are the same if they consist of the same + // contract conditions in the same order. [...] + for (std::size_t i = 0, End = std::min(NewCount, OldCount); i < End; ++i) { + const ContractAttr *NewContract = New->contracts()[i]; + const ContractAttr *OldContract = Old->contracts()[i]; + const auto Ordinal = static_cast(i + 1); + + // C++2a [dcl.attr.contract.cond]p2: + // [...] Two contract conditions are the same if their contract levels are + // the same and their predicates are the same. [...] + if (NewContract->getKind() != OldContract->getKind() || + NewContract->getLevel() != OldContract->getLevel()) { + EmitErr(); + Diag(NewContract->getKindLoc(), diag::note_redecl_contract_type) + << Ordinal + << static_cast(NewContract->getKind()) + << static_cast(NewContract->getLevel()); + Diag(OldContract->getKindLoc(), diag::note_orig_contract_type) + << static_cast(OldContract->getKind()) + << static_cast(OldContract->getLevel()); + + // Don't bother checking the predicate if the type doesn't match. + continue; + } + + // C++2a [dcl.attr.contract.cond]p2: + // [...] Two predicates contained in contract-attribute-specifiers are the + // same if they would satisfy the one-definition rule were they to appear + // in function definitions, except for renaming of parameters, return + // value identifiers (if any), and template parameters. + llvm::FoldingSetNodeID OldFSN, NewFSN; + OldContract->getPredicate()->Profile(OldFSN, Context, true); + NewContract->getPredicate()->Profile(NewFSN, Context, true); + if (OldFSN != NewFSN) { + EmitErr(); + Diag(NewContract->getPredicate()->getBeginLoc(), + diag::note_redecl_contract_pred) << Ordinal; + Diag(OldContract->getPredicate()->getBeginLoc(), + diag::note_orig_contract_pred); + } + } + + // Check for a size mismatch. + if (NewCount != OldCount) { + const bool NewHasMore = NewCount > OldCount; + + // Get an iterator pair over the range of excess contracts. + ArrayRef::iterator It, End; + if (NewHasMore) { + It = New->contracts().begin(); + End = New->contracts().end(); + std::advance(It, OldCount); + } else { + It = Old->contracts().begin(); + End = Old->contracts().end(); + std::advance(It, NewCount); + } + + // Highlight the missing contracts. + EmitErr(); + auto Builder = Diag((*It)->getBeginLoc(), diag::note_missing_contracts) + << NewHasMore + << static_cast(std::distance(It, End)); + for (; It != End; ++It) + Builder << (*It)->getRange(); + } + + return Invalid; +} Index: lib/Sema/SemaDecl.cpp =================================================================== --- lib/Sema/SemaDecl.cpp +++ lib/Sema/SemaDecl.cpp @@ -15,6 +15,7 @@ #include "clang/AST/ASTConsumer.h" #include "clang/AST/ASTContext.h" #include "clang/AST/ASTLambda.h" +#include "clang/AST/AttrContract.h" #include "clang/AST/CXXInheritance.h" #include "clang/AST/CharUnits.h" #include "clang/AST/CommentDiagnostic.h" @@ -44,6 +45,7 @@ #include "clang/Sema/Template.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/Triple.h" +#include "llvm/Support/CastingIterators.h" #include #include #include @@ -1663,6 +1665,9 @@ if (isa(D)) return true; + if (isa(D) && cast(D)->isContractFunctionResult()) + return true; + // Except for labels, we only care about unused decls that are local to // functions. bool WithinFunction = D->getDeclContext()->isFunctionOrMethod(); @@ -5494,7 +5499,7 @@ // Check that there are no default arguments other than in the parameters // of a function declaration (C++ only). if (getLangOpts().CPlusPlus) - CheckExtraCXXDefaultArguments(D); + CheckCXXNonDeclFunctionRestrictions(D); NamedDecl *New; @@ -8671,9 +8676,12 @@ } } - // Copy the parameter declarations from the declarator D to the function - // declaration NewFD, if they are available. First scavenge them into Params. SmallVector Params; + ArrayRef Contracts; + + // Copy the parameter declarations and contract attributes from the declarator + // D to the function declaration NewFD, if they are available. First scavenge + // them into local storage. unsigned FTIIdx; if (D.isFunctionDeclarator(FTIIdx)) { DeclaratorChunk::FunctionTypeInfo &FTI = D.getTypeObject(FTIIdx).Fun; @@ -8695,6 +8703,12 @@ } } + // Get the contracts from the declarator. + if (FTI.NumParsedContracts > 0) { + Contracts = llvm::makeArrayRef(FTI.ParsedContracts, + FTI.NumParsedContracts); + } + if (!getLangOpts().CPlusPlus) { // In C, find all the tag declarations from the prototype and move them // into the function DeclContext. Remove them from the surrounding tag @@ -8754,6 +8768,15 @@ // Finally, we know we have the right number of parameters, install them. NewFD->setParams(Params); + // Install the contracts. + if (!Contracts.empty()) { + AttachContractsToFunctionDecl(Contracts, NewFD); + + // The result type wasn't known when parsing the declarator. + if (UpdateFunctionContractsWithNewResultType(NewFD)) + NewFD->setInvalidDecl(); + } + if (D.getDeclSpec().isNoreturnSpecified()) NewFD->addAttr( ::new(Context) C11NoReturnAttr(D.getDeclSpec().getNoreturnSpecLoc(), @@ -12301,7 +12324,7 @@ if (getLangOpts().CPlusPlus) { // Check that there are no default arguments inside the type of this // parameter. - CheckExtraCXXDefaultArguments(D); + CheckCXXNonDeclFunctionRestrictions(D); // Parameter declarators cannot be qualified (C++ [dcl.meaning]p1). if (D.getCXXScopeSpec().isSet()) { @@ -12989,7 +13012,7 @@ } else { // Substitute 'void' for the 'auto' in the type. TypeLoc ResultType = getReturnTypeLoc(FD); - Context.adjustDeducedFunctionResultType( + AdjustDeducedFunctionResultType( FD, SubstAutoType(ResultType.getType(), Context.VoidTy)); } } @@ -15039,7 +15062,7 @@ TypeSourceInfo *TInfo = GetTypeForDeclarator(D, S); QualType T = TInfo->getType(); if (getLangOpts().CPlusPlus) { - CheckExtraCXXDefaultArguments(D); + CheckCXXNonDeclFunctionRestrictions(D); if (DiagnoseUnexpandedParameterPack(D.getIdentifierLoc(), TInfo, UPPC_DataMemberType)) { Index: lib/Sema/SemaDeclCXX.cpp =================================================================== --- lib/Sema/SemaDeclCXX.cpp +++ lib/Sema/SemaDeclCXX.cpp @@ -15,6 +15,7 @@ #include "clang/AST/ASTContext.h" #include "clang/AST/ASTLambda.h" #include "clang/AST/ASTMutationListener.h" +#include "clang/AST/AttrContract.h" #include "clang/AST/CXXInheritance.h" #include "clang/AST/CharUnits.h" #include "clang/AST/ComparisonCategories.h" @@ -367,36 +368,36 @@ VK_RValue)); } -/// CheckExtraCXXDefaultArguments - Check for any extra default -/// arguments in the declarator, which is not a function declaration -/// or definition and therefore is not permitted to have default -/// arguments. This routine should be invoked for every declarator -/// that is not a function declaration or definition. -void Sema::CheckExtraCXXDefaultArguments(Declarator &D) { - // C++ [dcl.fct.default]p3 - // A default argument expression shall be specified only in the - // parameter-declaration-clause of a function declaration or in a - // template-parameter (14.1). It shall not be specified for a - // parameter pack. If it is specified in a - // parameter-declaration-clause, it shall not occur within a - // declarator or abstract-declarator of a parameter-declaration. +/// Check the function types in a declarator for things that're only allowed on +/// a function declaration or definition (e.g. default arguments). This routine +/// should be invoked for every declarator that is not a function declaration or +/// definition. +void Sema::CheckCXXNonDeclFunctionRestrictions(Declarator &D) { bool MightBeFunction = D.isFunctionDeclarationContext(); - for (unsigned i = 0, e = D.getNumTypeObjects(); i != e; ++i) { - DeclaratorChunk &chunk = D.getTypeObject(i); - if (chunk.Kind == DeclaratorChunk::Function) { + for (const DeclaratorChunk &Chunk : D.type_objects()) { + switch (Chunk.Kind) { + case DeclaratorChunk::Function: if (MightBeFunction) { - // This is a function declaration. It can have default arguments, but - // keep looking in case its return type is a function type with default - // arguments. + // This is a function declaration; keep looking in case its return type + // or parameter types are function types. MightBeFunction = false; continue; } - for (unsigned argIdx = 0, e = chunk.Fun.NumParams; argIdx != e; - ++argIdx) { - ParmVarDecl *Param = cast(chunk.Fun.Params[argIdx].Param); + + // C++ [dcl.fct.default]p3: + // A default argument expression shall be specified only in the + // parameter-declaration-clause of a function declaration or in a + // template-parameter (14.1). It shall not be specified for a + // parameter pack. If it is specified in a + // parameter-declaration-clause, it shall not occur within a + // declarator or abstract-declarator of a parameter-declaration. + for (DeclaratorChunk::ParamInfo &ParamInfo : + llvm::make_range(Chunk.Fun.Params, + Chunk.Fun.Params + Chunk.Fun.NumParams)) { + auto *Param = cast(ParamInfo.Param); if (Param->hasUnparsedDefaultArg()) { std::unique_ptr Toks = - std::move(chunk.Fun.Params[argIdx].DefaultArgTokens); + std::move(ParamInfo.DefaultArgTokens); SourceRange SR; if (Toks->size() > 1) SR = SourceRange((*Toks)[1].getLocation(), @@ -411,7 +412,23 @@ Param->setDefaultArg(nullptr); } } - } else if (chunk.Kind != DeclaratorChunk::Paren) { + + // C++2a [dcl.attr.contract.cond]p3: + // A function pointer cannot include contract conditions. + if (Chunk.Fun.NumParsedContracts > 0) { + Diag(Chunk.Fun.ParsedContracts[0]->getBeginLoc(), + diag::err_contract_attribute_nonfunc); + } else if (Chunk.Fun.ContractTokens) { + Diag(Chunk.Fun.ContractTokens->front().getLocation(), + diag::err_contract_attribute_nonfunc); + } + + break; + + case DeclaratorChunk::Paren: + break; + + default: MightBeFunction = false; } } @@ -669,17 +686,32 @@ Diag(Old->getLocation(), diag::note_previous_declaration); } - // C++11 [dcl.fct.default]p4: If a friend declaration specifies a default - // argument expression, that declaration shall be a definition and shall be - // the only declaration of the function or function template in the - // translation unit. - if (Old->getFriendObjectKind() == Decl::FOK_Undeclared && - functionDeclHasDefaultArgument(Old)) { - Diag(New->getLocation(), diag::err_friend_decl_with_def_arg_redeclared); - Diag(Old->getLocation(), diag::note_previous_declaration); - Invalid = true; + if (Old->getFriendObjectKind() == Decl::FOK_Undeclared) { + // C++11 [dcl.fct.default]p4: + // If a friend declaration specifies a default argument expression, that + // declaration shall be a definition and shall be the only declaration of + // the function or function template in the translation unit. + if (functionDeclHasDefaultArgument(Old)) { + Diag(New->getLocation(), diag::err_friend_decl_with_def_arg_redeclared); + Diag(Old->getLocation(), diag::note_previous_declaration); + Invalid = true; + } + + // C++2a [dcl.attr.contract.cond]p1: + // If a friend declaration is the first declaration of the function in a + // translation unit and has a contract condition, the declaration shall be + // a definition and shall be the only declaration of the function in the + // translation unit. + if (!Old->contracts().empty()) { + Diag(New->getLocation(), diag::err_friend_decl_with_contracts_redeclared); + Diag(Old->getLocation(), diag::note_previous_declaration); + Invalid = true; + } } + // Merge contracts if we haven't encountered an error yet. + Invalid = Invalid || MergeContractsOnFunctionRedecl(New, Old); + return Invalid; } @@ -14426,6 +14458,30 @@ Diag(FD->getLocation(), diag::err_friend_decl_with_def_arg_must_be_def); } + // C++2a [dcl.attr.contract.cond]p1: + // If a friend declaration is the first declaration of the function in a + // translation unit and has a contract condition, the declaration shall be + // a definition and shall be the only declaration of the function in the + // translation unit. + // + // Late-parsing hasn't happened yet, so we also need to check the declarator + // for any unparsed tokens. + { + unsigned Idx; + if (!FD->contracts().empty() || + (D.isFunctionDeclarator(Idx) && + D.getTypeObject(Idx).Fun.ContractTokens)) { + if (D.isRedeclaration()) { + Diag(FD->getLocation(), + diag::err_friend_decl_with_contracts_redeclared); + Diag(Previous.getRepresentativeDecl()->getLocation(), + diag::note_previous_declaration); + } else if (!D.isFunctionDefinition()) + Diag(FD->getLocation(), + diag::err_friend_decl_with_contracts_must_be_def); + } + } + // Mark templated-scope function declarations as unsupported. if (FD->getNumTemplateParameterLists() && SS.isValid()) { Diag(FD->getLocation(), diag::warn_template_qualified_friend_unsupported) @@ -15415,7 +15471,7 @@ TypeSourceInfo *TInfo = GetTypeForDeclarator(D, S); QualType T = TInfo->getType(); if (getLangOpts().CPlusPlus) { - CheckExtraCXXDefaultArguments(D); + CheckCXXNonDeclFunctionRestrictions(D); if (DiagnoseUnexpandedParameterPack(D.getIdentifierLoc(), TInfo, UPPC_DataMemberType)) { Index: lib/Sema/SemaDeclObjC.cpp =================================================================== --- lib/Sema/SemaDeclObjC.cpp +++ lib/Sema/SemaDeclObjC.cpp @@ -4903,7 +4903,7 @@ // Check that there are no default arguments inside the type of this // exception object (C++ only). if (getLangOpts().CPlusPlus) - CheckExtraCXXDefaultArguments(D); + CheckCXXNonDeclFunctionRestrictions(D); TypeSourceInfo *TInfo = GetTypeForDeclarator(D, S); QualType ExceptionType = TInfo->getType(); Index: lib/Sema/SemaExpr.cpp =================================================================== --- lib/Sema/SemaExpr.cpp +++ lib/Sema/SemaExpr.cpp @@ -6153,7 +6153,7 @@ if (getLangOpts().CPlusPlus) { // Check that there are no default arguments (C++ only). - CheckExtraCXXDefaultArguments(D); + CheckCXXNonDeclFunctionRestrictions(D); } else { // Make sure any TypoExprs have been dealt with. ExprResult Res = CorrectDelayedTyposInExpr(CastExpr); Index: lib/Sema/SemaStmt.cpp =================================================================== --- lib/Sema/SemaStmt.cpp +++ lib/Sema/SemaStmt.cpp @@ -15,6 +15,7 @@ #include "clang/AST/ASTContext.h" #include "clang/AST/ASTDiagnostic.h" #include "clang/AST/ASTLambda.h" +#include "clang/AST/AttrContract.h" #include "clang/AST/CharUnits.h" #include "clang/AST/CXXInheritance.h" #include "clang/AST/DeclObjC.h" @@ -66,8 +67,24 @@ } StmtResult Sema::ActOnNullStmt(SourceLocation SemiLoc, - bool HasLeadingEmptyMacro) { - return NullStmt::Create(Context, SemiLoc, HasLeadingEmptyMacro); + bool HasLeadingEmptyMacro, + ArrayRef Contracts) { + // Transform the contracts array from the parser's representation to an actual + // array of assertions. + SmallVector Assertions; + Assertions.reserve(Contracts.size()); + llvm::transform(Contracts, std::back_inserter(Assertions), + [](ContractResult C) { + // Force cast; we shouldn't have any other contract kind here. + return cast(C.get()); + }); + + return BuildNullStmt(SemiLoc, HasLeadingEmptyMacro, Assertions); +} + +StmtResult Sema::BuildNullStmt(SourceLocation SemiLoc, bool HasLeadingEmptyMacro, + ArrayRef Assertions) { + return NullStmt::Create(Context, SemiLoc, HasLeadingEmptyMacro, Assertions); } StmtResult Sema::ActOnDeclStmt(DeclGroupPtrTy dg, SourceLocation StartLoc, @@ -3431,7 +3448,7 @@ } } else if (!FD->isInvalidDecl()) { // Update all declarations of the function to have the deduced return type. - Context.adjustDeducedFunctionResultType(FD, Deduced); + AdjustDeducedFunctionResultType(FD, Deduced); } return false; Index: lib/Sema/SemaTemplateDeduction.cpp =================================================================== --- lib/Sema/SemaTemplateDeduction.cpp +++ lib/Sema/SemaTemplateDeduction.cpp @@ -4626,7 +4626,7 @@ assert(FD->getReturnType()->getAs()); RetType = Context.getBlockPointerType(RetType); } - Context.adjustDeducedFunctionResultType(FD, RetType); + AdjustDeducedFunctionResultType(FD, RetType); return false; } @@ -4642,6 +4642,42 @@ return StillUndeduced; } +void Sema::AdjustDeducedFunctionResultType(FunctionDecl *FD, + QualType ResultType) { + // Get ASTContext to change the function type. + Context.adjustDeducedFunctionResultType(FD, ResultType); + + auto It = FD->getFirstDecl()->redecls_begin(); + const auto End = FD->getFirstDecl()->redecls_end(); + + // We're only dealing with contracts past this point. + if ((*It)->contracts().empty()) + return; + + // Start by updating the first declaration as that's the most relevant + // location for diagnostics; subsequent redeclarations might not even have any + // contracts written in the source. + if (UpdateFunctionContractsWithNewResultType(*It)) { + // If that didn't work, give up and mark everything as invalid. + for (; It != End; ++It) + (*It)->setInvalidDecl(); + return; + } + + // Update the remaining declarations. + ++It; + for (; It != End; ++It) { + if ((*It)->isInvalidDecl()) + continue; + + // The contracts should be exactly the same as the first declaration. + // If these fail then it means that there's a problem with the contract + // comparison/validation handler. + bool Ok = UpdateFunctionContractsWithNewResultType(*It); + assert(Ok && "Updating redeclarations shouldn't fail"); + } +} + /// If this is a non-static member function, static void AddImplicitObjectParameterType(ASTContext &Context, Index: lib/Sema/SemaTemplateInstantiate.cpp =================================================================== --- lib/Sema/SemaTemplateInstantiate.cpp +++ lib/Sema/SemaTemplateInstantiate.cpp @@ -1951,6 +1951,28 @@ return Invalid; } +bool Sema::SubstContracts(FunctionDecl *New, const FunctionDecl *Tmpl, + const MultiLevelTemplateArgumentList &TemplateArgs) { + bool Invalid = false; + + const SourceLocation Loc = Tmpl->getTypeSourceInfo()->getTypeLoc().getEndLoc(); + TemplateInstantiator Instantiator(*this, TemplateArgs, Loc, + /* Entity */DeclarationName()); + + SmallVector Contracts; + Contracts.reserve(Tmpl->contracts().size()); + for (ContractAttr *C : Tmpl->contracts()) { + ContractResult Result = Instantiator.TransformContractAttr(C); + if (Result.isUsable()) + Contracts.push_back(Result.get()); + else + Invalid = true; + } + New->setContracts(Contracts); + + return Invalid; +} + // Defined via #include from SemaTemplateInstantiateDecl.cpp namespace clang { namespace sema { Index: lib/Sema/SemaTemplateInstantiateDecl.cpp =================================================================== --- lib/Sema/SemaTemplateInstantiateDecl.cpp +++ lib/Sema/SemaTemplateInstantiateDecl.cpp @@ -3669,6 +3669,11 @@ } } + if (!Tmpl->contracts().empty()) { + Sema::ContextRAII SwitchContext(SemaRef, New); + SemaRef.SubstContracts(New, Tmpl, TemplateArgs); + } + // Get the definition. Leaves the variable unchanged if undefined. const FunctionDecl *Definition = Tmpl; Tmpl->isDefined(Definition); @@ -3687,10 +3692,14 @@ bool TemplateDeclInstantiator::InitMethodInstantiation(CXXMethodDecl *New, CXXMethodDecl *Tmpl) { + // We need to set the access first because contract condition instantiation, + // which is part of InitFunctionInstantiation, depends on it. + // FIXME: Is there a better way to structure this? + New->setAccess(Tmpl->getAccess()); + if (InitFunctionInstantiation(New, Tmpl)) return true; - New->setAccess(Tmpl->getAccess()); if (Tmpl->isVirtualAsWritten()) New->setVirtualAsWritten(true); Index: lib/Sema/SemaType.cpp =================================================================== --- lib/Sema/SemaType.cpp +++ lib/Sema/SemaType.cpp @@ -5710,7 +5710,7 @@ if (getLangOpts().CPlusPlus) { // Check that there are no default arguments (C++ only). - CheckExtraCXXDefaultArguments(D); + CheckCXXNonDeclFunctionRestrictions(D); } return CreateParsedType(T, TInfo); Index: lib/Sema/TreeTransform.h =================================================================== --- lib/Sema/TreeTransform.h +++ lib/Sema/TreeTransform.h @@ -16,6 +16,7 @@ #include "CoroutineStmtBuilder.h" #include "TypeLocBuilder.h" +#include "clang/AST/AttrContract.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclObjC.h" #include "clang/AST/DeclTemplate.h" @@ -668,6 +669,16 @@ StmtResult TransformOMPExecutableDirective(OMPExecutableDirective *S); + /// Transform the given contract attribute. + /// + /// By default, this routine transforms a contract by delegating to the + /// appropriate TransformXXXContract function to build a new contract. + /// Subclasses may override this function to transform contracts using some + /// other mechanism. + /// + /// \returns the transformed contract. + ContractResult TransformContractAttr(ContractAttr *C); + // FIXME: We use LLVM_ATTRIBUTE_NOINLINE because inlining causes a ridiculous // amount of stack usage with clang. #define STMT(Node, Parent) \ @@ -684,6 +695,19 @@ OMPClause *Transform ## Class(Class *S); #include "clang/Basic/OpenMPKinds.def" + /// Transform the specified contract attribute. + /// + /// By default, this transforms the predicate and result variable (if + /// applicable) and rebuilds the contract. + /// @{ + ContractResult TransformExpectsContract(ExpectsContract *C); + ContractResult TransformEnsuresContract(EnsuresContract *C); + ContractResult TransformAssertContract(AssertContract *C); + /// @} + + /// Transform the predicate of the specified contract attribute. + ExprResult TransformContractPredicate(ContractAttr *C); + /// Build a new qualified type given its unqualified type and type /// qualifiers. /// @@ -1187,6 +1211,15 @@ return getSema().Context.getSubstTemplateTemplateParmPack(Param, ArgPack); } + /// Build a new null statement. + /// + /// By default, performs semantic analysis to build the new statement. + /// Subclasses may override this routine to provide different behavior. + StmtResult RebuildNullStmt(SourceLocation SemiLoc, bool HasLeadingEmptyMacro, + ArrayRef Assertions) { + return getSema().BuildNullStmt(SemiLoc, HasLeadingEmptyMacro, Assertions); + } + /// Build a new compound statement. /// /// By default, performs semantic analysis to build the new statement. @@ -3243,6 +3276,23 @@ RParenLoc); } + /// Build a new contract attribute. + /// + /// By default, performs semantic analysis to build the new contract. + /// Subclasses may override this routine to provide different behavior. + ContractResult RebuildContractAttr(SourceRange Range, + ContractKind Kind, + SourceLocation KindLoc, + ContractLevel Level, + SourceLocation LevelLoc, + VarDecl *ResultVar, + SourceLocation ColonLoc, + Expr *Predicate) { + return getSema().BuildContractAttribute( + Range, Kind, KindLoc, Level, LevelLoc, ResultVar, + ColonLoc, Predicate); + } + private: TypeLoc TransformTypeInObjectScope(TypeLoc TL, QualType ObjectType, @@ -6461,7 +6511,32 @@ template StmtResult TreeTransform::TransformNullStmt(NullStmt *S) { - return S; + bool AssertionInvalid = false; + bool AssertionChanged = false; + + SmallVector Assertions; + Assertions.reserve(llvm::size(S->assertions())); + for (AssertContract *C : S->assertions()) { + ContractResult Result = getDerived().TransformContractAttr(C); + + if (Result.isInvalid()) { + AssertionInvalid = true; + continue; + } + + AssertionChanged = AssertionChanged || Result.get() != C; + Assertions.push_back(cast(Result.get())); + } + + if (AssertionInvalid) + return StmtError(); + + if (!getDerived().AlwaysRebuild() && !AssertionChanged) + return S; + + return getDerived().RebuildNullStmt(S->getSemiLoc(), + S->hasLeadingEmptyMacro(), + Assertions); } template @@ -12210,6 +12285,105 @@ RetTy, E->getOp(), E->getRParenLoc()); } +//===----------------------------------------------------------------------===// +// Contract transformation +//===----------------------------------------------------------------------===// + +template +ContractResult TreeTransform::TransformContractAttr(ContractAttr *C) { + if (!C) + return C; + + if (auto *Expects = dyn_cast(C)) + return TransformExpectsContract(Expects); + if (auto *Ensures = dyn_cast(C)) + return TransformEnsuresContract(Ensures); + if (auto *Assert = dyn_cast(C)) + return TransformAssertContract(Assert); + + llvm_unreachable("Unexpected contract kind"); +} + +template +ContractResult +TreeTransform::TransformExpectsContract(ExpectsContract *C) { + // Transform the predicate. + ExprResult Pred = getDerived().TransformContractPredicate(C); + if (Pred.isInvalid()) + return ContractResult(/* Invalid */true); + + if (!getDerived().AlwaysRebuild() && Pred.get() == C->getPredicate()) + return C; + + return getDerived().RebuildContractAttr( + C->getRange(), ContractKind::Expects, C->getKindLoc(), + C->getLevel(), C->getLevelLoc(), /* ResultVar */nullptr, + C->getColonLoc(), Pred.get()); +} + +template +ContractResult +TreeTransform::TransformEnsuresContract(EnsuresContract *C) { + // Transform the result variable. + VarDecl *ResultVar = C->getResultVar(); + if (ResultVar) { + ResultVar = cast_or_null( + getDerived().TransformDecl(ResultVar->getLocation(), + ResultVar)); + if (!ResultVar) + return ContractResult(/* Invalid */true); + + ResultVar->setContractFunctionResult(true); + if (getSema().ValidateContractResultVariable(ResultVar)) + ResultVar->setInvalidDecl(); + } + + // Transform the predicate. + ExprResult Pred = getDerived().TransformContractPredicate(C); + if (Pred.isInvalid()) + return ContractResult(/* Invalid */true); + + if (!getDerived().AlwaysRebuild() && + ResultVar == C->getResultVar() && + Pred.get() == C->getPredicate()) + return C; + + return getDerived().RebuildContractAttr( + C->getRange(), ContractKind::Ensures, C->getKindLoc(), + C->getLevel(), C->getLevelLoc(), ResultVar, + C->getColonLoc(), Pred.get()); +} + +template +ContractResult +TreeTransform::TransformAssertContract(AssertContract *C) { + // Transform the predicate. + ExprResult Pred = getDerived().TransformContractPredicate(C); + if (Pred.isInvalid()) + return ContractResult(/* Invalid */true); + + if (!getDerived().AlwaysRebuild() && Pred.get() == C->getPredicate()) + return C; + + return getDerived().RebuildContractAttr( + C->getRange(), ContractKind::Assert, C->getKindLoc(), + C->getLevel(), C->getLevelLoc(), /* ResultVar */nullptr, + C->getColonLoc(), Pred.get()); +} + +template +ExprResult +TreeTransform::TransformContractPredicate(ContractAttr *C) { + Sema::EnterContractPredicateRAII _(getSema(), C->getKind()); + + Sema::ConditionResult Result = + getDerived().TransformCondition(C->getKindLoc(), /* Var */nullptr, + C->getPredicate(), + Sema::ConditionKind::Boolean); + + return (Result.isInvalid() ? ExprError() : Result.get().second); +} + //===----------------------------------------------------------------------===// // Type reconstruction //===----------------------------------------------------------------------===// Index: test/CXX/dcl.dcl/dcl.attr/dcl.attr.contract/dcl.attr.contract.cond/p1.cpp =================================================================== --- /dev/null +++ test/CXX/dcl.dcl/dcl.attr/dcl.attr.contract/dcl.attr.contract.cond/p1.cpp @@ -0,0 +1,86 @@ +// RUN: %clang_cc1 -fsyntax-only -std=c++2a -verify %s + +namespace no_contracts_on_redecl { + int f(int x, int y) + [[expects: x != 0]] + [[expects: y != 0]] + [[ensures r: r != x && r != y]]; + + // Redeclarations should be writable without any contract conditions. + int f(int x, int y); + int f(int x, int y) { return 0; } +} + +namespace overloading_is_ok { + int f(int x, int y); + + // This is an overload, not the same function. So there shouldn't be any + // comparisons made between its contracts and the above function. + int f(float x, int y) + [[expects: x != 0.003f]]; +} + +namespace no_contracts_on_first_decl { + // Singular. + int f(int x, int y); + int f(int x, int y) // expected-error {{redeclaration of 'f' with different contract conditions}} + [[expects: x != 0]]; // expected-note {{redeclaration has this additional contract}} + + // Plural. + int g(int x, int y); + int g(int x, int y) // expected-error {{redeclaration of 'g' with different contract conditions}} + [[expects: x != 0]] // expected-note {{redeclaration has these 2 additional contracts}} + [[expects: y != 0]]; +} + +namespace different_type { + int f(int x, int y) + [[expects: true]] // expected-note {{whereas on the original declaration it's a regular precondition}} + [[ensures: false]] + [[ensures audit: false]]; // expected-note {{whereas on the original declaration it's an auditing postcondition}} + + int f(int x, int y) // expected-error {{redeclaration of 'f' with different contract conditions}} + [[ensures: true]] // expected-note {{the 1st contract is a regular postcondition}} + [[ensures default: false]] + [[ensures axiom: false]]; // expected-note {{the 3rd contract is an axiomatic postcondition}} +} + +namespace different_predicate { + int f(int x, int y) + [[ensures r: r == x + y]]; // expected-note {{is different from the original declaration here}} + + int f(int x, int y) // expected-error {{redeclaration of 'f' with different contract conditions}} + [[ensures r: r == 50]]; // expected-note {{the predicate of the 1st contract}} +} + +namespace mixed { + template + T f(T x, T y) + [[expects: true]] + [[expects audit: x == y]] // expected-note {{whereas on the original declaration it's an auditing precondition}} + [[expects axiom: x == y * y]]; // expected-note {{is different from the original declaration here}} + + template + T f(T x, T y) // expected-error {{redeclaration of 'f' with different contract conditions}} + [[expects default: true]] + [[ensures axiom: y == x]] // expected-note {{the 2nd contract is an axiomatic postcondition}} + [[expects axiom: x == y + y]] // expected-note {{the predicate of the 3rd contract}} + [[ensures audit: false]]; // expected-note {{redeclaration has this additional contract}} +} + +namespace friends_with_contracts { + class MyClass { + friend void f(int x) // expected-note {{previous declaration is here}} + [[expects: x == 10]] {} + + template + friend void g(T x) // expected-note {{previous declaration is here}} + [[expects: sizeof(x) == 500]] {} + + friend void h() // expected-error {{friend declaration specifying contract conditions must be a definition}} + [[expects: true]]; + }; + + void f(int x); // expected-error {{friend declaration specifying contract conditions must be the only declaration}} + template void g(T x); // expected-error {{friend declaration specifying contract conditions must be the only declaration}} +} Index: test/CXX/dcl.dcl/dcl.attr/dcl.attr.contract/dcl.attr.contract.cond/p2.cpp =================================================================== --- /dev/null +++ test/CXX/dcl.dcl/dcl.attr/dcl.attr.contract/dcl.attr.contract.cond/p2.cpp @@ -0,0 +1,24 @@ +// RUN: %clang_cc1 -fsyntax-only -std=c++2a -verify %s +// expected-no-diagnostics + +int f(int x, int y) + [[expects: x != 0]] + [[expects: y != 0]] + [[ensures r: r != x && r != y]]; + +int f(int a, int b) + [[expects: a != 0]] + [[expects: b != 0]] + [[ensures res: res != a && res != b]]; + +template +int g(T1 x, T2 y) + [[expects: x != 0]] + [[expects: y != 0]] + [[ensures r: r != x && r != y]]; + +template +int g(First a, Second b) + [[expects: a != 0]] + [[expects: b != 0]] + [[ensures res: res != a && res != b]]; Index: test/CXX/dcl.dcl/dcl.attr/dcl.attr.contract/dcl.attr.contract.cond/p3.cpp =================================================================== --- /dev/null +++ test/CXX/dcl.dcl/dcl.attr/dcl.attr.contract/dcl.attr.contract.cond/p3.cpp @@ -0,0 +1,16 @@ +// RUN: %clang_cc1 -fsyntax-only -std=c++2a -verify %s + +typedef int (*MyType1)() [[ensures r: r != 0]]; // expected-error {{contract conditions can only be specified on a function declaration}} + +using MyType2 = void(*)() [[expects: true]]; // expected-error {{contract conditions can only be specified on a function declaration}} + +float (*f)(int x) [[ensures r: r == (float)x]]; // expected-error {{contract conditions can only be specified on a function declaration}} + +bool (*g())(int) [[expects: true]]; // expected-error {{contract conditions can only be specified on a function declaration}} + +bool (*h() [[expects: true]])(int); + +class MyClass { + // Make sure that this isn't eaten by late-parsing. + void (*member)() [[expects: true]]; // expected-error {{contract conditions can only be specified on a function declaration}} +}; Index: test/CXX/dcl.dcl/dcl.attr/dcl.attr.contract/dcl.attr.contract.cond/p4.cpp =================================================================== --- /dev/null +++ test/CXX/dcl.dcl/dcl.attr/dcl.attr.contract/dcl.attr.contract.cond/p4.cpp @@ -0,0 +1,103 @@ +// RUN: %clang_cc1 -fsyntax-only -std=c++2a -verify %s + +class Standalone { +public: + void fromPub() + [[expects: pubMem]] + [[expects: protMem]] // expected-error {{'protMem' is a protected member}} + [[expects: privMem]]; // expected-error {{'privMem' is a private member}} + +protected: + void fromProt() + [[expects: pubMem]] + [[expects: protMem]] + [[expects: privMem]]; // expected-error {{'privMem' is a private member}} + +private: + void fromPriv() + [[expects: pubMem]] + [[expects: protMem]] + [[expects: privMem]]; + + public: bool pubMem; + protected: bool protMem; // expected-note {{declared protected here}} + private: bool privMem; // expected-note 2{{declared private here}} +}; + +namespace friendly { + class C { + friend class Friendly; + friend void f(const C &x) + [[expects: x.pubPred()]] + [[expects: x.protPred()]] // expected-error {{'protPred' is a protected member}} + [[expects: x.privPred()]] // expected-error {{'privPred' is a private member}} + {} + + public: bool pubPred() const; + protected: bool protPred() const; // expected-note 2{{declared protected here}} + private: bool privPred() const; // expected-note 2{{declared private here}} + }; + + class Friendly { + void f(C &x) + [[ensures: x.pubPred()]] + [[ensures: x.protPred()]] // expected-error {{'protPred' is a protected member}} + [[ensures: x.privPred()]]; // expected-error {{'privPred' is a private member}} + }; +} + +namespace inaccessible_base { + class Base { + public: + bool pred() const; // expected-note {{member is declared here}} + }; + + class Derived : private Base { // expected-note {{constrained by private inheritance here}} \ + // expected-note {{declared private here}} + public: + void doStuff() + [[ensures: pred()]]; // expected-error {{'pred' is a private member}} \ + // expected-error {{to its private base class}} + }; +} + +namespace tmpl_instantiation { + class C { + int i; // expected-note {{implicitly declared private here}} + + public: + template + void f(const T &x) + [[expects: x.i > 9000]] // expected-error {{'i' is a private member}} + {} + }; + + template void C::f(const C &); // expected-note {{in instantiation}} +} + +namespace assertions_arent_conditions { + // A contract condition is a precondition or a postcondition. + // So the additional access restrictions shouldn't apply to assertions. + + class C { + bool pred() const; + + public: + void f() { + [[assert: pred()]]; + } + + friend class FriendClass; + + friend void friendFunc(const C &x) { + [[assert: x.pred()]]; + } + }; + + class FriendClass { + public: + void f(const C &x) { + [[assert: x.pred()]]; + } + }; +} Index: test/CXX/expr/expr.const/p2-2a.cpp =================================================================== --- /dev/null +++ test/CXX/expr/expr.const/p2-2a.cpp @@ -0,0 +1,45 @@ +// RUN: %clang_cc1 -std=c++2a -fsyntax-only -verify %s + +// - A checked contract (10.6.11) whose predicate evaluates to false. + +constexpr int f(int i) + [[expects: i != 0]] // expected-note {{precondition failed}} + [[ensures r: r == i]] +{ + return i; +} +static_assert(f(42) == 42); +static_assert(f(0) == -1); // expected-error {{not an integral constant expression}} expected-note{{in call}} + +namespace never { + constexpr void f() // expected-error {{never produces a constant expression}} + [[expects: false]] // expected-note {{precondition is unsatisfiable}} + {} + + constexpr int g() // expected-error {{never produces a constant expression}} + [[ensures r: r == 66]] // expected-note {{postcondition is unsatisfiable}} + { + return 0; + } + + constexpr void h() // expected-error {{never produces a constant expression}} + { + [[assert: true]]; + [[assert: 10 == 100]]; // expected-note {{assertion is unsatisfiable}} + } +} + +class CheckedConstructor { +public: + constexpr explicit CheckedConstructor() // expected-error {{never produces a constant expression}} + [[ensures: false]] // expected-note {{postcondition is unsatisfiable}} + {} + + constexpr explicit CheckedConstructor(int i) + [[expects: i != 0]] // expected-note {{precondition failed}} + {} + + constexpr explicit operator bool() const { return true; } +}; +static_assert(CheckedConstructor(14)); +static_assert(CheckedConstructor(0)); // expected-error {{not an integral constant expression}} expected-note {{in call}} Index: test/CodeGenCXX/cxx2a-contracts-unsupported.cpp =================================================================== --- /dev/null +++ test/CodeGenCXX/cxx2a-contracts-unsupported.cpp @@ -0,0 +1,10 @@ +// RUN: %clang_cc1 -std=c++2a -emit-llvm -verify %s + +// Test that CodeGen emits an error when attempting to compile contracts. +// This should be deleted when the contracts implementation is complete. + +void f() // expected-error {{cannot compile this}} + [[expects: true]] +{ + [[assert: true]]; // expected-error {{cannot compile this}} +} Index: test/FixIt/fixit-cxx-contract.cpp =================================================================== --- /dev/null +++ test/FixIt/fixit-cxx-contract.cpp @@ -0,0 +1,32 @@ +// RUN: %clang_cc1 -std=c++2a -verify %s +// RUN: not %clang_cc1 -std=c++2a -fdiagnostics-parseable-fixits %s 2>&1 | FileCheck %s +// RUN: cp %s %t +// RUN: not %clang_cc1 -std=c++2a -fixit -x c++ %t +// RUN: %clang_cc1 -std=c++2a -x c++ %t + +int colonRecovery(int i) + [[expects true]] // expected-error {{expected ':'}} + // CHECK: fix-it:{{.*}}:{[[@LINE-1]]:12-[[@LINE-1]]:12}:":" + + [[expects audit true]] // expected-error {{expected ':'}} + // CHECK: fix-it:{{.*}}:{[[@LINE-1]]:18-[[@LINE-1]]:18}:":" + + [[ensures r r > 100]] // expected-error {{expected ':'}} + // CHECK: fix-it:{{.*}}:{[[@LINE-1]]:14-[[@LINE-1]]:14}:":" + + [[ensures axiom r r > i]]; // expected-error {{expected ':'}} + // CHECK: fix-it:{{.*}}:{[[@LINE-1]]:20-[[@LINE-1]]:20}:":" + +void semicolonRecovery() +{ + [[assert: false]] // expected-error {{expected ';' after assertion}} + // CHECK: fix-it:{{.*}}:{[[@LINE-1]]:20-[[@LINE-1]]:20}:";" + + for (int i = 0; i < 5; ++i) { + [[assert: i < 100]] // expected-error {{expected ';' after assertion}} + // CHECK: fix-it:{{.*}}:{[[@LINE-1]]:24-[[@LINE-1]]:24}:";" + } + + [[assert: true]] // expected-error {{expected ';' after assertion}} + // CHECK: fix-it:{{.*}}:{[[@LINE-1]]:19-[[@LINE-1]]:19}:";" +} Index: test/Parser/cxx-contract.cpp =================================================================== --- /dev/null +++ test/Parser/cxx-contract.cpp @@ -0,0 +1,90 @@ +// RUN: %clang_cc1 -std=c++2a -fsyntax-only -Wno-unknown-attributes -verify %s + +int addPositive(int a, int b) + [[expects: a > 0]] + [[expects: b > 0]] + [[ensures r: r > a]] + [[ensures r: r > b]] + [[ensures axiom r: r == a + b]] +{ + int result = a; + for (int i = 0; i < b; ++i) { + ++result; + [[assert audit: result > a]]; + } + return result; +} + +void tryAllLevels() + [[expects: true]] + [[ensures: true]] + [[expects default: true]] + [[ensures default: true]] + [[expects audit: true]] + [[ensures audit: true]] + [[expects axiom: true]] + [[ensures axiom: true]] +{ + [[assert: true]]; + [[assert default: true]]; + [[assert audit: true]]; + [[assert axiom: true]]; +} + +void invalidLevels() + // We should be able to use the surrounding tokens to infer that 'not_a_level' + // was inteded as a contract level. + [[expects not_a_level: true]] // expected-error {{unknown contract level 'not_a_level'}} + + // But in this case it's probably part of the predicate (with a missing colon). + [[expects not_a_level]]; // expected-error {{expected ':'}} \ + // expected-error {{undeclared identifier 'not_a_level'}} + +void badBrackets() +{ + [[assert: true]]]; // expected-error {{extraneous ']' before ';'}} + [[assert: true]; // expected-error {{expected ']'}} expected-note {{to match this '['}} +} + +int missingPredicate() + [[expects:]] // expected-error {{expected expression}} + [[ensures axiom r:]] // expected-error {{expected expression}} +{ + [[assert audit:]]; // expected-error {{expected expression}} + return 5; +} + +void wrongPlacement() + [[assert: true]] // expected-error {{'assert' attribute is only allowed on an empty statement}} +{ + [[expects: true]]; // expected-error {{'expects' attribute is only allowed on a function type}} + [[ensures: true]]; // expected-error {{'ensures' attribute is only allowed on a function type}} +} + +void sharingList(int i) + [[myattr, expects: true]] // expected-error {{contract attributes must be the only}} + [[myattr1, myattr, ensures: false]] // expected-error {{contract attributes must be the only}} +{ + switch (i) { + case 0: + [[fallthrough, assert: true]]; // expected-error {{contract attributes must be the only}} + + default: + break; + } +} + +void multiAssert(int i) +{ + // This isn't explicitly mentioned in the standard, however the grammar + // implies that it's allowed. + [[assert: i != 2]] [[assert: i != 3]] [[assert: i != 4]]; +} + +class DelayMemberParsing { +public: + void f(int arg) [[expects: arg != i]]; + void g(int arg) [[expects: arg != j]]; // expected-error {{use of undeclared identifier 'j'}} + + int i; +}; Index: test/SemaCXX/typo-correction-contract.cpp =================================================================== --- /dev/null +++ test/SemaCXX/typo-correction-contract.cpp @@ -0,0 +1,10 @@ +// RUN: %clang_cc1 -std=c++2a -fsyntax-only -verify %s + +int f() + [[expects flooat: true]] // expected-error {{unknown contract level 'flooat'}} + [[expects defaul: true]] // expected-error {{unknown contract level 'defaul'; did you mean 'default'?}} + [[ensures audio r: true]] // expected-error {{unknown contract level 'audio'; did you mean 'audit'?}} +{ + [[assert axiomm: true]]; // expected-error {{unknown contract level 'axiomm'; did you mean 'axiom'?}} + return 0; +} Index: test/SemaCXX/warn-unused-result-contract.cpp =================================================================== --- /dev/null +++ test/SemaCXX/warn-unused-result-contract.cpp @@ -0,0 +1,20 @@ +// RUN: %clang_cc1 -std=c++2a -fsyntax-only -Wunused-variable -verify %s + +namespace at_ns_scope { + int f() [[ensures r: r != 0]]; + int g() [[ensures r: true]]; // expected-warning {{unused variable 'r'}} +} + +class AtClassScope { +public: + int f() [[ensures r: r != 0]]; + int g() [[ensures r: true]]; // expected-warning {{unused variable 'r'}} +}; + +namespace templated_funcs { + template + T f() [[ensures r: r != 0]]; + + template + T g() [[ensures r: true]]; // expected-warning {{unused variable 'r'}} +}