From dd5ce677444966ead70d97264440c82b629fd393 Mon Sep 17 00:00:00 2001 From: Eric Fiselier Date: Wed, 19 Jun 2024 14:09:06 -0500 Subject: [PATCH 1/9] Add contracts representation and parsing. Much thanks to Peter for the initial implementation --- clang/include/clang/AST/Decl.h | 35 ++- clang/include/clang/AST/DeclCXX.h | 41 +++- clang/include/clang/AST/RecursiveASTVisitor.h | 3 + clang/include/clang/AST/Stmt.h | 19 ++ clang/include/clang/AST/StmtCXX.h | 99 +++++++++ clang/include/clang/Basic/Builtins.td | 14 ++ .../include/clang/Basic/DiagnosticASTKinds.td | 2 + clang/include/clang/Basic/LangOptions.def | 1 + clang/include/clang/Basic/LangOptions.h | 37 +++ clang/include/clang/Basic/StmtNodes.td | 3 + clang/include/clang/Basic/TokenKinds.def | 7 + clang/include/clang/Driver/Options.td | 23 ++ clang/include/clang/Parse/Parser.h | 24 ++ clang/include/clang/Sema/DeclSpec.h | 70 +++++- clang/include/clang/Sema/Sema.h | 23 ++ .../include/clang/Serialization/ASTBitCodes.h | 3 + clang/lib/AST/ASTImporter.cpp | 12 +- clang/lib/AST/Decl.cpp | 51 ++++- clang/lib/AST/DeclCXX.cpp | 37 +-- clang/lib/AST/ExprConstant.cpp | 47 ++++ clang/lib/AST/StmtCXX.cpp | 20 ++ clang/lib/AST/StmtPrinter.cpp | 21 ++ clang/lib/AST/StmtProfile.cpp | 2 + clang/lib/Basic/IdentifierTable.cpp | 5 +- clang/lib/CodeGen/CGBuiltin.cpp | 5 + clang/lib/CodeGen/CGStmt.cpp | 11 + clang/lib/CodeGen/CodeGenFunction.cpp | 56 +++++ clang/lib/CodeGen/CodeGenFunction.h | 4 + clang/lib/Parse/ParseDecl.cpp | 6 + clang/lib/Parse/ParseDeclCXX.cpp | 210 ++++++++++++++++++ clang/lib/Parse/ParseExprCXX.cpp | 3 + clang/lib/Parse/ParseStmt.cpp | 51 +++++ clang/lib/Parse/Parser.cpp | 2 + clang/lib/Sema/CMakeLists.txt | 1 + clang/lib/Sema/SemaContract.cpp | 89 ++++++++ clang/lib/Sema/SemaDecl.cpp | 14 +- clang/lib/Sema/SemaExceptionSpec.cpp | 1 + .../lib/Sema/SemaTemplateInstantiateDecl.cpp | 4 +- clang/lib/Sema/TreeTransform.h | 22 ++ clang/lib/Serialization/ASTReaderStmt.cpp | 19 ++ clang/lib/Serialization/ASTWriterStmt.cpp | 16 ++ clang/test/SemaCXX/contracts.cpp | 39 ++++ clang/tools/libclang/CXCursor.cpp | 7 +- libcxx/include/CMakeLists.txt | 1 + libcxx/include/contracts | 67 ++++++ libcxx/src/CMakeLists.txt | 1 + libcxx/src/contracts.cpp | 23 ++ libcxx/src/system_error.cpp | 4 + libcxx/test/CMakeLists.txt | 2 +- libcxx/test/configs/llvm-libc++-shared.cfg.in | 2 +- .../std/contracts/breathing_test.pass.cpp | 14 ++ libcxx/utils/libcxx/test/params.py | 2 +- 52 files changed, 1217 insertions(+), 58 deletions(-) create mode 100644 clang/lib/Sema/SemaContract.cpp create mode 100644 clang/test/SemaCXX/contracts.cpp create mode 100644 libcxx/include/contracts create mode 100644 libcxx/src/contracts.cpp create mode 100644 libcxx/test/std/contracts/breathing_test.pass.cpp diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h index 7fd80b90d10337..96b70411940696 100644 --- a/clang/include/clang/AST/Decl.h +++ b/clang/include/clang/AST/Decl.h @@ -55,6 +55,7 @@ namespace clang { class ASTContext; struct ASTTemplateArgumentListInfo; class CompoundStmt; +class ContractStmt; class DependentFunctionTemplateSpecializationInfo; class EnumDecl; class Expr; @@ -769,11 +770,13 @@ struct QualifierInfo { /// Contains type source information through TypeSourceInfo. class DeclaratorDecl : public ValueDecl { // A struct representing a TInfo, a trailing requires-clause and a syntactic - // qualifier, to be used for the (uncommon) case of out-of-line declarations - // and constrained function decls. + // qualifier, to be used for the (uncommon) case of out-of-line declarations, + // constrained function decls or functions with contracts. struct ExtInfo : public QualifierInfo { TypeSourceInfo *TInfo; Expr *TrailingRequiresClause = nullptr; + SmallVector PreContracts = {}; + SmallVector PostContracts = {}; }; llvm::PointerUnion DeclInfo; @@ -853,8 +856,22 @@ class DeclaratorDecl : public ValueDecl { : nullptr; } + SmallVector getPreContracts() const { + if (hasExtInfo()) return getExtInfo()->PreContracts; + return {}; + } + + SmallVector getPostContracts() const { + if (hasExtInfo()) return getExtInfo()->PostContracts; + return {}; + } + void setTrailingRequiresClause(Expr *TrailingRequiresClause); + void setPreContracts(SmallVector PreContracts); + + void setPostContracts(SmallVector PostContracts); + unsigned getNumTemplateParameterLists() const { return hasExtInfo() ? getExtInfo()->NumTemplParamLists : 0; } @@ -2037,6 +2054,7 @@ class FunctionDecl : public DeclaratorDecl, LazyDeclStmtPtr Body; /// Information about a future defaulted function definition. DefaultedOrDeletedFunctionInfo *DefaultedOrDeletedInfo; + /// }; unsigned ODRHash; @@ -2126,7 +2144,9 @@ class FunctionDecl : public DeclaratorDecl, const DeclarationNameInfo &NameInfo, QualType T, TypeSourceInfo *TInfo, StorageClass S, bool UsesFPIntrin, bool isInlineSpecified, ConstexprSpecKind ConstexprKind, - Expr *TrailingRequiresClause = nullptr); + Expr *TrailingRequiresClause = nullptr, + SmallVector PreContracts = {}, + SmallVector PostContracts = {}); using redeclarable_base = Redeclarable; @@ -2162,12 +2182,14 @@ class FunctionDecl : public DeclaratorDecl, TypeSourceInfo *TInfo, StorageClass SC, bool UsesFPIntrin = false, bool isInlineSpecified = false, bool hasWrittenPrototype = true, ConstexprSpecKind ConstexprKind = ConstexprSpecKind::Unspecified, - Expr *TrailingRequiresClause = nullptr) { + Expr *TrailingRequiresClause = nullptr, + SmallVector PreContracts = {}, + SmallVector PostContracts = {}) { DeclarationNameInfo NameInfo(N, NLoc); return FunctionDecl::Create(C, DC, StartLoc, NameInfo, T, TInfo, SC, UsesFPIntrin, isInlineSpecified, hasWrittenPrototype, ConstexprKind, - TrailingRequiresClause); + TrailingRequiresClause, PreContracts, PostContracts); } static FunctionDecl * @@ -2175,7 +2197,8 @@ class FunctionDecl : public DeclaratorDecl, const DeclarationNameInfo &NameInfo, QualType T, TypeSourceInfo *TInfo, StorageClass SC, bool UsesFPIntrin, bool isInlineSpecified, bool hasWrittenPrototype, ConstexprSpecKind ConstexprKind, - Expr *TrailingRequiresClause); + Expr *TrailingRequiresClause, SmallVector PreContracts, + SmallVector PostContracts); static FunctionDecl *CreateDeserialized(ASTContext &C, GlobalDeclID ID); diff --git a/clang/include/clang/AST/DeclCXX.h b/clang/include/clang/AST/DeclCXX.h index fb52ac804849d8..314daaa061dc45 100644 --- a/clang/include/clang/AST/DeclCXX.h +++ b/clang/include/clang/AST/DeclCXX.h @@ -2066,9 +2066,12 @@ class CXXMethodDecl : public FunctionDecl { QualType T, TypeSourceInfo *TInfo, StorageClass SC, bool UsesFPIntrin, bool isInline, ConstexprSpecKind ConstexprKind, SourceLocation EndLocation, - Expr *TrailingRequiresClause = nullptr) + Expr *TrailingRequiresClause = nullptr, + SmallVector PreContracts = {}, + SmallVector PostContracts = {}) : FunctionDecl(DK, C, RD, StartLoc, NameInfo, T, TInfo, SC, UsesFPIntrin, - isInline, ConstexprKind, TrailingRequiresClause) { + isInline, ConstexprKind, TrailingRequiresClause, + PreContracts, PostContracts) { if (EndLocation.isValid()) setRangeEnd(EndLocation); } @@ -2079,7 +2082,9 @@ class CXXMethodDecl : public FunctionDecl { const DeclarationNameInfo &NameInfo, QualType T, TypeSourceInfo *TInfo, StorageClass SC, bool UsesFPIntrin, bool isInline, ConstexprSpecKind ConstexprKind, SourceLocation EndLocation, - Expr *TrailingRequiresClause = nullptr); + Expr *TrailingRequiresClause = nullptr, + SmallVector PreContracts = {}, + SmallVector PostContracts = {}); static CXXMethodDecl *CreateDeserialized(ASTContext &C, GlobalDeclID ID); @@ -2547,7 +2552,9 @@ class CXXConstructorDecl final bool UsesFPIntrin, bool isInline, bool isImplicitlyDeclared, ConstexprSpecKind ConstexprKind, InheritedConstructor Inherited, - Expr *TrailingRequiresClause); + Expr *TrailingRequiresClause, + SmallVector PreContracts, + SmallVector PostContracts); void anchor() override; @@ -2590,7 +2597,9 @@ class CXXConstructorDecl final ExplicitSpecifier ES, bool UsesFPIntrin, bool isInline, bool isImplicitlyDeclared, ConstexprSpecKind ConstexprKind, InheritedConstructor Inherited = InheritedConstructor(), - Expr *TrailingRequiresClause = nullptr); + Expr *TrailingRequiresClause = nullptr, + SmallVector PreContracts = {}, + SmallVector PostContracts = {}); void setExplicitSpecifier(ExplicitSpecifier ES) { assert((!ES.getExpr() || @@ -2809,10 +2818,13 @@ class CXXDestructorDecl : public CXXMethodDecl { const DeclarationNameInfo &NameInfo, QualType T, TypeSourceInfo *TInfo, bool UsesFPIntrin, bool isInline, bool isImplicitlyDeclared, ConstexprSpecKind ConstexprKind, - Expr *TrailingRequiresClause = nullptr) + Expr *TrailingRequiresClause = nullptr, + SmallVector PreContracts = {}, + SmallVector PostContracts = {}) : CXXMethodDecl(CXXDestructor, C, RD, StartLoc, NameInfo, T, TInfo, SC_None, UsesFPIntrin, isInline, ConstexprKind, - SourceLocation(), TrailingRequiresClause) { + SourceLocation(), TrailingRequiresClause, PreContracts, + PostContracts) { setImplicit(isImplicitlyDeclared); } @@ -2824,7 +2836,9 @@ class CXXDestructorDecl : public CXXMethodDecl { const DeclarationNameInfo &NameInfo, QualType T, TypeSourceInfo *TInfo, bool UsesFPIntrin, bool isInline, bool isImplicitlyDeclared, ConstexprSpecKind ConstexprKind, - Expr *TrailingRequiresClause = nullptr); + Expr *TrailingRequiresClause = nullptr, + SmallVector PreContracts = {}, + SmallVector PostContracts = {}); static CXXDestructorDecl *CreateDeserialized(ASTContext &C, GlobalDeclID ID); void setOperatorDelete(FunctionDecl *OD, Expr *ThisArg); @@ -2865,10 +2879,13 @@ class CXXConversionDecl : public CXXMethodDecl { TypeSourceInfo *TInfo, bool UsesFPIntrin, bool isInline, ExplicitSpecifier ES, ConstexprSpecKind ConstexprKind, SourceLocation EndLocation, - Expr *TrailingRequiresClause = nullptr) + Expr *TrailingRequiresClause = nullptr, + SmallVector PreContracts = {}, + SmallVector PostContracts = {}) : CXXMethodDecl(CXXConversion, C, RD, StartLoc, NameInfo, T, TInfo, SC_None, UsesFPIntrin, isInline, ConstexprKind, - EndLocation, TrailingRequiresClause), + EndLocation, TrailingRequiresClause, PreContracts, + PostContracts), ExplicitSpec(ES) {} void anchor() override; @@ -2883,7 +2900,9 @@ class CXXConversionDecl : public CXXMethodDecl { const DeclarationNameInfo &NameInfo, QualType T, TypeSourceInfo *TInfo, bool UsesFPIntrin, bool isInline, ExplicitSpecifier ES, ConstexprSpecKind ConstexprKind, SourceLocation EndLocation, - Expr *TrailingRequiresClause = nullptr); + Expr *TrailingRequiresClause = nullptr, + SmallVector PreContracts = {}, + SmallVector PostContracts = {}); static CXXConversionDecl *CreateDeserialized(ASTContext &C, GlobalDeclID ID); ExplicitSpecifier getExplicitSpecifier() { diff --git a/clang/include/clang/AST/RecursiveASTVisitor.h b/clang/include/clang/AST/RecursiveASTVisitor.h index aa55e2e7e87188..c5ce6f112482e3 100644 --- a/clang/include/clang/AST/RecursiveASTVisitor.h +++ b/clang/include/clang/AST/RecursiveASTVisitor.h @@ -2443,6 +2443,9 @@ DEF_TRAVERSE_STMT(ObjCAtThrowStmt, {}) DEF_TRAVERSE_STMT(ObjCAtTryStmt, {}) DEF_TRAVERSE_STMT(ObjCForCollectionStmt, {}) DEF_TRAVERSE_STMT(ObjCAutoreleasePoolStmt, {}) +// FIXME(EricWF): This may have a declaration with a body eventually. +// Will that need a different implementation. +DEF_TRAVERSE_STMT(ContractStmt, {}) DEF_TRAVERSE_STMT(CXXForRangeStmt, { if (!getDerived().shouldVisitImplicitCode()) { diff --git a/clang/include/clang/AST/Stmt.h b/clang/include/clang/AST/Stmt.h index 9cd7a364cd3f1d..92102a710eadc1 100644 --- a/clang/include/clang/AST/Stmt.h +++ b/clang/include/clang/AST/Stmt.h @@ -74,6 +74,7 @@ enum class CXXNewInitializationStyle; enum class PredefinedIdentKind; enum class SourceLocIdentKind; enum class StringLiteralKind; +enum class ContractKind; //===----------------------------------------------------------------------===// // AST classes for statements. @@ -849,6 +850,21 @@ class alignas(void *) Stmt { SourceLocation RParenLoc; }; + class ContractAssertBitfields { + friend class ASTStmtReader; + friend class ASTStmtWriter; + friend class ContractStmt; + + LLVM_PREFERRED_TYPE(StmtBitfields) + unsigned : NumStmtBits; + + LLVM_PREFERRED_TYPE(bool) + unsigned HasResultName : 1; + + LLVM_PREFERRED_TYPE(ContractKind) + unsigned ContractKind : 2; + }; + class CXXNewExprBitfields { friend class ASTStmtReader; friend class ASTStmtWriter; @@ -1266,6 +1282,9 @@ class alignas(void *) Stmt { // C++ Coroutines expressions CoawaitExprBitfields CoawaitBits; + // C++ contracts + ContractAssertBitfields ContractAssertBits; + // Obj-C Expressions ObjCIndirectCopyRestoreExprBitfields ObjCIndirectCopyRestoreExprBits; diff --git a/clang/include/clang/AST/StmtCXX.h b/clang/include/clang/AST/StmtCXX.h index 8b4ef24ed376a1..0d2b3e9d073b9c 100644 --- a/clang/include/clang/AST/StmtCXX.h +++ b/clang/include/clang/AST/StmtCXX.h @@ -524,6 +524,105 @@ class CoreturnStmt : public Stmt { } }; +enum class ContractKind { Pre, Post, Assert }; + +class ContractStmt final : public Stmt, + private llvm::TrailingObjects { + friend class ASTStmtReader; + friend TrailingObjects; + + enum { ResultNameDeclOffset = 0, ConditionOffset = 1, Count = 2 }; + +public: + // These types correspond to the three C++ 'await_suspend' return variants + +private: + unsigned condOffset() const { + return ResultNameDeclOffset + ContractAssertBits.HasResultName; + } + + Stmt **getSubStmts() { return getTrailingObjects(); } + + Stmt *const *getSubStmts() const { return getTrailingObjects(); } + + SourceLocation KeywordLoc; + +public: + ContractStmt(ContractKind CK, SourceLocation KeywordLoc, Expr *Condition) + : Stmt(ContractStmtClass), KeywordLoc(KeywordLoc) { + ContractAssertBits.ContractKind = static_cast(CK); + ContractAssertBits.HasResultName = false; + } + + ContractStmt(EmptyShell Empty, ContractKind Kind, bool HasResultName = false) + : Stmt(ContractStmtClass, Empty) { + ContractAssertBits.ContractKind = static_cast(Kind); + ContractAssertBits.HasResultName = HasResultName; + } + + static ContractStmt *Create(const ASTContext &C, ContractKind Kind, + SourceLocation KeywordLoc, Expr *Condition, + DeclStmt *ResultNameDecl = nullptr); + + static ContractStmt *CreateEmpty(const ASTContext &C, ContractKind Kind, + bool HasResultName = false); + + ContractKind getContractKind() const { + return static_cast(ContractAssertBits.ContractKind); + } + void setContractKind(ContractKind CK) { + ContractAssertBits.ContractKind = static_cast(CK); + } + + bool hasResultNameDecl() const { return ContractAssertBits.HasResultName; } + + DeclStmt *getResultNameDecl() const { + return hasResultNameDecl() + ? static_cast( + getTrailingObjects()[ResultNameDeclOffset]) + : nullptr; + } + + void setResultNameDecl(DeclStmt *D) { + assert(hasResultNameDecl() && "no result name decl"); + getTrailingObjects()[ResultNameDeclOffset] = D; + } + + void setCondition(Expr *E) { + getTrailingObjects()[ConditionOffset] = E; + } + + Expr *getCond() { + return reinterpret_cast(getTrailingObjects()[condOffset()]); + } + + const Expr *getCond() const { + return reinterpret_cast(getTrailingObjects()[condOffset()]); + } + + void setCond(Expr *Cond) { + getTrailingObjects()[condOffset()] = reinterpret_cast(Cond); + } + + SourceLocation getKeywordLoc() const { return KeywordLoc; } + SourceLocation getBeginLoc() const LLVM_READONLY { return KeywordLoc; } + SourceLocation getEndLoc() const LLVM_READONLY { + return getCond()->getEndLoc(); + } + + child_range children() { + return child_range(getSubStmts(), getSubStmts() + condOffset() + 1); + } + + const_child_range children() const { + return const_child_range(getSubStmts(), getSubStmts() + condOffset() + 1); + } + + static bool classof(const Stmt *T) { + return T->getStmtClass() == ContractStmtClass; + } +}; + } // end namespace clang #endif diff --git a/clang/include/clang/Basic/Builtins.td b/clang/include/clang/Basic/Builtins.td index 7bef5fd7ad40f2..381050ea22a5f4 100644 --- a/clang/include/clang/Basic/Builtins.td +++ b/clang/include/clang/Basic/Builtins.td @@ -2575,6 +2575,20 @@ def SetJmpEx : MSLibBuiltin<"setjmpex.h"> { let Prototype = "int(jmp_buf)"; } +// C++ contracts +def ContractViolation : Builtin { + let Spellings = ["handle_contract_violation"]; + let Prototype = "void()"; + let Attributes = [NoThrow, NoReturn, Constexpr]; +} + +def ContractAssert : Builtin { + let Spellings = ["__builtin_contract_assert"]; + let Attributes = [NoThrow, Constexpr]; + + let Prototype = "void(bool)"; +} + // C99 library functions // C99 stdarg.h def VaStart : LibBuiltin<"stdarg.h"> { diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td index a024f9b2a9f8c0..82325f2b396e3e 100644 --- a/clang/include/clang/Basic/DiagnosticASTKinds.td +++ b/clang/include/clang/Basic/DiagnosticASTKinds.td @@ -403,6 +403,8 @@ def note_constexpr_assumption_failed : Note< "assumption evaluated to false">; def err_experimental_clang_interp_failed : Error< "the experimental clang interpreter failed to evaluate an expression">; +def note_constexpr_contract_failure : Error< + "contract failed during execution of constexpr function">; def warn_integer_constant_overflow : Warning< "overflow in expression; result is %0 with type %1">, diff --git a/clang/include/clang/Basic/LangOptions.def b/clang/include/clang/Basic/LangOptions.def index 6dd6b5614f44c3..a862550af1e11c 100644 --- a/clang/include/clang/Basic/LangOptions.def +++ b/clang/include/clang/Basic/LangOptions.def @@ -156,6 +156,7 @@ LANGOPT(NoBuiltin , 1, 0, "disable builtin functions") LANGOPT(NoMathBuiltin , 1, 0, "disable math builtin functions") LANGOPT(GNUAsm , 1, 1, "GNU-style inline assembly") LANGOPT(Coroutines , 1, 0, "C++20 coroutines") +LANGOPT(Contracts , 1, 1, "C++2c contracts") LANGOPT(CoroAlignedAllocation, 1, 0, "prefer Aligned Allocation according to P2014 Option 2") LANGOPT(DllExportInlines , 1, 1, "dllexported classes dllexport inline methods") LANGOPT(RelaxedTemplateTemplateArgs, 1, 1, "C++17 relaxed matching of template template arguments") diff --git a/clang/include/clang/Basic/LangOptions.h b/clang/include/clang/Basic/LangOptions.h index 75e88afbd97050..8d26d11d7727a0 100644 --- a/clang/include/clang/Basic/LangOptions.h +++ b/clang/include/clang/Basic/LangOptions.h @@ -441,6 +441,37 @@ class LangOptionsBase { CX_None }; + /// Contract evaluation mode. Determines whether to check contracts, and + // whether contract failures cause compile errors. + enum class ContractEvalMode { + // Contracts are parsed, syntax checked and type checked, but never evaluated. + Ignore = 0, + + // Contracts are run, and failures are reported, but contract failures do not + // logically stop execution of the program, nor can the compiler assume + // contracts are true for optimizing. + Observe = 1, + + // Contracts are run, failures are reported, and when a contract fails the + // program is terminated. The compiler can assume after contracts statements + // that the contracts hold. + Enforce = 2, + }; + + /// Determines the strategy to implement contract evaluation + enum class ContractImplStrategy { + // Contracts are checked after entering and before exiting a function. + Callee = 0, + + // Precontracts are checked before making a function call and after entering the function + // Postcontracts are checked before returning from a function, and after returning from a function call. + Both = 1, + + // Precontracts are checked before making a function call, postcontracts + // are checked before returning from a function call. + Split = 2, + }; + // Define simple language options (with no accessors). #define LANGOPT(Name, Bits, Default, Description) unsigned Name : Bits; #define ENUM_LANGOPT(Name, Type, Bits, Default, Description) @@ -555,6 +586,12 @@ class LangOptions : public LangOptionsBase { /// The default stream kind used for HIP kernel launching. GPUDefaultStreamKind GPUDefaultStream; + /// C++ contracts evaluation mode + ContractEvalMode ContractEvaluation; + + /// C++ contracts implementation strategy + ContractImplStrategy ContractStrategy; + /// The seed used by the randomize structure layout feature. std::string RandstructSeed; diff --git a/clang/include/clang/Basic/StmtNodes.td b/clang/include/clang/Basic/StmtNodes.td index 6ca08abdb14f07..52ec3d56943c3e 100644 --- a/clang/include/clang/Basic/StmtNodes.td +++ b/clang/include/clang/Basic/StmtNodes.td @@ -54,6 +54,9 @@ def CXXForRangeStmt : StmtNode; def CoroutineBodyStmt : StmtNode; def CoreturnStmt : StmtNode; +// C++ contract statements +def ContractStmt : StmtNode; + // Expressions def Expr : StmtNode; def PredefinedExpr : StmtNode; diff --git a/clang/include/clang/Basic/TokenKinds.def b/clang/include/clang/Basic/TokenKinds.def index 9c4b17465e18a1..f67bd102ab8da6 100644 --- a/clang/include/clang/Basic/TokenKinds.def +++ b/clang/include/clang/Basic/TokenKinds.def @@ -40,6 +40,9 @@ #ifndef MODULES_KEYWORD #define MODULES_KEYWORD(X) KEYWORD(X,KEYMODULES) #endif +#ifndef CONTRACTS_KEYWORD +#define CONTRACTS_KEYWORD(X) KEYWORD(X,KEYCONTRACTS) +#endif #ifndef TYPE_TRAIT #define TYPE_TRAIT(N,I,K) KEYWORD(I,K) #endif @@ -280,6 +283,7 @@ PUNCTUATOR(caretcaret, "^^") // which are heavily based on AltiVec // KEYBORLAND - This is a keyword if Borland extensions are enabled // KEYCOROUTINES - This is a keyword if support for C++ coroutines is enabled +// KEYCONTRACTS - This is a keyword if support for C++ contracts is enabled // BOOLSUPPORT - This is a keyword if 'bool' is a built-in type // HALFSUPPORT - This is a keyword if 'half' is a built-in type // WCHARSUPPORT - This is a keyword if 'wchar_t' is a built-in type @@ -415,6 +419,9 @@ CXX20_KEYWORD(constinit , 0) CXX20_KEYWORD(concept , 0) CXX20_KEYWORD(requires , 0) +// C++ contracts keywords +CONTRACTS_KEYWORD(contract_assert) + // Not a CXX20_KEYWORD because it is disabled by -fno-char8_t. KEYWORD(char8_t , CHAR8SUPPORT) diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td index 112eb286eb075e..2ab7b9810703c0 100644 --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -1661,6 +1661,29 @@ defm auto_import : BoolFOption<"auto-import", def offload_EQ : CommaJoined<["--"], "offload=">, Flags<[NoXarchOption]>, HelpText<"Specify comma-separated list of offloading target triples (CUDA and HIP only)">; +// C++ Contracts +defm fcontracts : BoolFOption<"contracts", + LangOpts<"Contracts">, DefaultFalse, + PosFlag, + NegFlag>; + +def fcontracts_eval_mode : Joined<["-"], "fcontracts_evaluation=">, + HelpText<"Specify contract evaluation mode. The default value is 'ignore'.">, + Values<"0,1,2">, + Visibility<[ClangOption, CC1Option]>, + NormalizedValuesScope<"LangOptions::ContractEvalMode">, + NormalizedValues<["Ignore", "Observe", "Enforce"]>, + MarshallingInfoEnum, "Ignore">; + +def fcontracts_impl_strategy : Joined<["-"], "fcontracts_strategy=">, + HelpText<"Specify contract implementation strategy. The default value is 'callee'.">, + Values<"0,1,2">, + Visibility<[ClangOption, CC1Option]>, + NormalizedValuesScope<"LangOptions::ContractImplStrategy">, + NormalizedValues<["Callee", "Both", "Split"]>, + MarshallingInfoEnum, "Callee">; + // C++ Coroutines defm coroutines : BoolFOption<"coroutines", LangOpts<"Coroutines">, Default, diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h index d054b8cf0d2405..a773c48f0fa6bf 100644 --- a/clang/include/clang/Parse/Parser.h +++ b/clang/include/clang/Parse/Parser.h @@ -169,6 +169,10 @@ class Parser : public CodeCompletionHandler { mutable IdentifierInfo *Ident_import; mutable IdentifierInfo *Ident_module; + // C++2c(?) contextual keywords. + mutable IdentifierInfo *Ident_pre; + mutable IdentifierInfo *Ident_post; + // C++ type trait keywords that can be reverted to identifiers and still be // used as type traits. llvm::SmallDenseMap RevertibleTypeTraits; @@ -2103,6 +2107,26 @@ class Parser : public CodeCompletionHandler { ExprResult ParseRequiresExpression(); void ParseTrailingRequiresClause(Declarator &D); + //===--------------------------------------------------------------------===// + // C++ Contracts + /* + ExceptionSpecificationType Parser::tryParseExceptionSpecification( + bool Delayed, SourceRange &SpecificationRange, + SmallVectorImpl &DynamicExceptions, + SmallVectorImpl &DynamicExceptionRanges, + ExprResult &NoexceptExpr, CachedTokens *&) { + */ + enum class ContractKeyword { None, Pre, Post }; + ContractKeyword isContractSpecifier(const Token &Tok) const; + ContractKeyword isContractSpecifier() const { + return isContractSpecifier(Tok); + } + + StmtResult ParseContractAssertStatement(); + void MaybeParseFunctionContractSpecifierSeq(Declarator &DeclaratorInfo); + StmtResult ParseFunctionContractSpecifier(Declarator &DeclaratorInfo); + void ParsePostContract(Declarator &DeclaratorInfo); + //===--------------------------------------------------------------------===// // C99 6.7.8: Initialization. diff --git a/clang/include/clang/Sema/DeclSpec.h b/clang/include/clang/Sema/DeclSpec.h index 23bc780e04979d..f249d1f27cb786 100644 --- a/clang/include/clang/Sema/DeclSpec.h +++ b/clang/include/clang/Sema/DeclSpec.h @@ -49,6 +49,7 @@ namespace clang { class ObjCDeclSpec; class Sema; class Declarator; + class ContractStmt; struct TemplateIdAnnotation; /// Represents a C++ nested-name-specifier or a global scope specifier. @@ -1970,6 +1971,12 @@ class Declarator { /// requires-clause, or null if no such clause was specified. Expr *TrailingRequiresClause; + /// \brief All pre contracts specified by the function declaration + SmallVector PreContracts; + + /// \brief All post contracts specified by the function declaration + SmallVector PostContracts; + /// If this declarator declares a template, its template parameter lists. ArrayRef TemplateParameterLists; @@ -2628,7 +2635,7 @@ class Declarator { SetRangeEnd(TRC->getEndLoc()); } - + /// \brief Sets a trailing requires clause for this declarator. Expr *getTrailingRequiresClause() { return TrailingRequiresClause; @@ -2640,6 +2647,20 @@ class Declarator { return TrailingRequiresClause != nullptr; } + /// \brief Add a pre contract for this declarator + void addPreContract(ContractStmt *TRC) { PreContracts.push_back(TRC); } + + /// \brief Get all pre contracts for this declarator + const SmallVector &getPreContracts() { return PreContracts; } + + /// \brief Add a post contract for this declarator + void addPostContract(ContractStmt *TRC) { PostContracts.push_back(TRC); } + + /// \brief Get all post contracts for this declarator + const SmallVector &getPostContracts() { + return PostContracts; + } + /// Sets the template parameter lists that preceded the declarator. void setTemplateParameterLists(ArrayRef TPLs) { TemplateParameterLists = TPLs; @@ -2776,6 +2797,53 @@ struct FieldDeclarator { BitfieldSize(nullptr) {} }; +class ContractSpecifiers { +public: + enum Specifier { CS_None = 0, CS_Pre, CS_Post }; + + struct ContractInfo { + Specifier Kind; + // Either the parsed expression or token soup for the contract. + const IdentifierInfo *ReturnValueIdent; + SourceLocation ReturnValueIdentLoc; + + Expr *ParsedContract; + std::unique_ptr ContractTokens; + + public: + ContractInfo(Specifier Kind, const IdentifierInfo *ReturnValueIdent, + SourceLocation ReturnValueIdentLoc, Expr *ParsedContract, + std::unique_ptr xContractTokens) + : Kind(Kind), ReturnValueIdent(ReturnValueIdent), + ReturnValueIdentLoc(ReturnValueIdentLoc), + ParsedContract(ParsedContract), + ContractTokens(std::move(xContractTokens)) { + + assert(Kind == CS_Post || ReturnValueIdent == nullptr); + assert((ContractTokens == nullptr) != (ParsedContract == nullptr)); + } + + bool isDelayed() const { + assert(ParsedContract == nullptr || ContractTokens == nullptr); + return ContractTokens != nullptr; + } + bool isPre() const { return Kind == CS_Pre; } + bool isPost() const { return Kind == CS_Post; } + bool hasReturnIdentifier() const { return ReturnValueIdent != nullptr; } + }; + + void addContract(ContractInfo CI) { Contracts.push_back(std::move(CI)); } + + const SmallVectorImpl &getContracts() const { + return Contracts; + } + + bool hasContracts() const { return !Contracts.empty(); } + +private: + SmallVector Contracts; +}; + /// Represents a C++11 virt-specifier-seq. class VirtSpecifiers { public: diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 174b9dbc6d980c..6692d91457e800 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -2384,6 +2384,29 @@ class Sema final : public SemaBase { // // + /// \name C++ Contracts + /// Implementations are in SemaContract.cpp + ///@{ + +public: + StmtResult ActOnContractAssert(SourceLocation KeywordLoc, Expr *Cond); + StmtResult ActOnPreContractAssert(SourceLocation KeywordLoc, Expr *Cond); + StmtResult ActOnPostContractAssert(SourceLocation KeywordLoc, Expr *Cond, + DeclStmt *ResultNameDecl = nullptr); + + ExprResult ActOnContractAssertCondition(Expr *Cond); + + StmtResult BuildContractStmt(ContractKind CK, SourceLocation KeywordLoc, + Expr *Cond, DeclStmt *ResultNameDecl = nullptr); + + ///@} + + // + // + // ------------------------------------------------------------------------- + // + // + /// \name C++ Scope Specifiers /// Implementations are in SemaCXXScopeSpec.cpp ///@{ diff --git a/clang/include/clang/Serialization/ASTBitCodes.h b/clang/include/clang/Serialization/ASTBitCodes.h index 9f0f900a029149..195145c41abaac 100644 --- a/clang/include/clang/Serialization/ASTBitCodes.h +++ b/clang/include/clang/Serialization/ASTBitCodes.h @@ -1953,6 +1953,9 @@ enum StmtCode { EXPR_COYIELD, EXPR_DEPENDENT_COAWAIT, + // contracts + STMT_CXX_CONTRACT, + // FixedPointLiteral EXPR_FIXEDPOINT_LITERAL, diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp index 1b67feaae88748..7bcaa6faa5486b 100644 --- a/clang/lib/AST/ASTImporter.cpp +++ b/clang/lib/AST/ASTImporter.cpp @@ -3861,6 +3861,8 @@ ExpectedDecl ASTNodeImporter::VisitFunctionDecl(FunctionDecl *D) { auto ToQualifierLoc = importChecked(Err, D->getQualifierLoc()); auto TrailingRequiresClause = importChecked(Err, D->getTrailingRequiresClause()); + auto PreContracts = D->getPreContracts(); + auto PostContracts = D->getPostContracts(); if (Err) return std::move(Err); @@ -3892,7 +3894,7 @@ ExpectedDecl ASTNodeImporter::VisitFunctionDecl(FunctionDecl *D) { ToFunction, D, Importer.getToContext(), cast(DC), ToInnerLocStart, NameInfo, T, TInfo, ESpec, D->UsesFPIntrin(), D->isInlineSpecified(), D->isImplicit(), D->getConstexprKind(), - ToInheritedConstructor, TrailingRequiresClause)) + ToInheritedConstructor, TrailingRequiresClause, PreContracts, PostContracts)) return ToFunction; } else if (CXXDestructorDecl *FromDtor = dyn_cast(D)) { @@ -3907,7 +3909,7 @@ ExpectedDecl ASTNodeImporter::VisitFunctionDecl(FunctionDecl *D) { ToFunction, D, Importer.getToContext(), cast(DC), ToInnerLocStart, NameInfo, T, TInfo, D->UsesFPIntrin(), D->isInlineSpecified(), D->isImplicit(), D->getConstexprKind(), - TrailingRequiresClause)) + TrailingRequiresClause, PreContracts, PostContracts)) return ToFunction; CXXDestructorDecl *ToDtor = cast(ToFunction); @@ -3923,14 +3925,14 @@ ExpectedDecl ASTNodeImporter::VisitFunctionDecl(FunctionDecl *D) { ToFunction, D, Importer.getToContext(), cast(DC), ToInnerLocStart, NameInfo, T, TInfo, D->UsesFPIntrin(), D->isInlineSpecified(), ESpec, D->getConstexprKind(), - SourceLocation(), TrailingRequiresClause)) + SourceLocation(), TrailingRequiresClause, PreContracts, PostContracts)) return ToFunction; } else if (auto *Method = dyn_cast(D)) { if (GetImportedOrCreateDecl( ToFunction, D, Importer.getToContext(), cast(DC), ToInnerLocStart, NameInfo, T, TInfo, Method->getStorageClass(), Method->UsesFPIntrin(), Method->isInlineSpecified(), - D->getConstexprKind(), SourceLocation(), TrailingRequiresClause)) + D->getConstexprKind(), SourceLocation(), TrailingRequiresClause, PreContracts, PostContracts)) return ToFunction; } else if (auto *Guide = dyn_cast(D)) { ExplicitSpecifier ESpec = @@ -3950,7 +3952,7 @@ ExpectedDecl ASTNodeImporter::VisitFunctionDecl(FunctionDecl *D) { ToFunction, D, Importer.getToContext(), DC, ToInnerLocStart, NameInfo, T, TInfo, D->getStorageClass(), D->UsesFPIntrin(), D->isInlineSpecified(), D->hasWrittenPrototype(), - D->getConstexprKind(), TrailingRequiresClause)) + D->getConstexprKind(), TrailingRequiresClause, PreContracts, PostContracts)) return ToFunction; } diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp index 9d0a835a12c458..3ecce1dfb1263d 100644 --- a/clang/lib/AST/Decl.cpp +++ b/clang/lib/AST/Decl.cpp @@ -2021,6 +2021,35 @@ void DeclaratorDecl::setTrailingRequiresClause(Expr *TrailingRequiresClause) { getExtInfo()->TrailingRequiresClause = TrailingRequiresClause; } +void DeclaratorDecl::setPreContracts(SmallVector PreContracts) { + // Make sure the extended decl info is allocated. + if (!hasExtInfo()) { + // Save (non-extended) type source info pointer. + auto *savedTInfo = DeclInfo.get(); + // Allocate external info struct. + DeclInfo = new (getASTContext()) ExtInfo; + // Restore savedTInfo into (extended) decl info. + getExtInfo()->TInfo = savedTInfo; + } + // Set requires clause info. + getExtInfo()->PreContracts = PreContracts; +} + +void DeclaratorDecl::setPostContracts( + SmallVector PostContracts) { + // Make sure the extended decl info is allocated. + if (!hasExtInfo()) { + // Save (non-extended) type source info pointer. + auto *savedTInfo = DeclInfo.get(); + // Allocate external info struct. + DeclInfo = new (getASTContext()) ExtInfo; + // Restore savedTInfo into (extended) decl info. + getExtInfo()->TInfo = savedTInfo; + } + // Set requires clause info. + getExtInfo()->PostContracts = PostContracts; +} + void DeclaratorDecl::setTemplateParameterListsInfo( ASTContext &Context, ArrayRef TPLists) { assert(!TPLists.empty()); @@ -3036,7 +3065,9 @@ FunctionDecl::FunctionDecl(Kind DK, ASTContext &C, DeclContext *DC, TypeSourceInfo *TInfo, StorageClass S, bool UsesFPIntrin, bool isInlineSpecified, ConstexprSpecKind ConstexprKind, - Expr *TrailingRequiresClause) + Expr *TrailingRequiresClause, + SmallVector PreContracts, + SmallVector PostContracts) : DeclaratorDecl(DK, DC, NameInfo.getLoc(), NameInfo.getName(), T, TInfo, StartLoc), DeclContext(DK), redeclarable_base(C), Body(), ODRHash(0), @@ -3072,6 +3103,8 @@ FunctionDecl::FunctionDecl(Kind DK, ASTContext &C, DeclContext *DC, FunctionDeclBits.FriendConstraintRefersToEnclosingTemplate = false; if (TrailingRequiresClause) setTrailingRequiresClause(TrailingRequiresClause); + setPreContracts(PreContracts); + setPostContracts(PostContracts); } void FunctionDecl::getNameForDiagnostic( @@ -5389,16 +5422,16 @@ ImplicitParamDecl *ImplicitParamDecl::CreateDeserialized(ASTContext &C, return new (C, ID) ImplicitParamDecl(C, QualType(), ImplicitParamKind::Other); } -FunctionDecl * -FunctionDecl::Create(ASTContext &C, DeclContext *DC, SourceLocation StartLoc, - const DeclarationNameInfo &NameInfo, QualType T, - TypeSourceInfo *TInfo, StorageClass SC, bool UsesFPIntrin, - bool isInlineSpecified, bool hasWrittenPrototype, - ConstexprSpecKind ConstexprKind, - Expr *TrailingRequiresClause) { +FunctionDecl *FunctionDecl::Create( + ASTContext &C, DeclContext *DC, SourceLocation StartLoc, + const DeclarationNameInfo &NameInfo, QualType T, TypeSourceInfo *TInfo, + StorageClass SC, bool UsesFPIntrin, bool isInlineSpecified, + bool hasWrittenPrototype, ConstexprSpecKind ConstexprKind, + Expr *TrailingRequiresClause, SmallVector PreContracts, + SmallVector PostContracts) { FunctionDecl *New = new (C, DC) FunctionDecl( Function, C, DC, StartLoc, NameInfo, T, TInfo, SC, UsesFPIntrin, - isInlineSpecified, ConstexprKind, TrailingRequiresClause); + isInlineSpecified, ConstexprKind, TrailingRequiresClause, PreContracts, PostContracts); New->setHasWrittenPrototype(hasWrittenPrototype); return New; } diff --git a/clang/lib/AST/DeclCXX.cpp b/clang/lib/AST/DeclCXX.cpp index 7f2c786547b9b6..0147d860326b59 100644 --- a/clang/lib/AST/DeclCXX.cpp +++ b/clang/lib/AST/DeclCXX.cpp @@ -2275,11 +2275,12 @@ CXXMethodDecl::Create(ASTContext &C, CXXRecordDecl *RD, SourceLocation StartLoc, const DeclarationNameInfo &NameInfo, QualType T, TypeSourceInfo *TInfo, StorageClass SC, bool UsesFPIntrin, bool isInline, ConstexprSpecKind ConstexprKind, - SourceLocation EndLocation, - Expr *TrailingRequiresClause) { + SourceLocation EndLocation, Expr *TrailingRequiresClause, + SmallVector PreContracts, + SmallVector PostContracts) { return new (C, RD) CXXMethodDecl( CXXMethod, C, RD, StartLoc, NameInfo, T, TInfo, SC, UsesFPIntrin, - isInline, ConstexprKind, EndLocation, TrailingRequiresClause); + isInline, ConstexprKind, EndLocation, TrailingRequiresClause, PreContracts, PostContracts); } CXXMethodDecl *CXXMethodDecl::CreateDeserialized(ASTContext &C, @@ -2287,7 +2288,7 @@ CXXMethodDecl *CXXMethodDecl::CreateDeserialized(ASTContext &C, return new (C, ID) CXXMethodDecl( CXXMethod, C, nullptr, SourceLocation(), DeclarationNameInfo(), QualType(), nullptr, SC_None, false, false, - ConstexprSpecKind::Unspecified, SourceLocation(), nullptr); + ConstexprSpecKind::Unspecified, SourceLocation(), nullptr, {}, {}); } CXXMethodDecl *CXXMethodDecl::getDevirtualizedMethod(const Expr *Base, @@ -2685,10 +2686,13 @@ CXXConstructorDecl::CXXConstructorDecl( const DeclarationNameInfo &NameInfo, QualType T, TypeSourceInfo *TInfo, ExplicitSpecifier ES, bool UsesFPIntrin, bool isInline, bool isImplicitlyDeclared, ConstexprSpecKind ConstexprKind, - InheritedConstructor Inherited, Expr *TrailingRequiresClause) + InheritedConstructor Inherited, Expr *TrailingRequiresClause, + SmallVector PreContracts, + SmallVector PostContracts) : CXXMethodDecl(CXXConstructor, C, RD, StartLoc, NameInfo, T, TInfo, SC_None, UsesFPIntrin, isInline, ConstexprKind, - SourceLocation(), TrailingRequiresClause) { + SourceLocation(), TrailingRequiresClause, PreContracts, + PostContracts) { setNumCtorInitializers(0); setInheritingConstructor(static_cast(Inherited)); setImplicit(isImplicitlyDeclared); @@ -2712,7 +2716,7 @@ CXXConstructorDecl *CXXConstructorDecl::CreateDeserialized(ASTContext &C, auto *Result = new (C, ID, Extra) CXXConstructorDecl( C, nullptr, SourceLocation(), DeclarationNameInfo(), QualType(), nullptr, ExplicitSpecifier(), false, false, false, ConstexprSpecKind::Unspecified, - InheritedConstructor(), nullptr); + InheritedConstructor(), nullptr, {}, {}); Result->setInheritingConstructor(isInheritingConstructor); Result->CXXConstructorDeclBits.HasTrailingExplicitSpecifier = hasTrailingExplicit; @@ -2725,7 +2729,9 @@ CXXConstructorDecl *CXXConstructorDecl::Create( const DeclarationNameInfo &NameInfo, QualType T, TypeSourceInfo *TInfo, ExplicitSpecifier ES, bool UsesFPIntrin, bool isInline, bool isImplicitlyDeclared, ConstexprSpecKind ConstexprKind, - InheritedConstructor Inherited, Expr *TrailingRequiresClause) { + InheritedConstructor Inherited, Expr *TrailingRequiresClause, + SmallVector PreContracts, + SmallVector PostContracts) { assert(NameInfo.getName().getNameKind() == DeclarationName::CXXConstructorName && "Name must refer to a constructor"); @@ -2734,7 +2740,7 @@ CXXConstructorDecl *CXXConstructorDecl::Create( Inherited ? 1 : 0, ES.getExpr() ? 1 : 0); return new (C, RD, Extra) CXXConstructorDecl( C, RD, StartLoc, NameInfo, T, TInfo, ES, UsesFPIntrin, isInline, - isImplicitlyDeclared, ConstexprKind, Inherited, TrailingRequiresClause); + isImplicitlyDeclared, ConstexprKind, Inherited, TrailingRequiresClause, PreContracts, PostContracts); } CXXConstructorDecl::init_const_iterator CXXConstructorDecl::init_begin() const { @@ -2851,20 +2857,22 @@ CXXDestructorDecl *CXXDestructorDecl::CreateDeserialized(ASTContext &C, GlobalDeclID ID) { return new (C, ID) CXXDestructorDecl( C, nullptr, SourceLocation(), DeclarationNameInfo(), QualType(), nullptr, - false, false, false, ConstexprSpecKind::Unspecified, nullptr); + false, false, false, ConstexprSpecKind::Unspecified, nullptr, {}); } CXXDestructorDecl *CXXDestructorDecl::Create( ASTContext &C, CXXRecordDecl *RD, SourceLocation StartLoc, const DeclarationNameInfo &NameInfo, QualType T, TypeSourceInfo *TInfo, bool UsesFPIntrin, bool isInline, bool isImplicitlyDeclared, - ConstexprSpecKind ConstexprKind, Expr *TrailingRequiresClause) { + ConstexprSpecKind ConstexprKind, Expr *TrailingRequiresClause, + SmallVector PreContracts, + SmallVector PostContracts) { assert(NameInfo.getName().getNameKind() == DeclarationName::CXXDestructorName && "Name must refer to a destructor"); return new (C, RD) CXXDestructorDecl( C, RD, StartLoc, NameInfo, T, TInfo, UsesFPIntrin, isInline, - isImplicitlyDeclared, ConstexprKind, TrailingRequiresClause); + isImplicitlyDeclared, ConstexprKind, TrailingRequiresClause, PreContracts, PostContracts); } void CXXDestructorDecl::setOperatorDelete(FunctionDecl *OD, Expr *ThisArg) { @@ -2892,13 +2900,14 @@ CXXConversionDecl *CXXConversionDecl::Create( const DeclarationNameInfo &NameInfo, QualType T, TypeSourceInfo *TInfo, bool UsesFPIntrin, bool isInline, ExplicitSpecifier ES, ConstexprSpecKind ConstexprKind, SourceLocation EndLocation, - Expr *TrailingRequiresClause) { + Expr *TrailingRequiresClause, SmallVector PreContracts, + SmallVector PostContracts) { assert(NameInfo.getName().getNameKind() == DeclarationName::CXXConversionFunctionName && "Name must refer to a conversion function"); return new (C, RD) CXXConversionDecl( C, RD, StartLoc, NameInfo, T, TInfo, UsesFPIntrin, isInline, ES, - ConstexprKind, EndLocation, TrailingRequiresClause); + ConstexprKind, EndLocation, TrailingRequiresClause, PreContracts, PostContracts); } bool CXXConversionDecl::isLambdaToBlockPointerConversion() const { diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 37a869c12e56d2..7e15a68aa03261 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -6294,6 +6294,36 @@ static bool handleTrivialCopy(EvalInfo &Info, const ParmVarDecl *Param, CopyObjectRepresentation); } +static bool EvaluatePreContracts(const FunctionDecl* Callee, EvalInfo& Info) { + for (ContractStmt *S : Callee->getPreContracts()) { + Expr *E = S->getCond(); + APSInt Desired; + if (!EvaluateInteger(E, Desired, Info)){ + return false; + } + if (!Desired) { + Info.CCEDiag(E, diag::note_constexpr_contract_failure); + return false; + } + } + return true; +} + +static bool EvaluatePostContracts(const FunctionDecl* Callee, EvalInfo& Info) { + for (ContractStmt *S : Callee->getPostContracts()) { + Expr *E = S->getCond(); + APSInt Desired; + if (!EvaluateInteger(E, Desired, Info)){ + return false; + } + if (!Desired) { + Info.CCEDiag(E, diag::note_constexpr_contract_failure); + return false; + } + } + return true; +} + /// Evaluate a function call. static bool HandleFunctionCall(SourceLocation CallLoc, const FunctionDecl *Callee, const LValue *This, @@ -6339,8 +6369,14 @@ static bool HandleFunctionCall(SourceLocation CallLoc, Frame.LambdaThisCaptureField); } + if (!EvaluatePreContracts(Callee, Info)) + return false; + StmtResult Ret = {Result, ResultSlot}; EvalStmtResult ESR = EvaluateStmt(Ret, Info, Body); + if (!EvaluatePostContracts(Callee, Info)) + return false; + if (ESR == ESR_Succeeded) { if (Callee->getReturnType()->isVoidType()) return true; @@ -15550,6 +15586,17 @@ class VoidExprEvaluator case Builtin::BI__builtin_operator_delete: return HandleOperatorDeleteCall(Info, E); + case Builtin::BI__builtin_contract_assert: { + APSInt Desired; + if (!EvaluateInteger(E->getArg(0), Desired, Info)){ + return false; + } + if (!Desired) { + Info.CCEDiag(E->getArg(0), diag::note_constexpr_contract_failure); + return false; + } + return true; + } default: return false; } diff --git a/clang/lib/AST/StmtCXX.cpp b/clang/lib/AST/StmtCXX.cpp index 0d6fc848f73964..c4197b3b1daa53 100644 --- a/clang/lib/AST/StmtCXX.cpp +++ b/clang/lib/AST/StmtCXX.cpp @@ -126,3 +126,23 @@ CoroutineBodyStmt::CoroutineBodyStmt(CoroutineBodyStmt::CtorArgs const &Args) std::copy(Args.ParamMoves.begin(), Args.ParamMoves.end(), const_cast(getParamMoves().data())); } + +ContractStmt *ContractStmt::CreateEmpty(const ASTContext &C, ContractKind Kind, + bool HasResultName) { + void *Mem = C.Allocate(totalSizeToAlloc(1 + HasResultName), + alignof(ContractStmt)); + return new (Mem) ContractStmt(EmptyShell(), Kind, HasResultName); +} + +ContractStmt *ContractStmt::Create(const ASTContext &C, ContractKind Kind, + SourceLocation KeywordLoc, Expr *Condition, + DeclStmt *ResultNameDecl) { + assert((ResultNameDecl == nullptr || Kind == ContractKind::Post) && + "Only a postcondition can have a result name declaration"); + auto *S = CreateEmpty(C, Kind, ResultNameDecl != nullptr); + S->KeywordLoc = KeywordLoc; + S->setCondition(Condition); + if (ResultNameDecl) + S->setResultNameDecl(ResultNameDecl); + return S; +} diff --git a/clang/lib/AST/StmtPrinter.cpp b/clang/lib/AST/StmtPrinter.cpp index 8f51d16b5db037..f82e7ce013b3ab 100644 --- a/clang/lib/AST/StmtPrinter.cpp +++ b/clang/lib/AST/StmtPrinter.cpp @@ -2620,6 +2620,27 @@ void StmtPrinter::VisitCoyieldExpr(CoyieldExpr *S) { PrintExpr(S->getOperand()); } +// C++ contracts + +void StmtPrinter::VisitContractStmt(ContractStmt *Node) { + const char *Keyword = [=]() { + switch (Node->getContractKind()) { + case ContractKind::Assert: + return "contract_assert"; + case ContractKind::Pre: + return "pre"; + case ContractKind::Post: + return "post"; + } + llvm_unreachable("unhandled case"); + }(); + OS << Keyword << "("; + PrintExpr(Node->getCond()); + OS << ")"; + if (Node->getContractKind() == ContractKind::Assert) + OS << ";"; +} + // Obj-C void StmtPrinter::VisitObjCStringLiteral(ObjCStringLiteral *Node) { diff --git a/clang/lib/AST/StmtProfile.cpp b/clang/lib/AST/StmtProfile.cpp index d1655905a66566..ac78b184c5fa53 100644 --- a/clang/lib/AST/StmtProfile.cpp +++ b/clang/lib/AST/StmtProfile.cpp @@ -2301,6 +2301,8 @@ void StmtProfiler::VisitCoyieldExpr(const CoyieldExpr *S) { VisitExpr(S); } +void StmtProfiler::VisitContractStmt(const ContractStmt *S) { VisitStmt(S); } + void StmtProfiler::VisitOpaqueValueExpr(const OpaqueValueExpr *E) { VisitExpr(E); } diff --git a/clang/lib/Basic/IdentifierTable.cpp b/clang/lib/Basic/IdentifierTable.cpp index feea84544d62fb..3070bba7c3f936 100644 --- a/clang/lib/Basic/IdentifierTable.cpp +++ b/clang/lib/Basic/IdentifierTable.cpp @@ -109,7 +109,8 @@ namespace { KEYCUDA = 0x1000000, KEYHLSL = 0x2000000, KEYFIXEDPOINT = 0x4000000, - KEYMAX = KEYFIXEDPOINT, // The maximum key + KEYCONTRACTS = 0x8000000, + KEYMAX = KEYCONTRACTS, // The maximum key KEYALLCXX = KEYCXX | KEYCXX11 | KEYCXX20, KEYALL = (KEYMAX | (KEYMAX-1)) & ~KEYNOMS18 & ~KEYNOOPENCL // KEYNOMS18 and KEYNOOPENCL are used to exclude. @@ -189,6 +190,8 @@ static KeywordStatus getKeywordStatusHelper(const LangOptions &LangOpts, return LangOpts.ZVector ? KS_Enabled : KS_Unknown; case KEYCOROUTINES: return LangOpts.Coroutines ? KS_Enabled : KS_Unknown; + case KEYCONTRACTS: + return LangOpts.Contracts ? KS_Enabled : KS_Unknown; case KEYMODULES: return KS_Unknown; case KEYOPENCLCXX: diff --git a/clang/lib/CodeGen/CGBuiltin.cpp b/clang/lib/CodeGen/CGBuiltin.cpp index 4f393ac1d18058..48251bc38cc479 100644 --- a/clang/lib/CodeGen/CGBuiltin.cpp +++ b/clang/lib/CodeGen/CGBuiltin.cpp @@ -3359,6 +3359,11 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID, Builder.CreateCall(FnAssume, ArgValue); return RValue::get(nullptr); } + case Builtin::BI__builtin_contract_assert: { + EmitCXXContractCheck(E->getArg(0)); + EmitCXXContractImply(E->getArg(0)); + return RValue::get(nullptr); + } case Builtin::BI__builtin_assume_separate_storage: { const Expr *Arg0 = E->getArg(0); const Expr *Arg1 = E->getArg(1); diff --git a/clang/lib/CodeGen/CGStmt.cpp b/clang/lib/CodeGen/CGStmt.cpp index 39222c0e65353b..07032e065c5d35 100644 --- a/clang/lib/CodeGen/CGStmt.cpp +++ b/clang/lib/CodeGen/CGStmt.cpp @@ -18,6 +18,7 @@ #include "clang/AST/Attr.h" #include "clang/AST/Expr.h" #include "clang/AST/Stmt.h" +#include "clang/AST/StmtCXX.h" #include "clang/AST/StmtVisitor.h" #include "clang/Basic/Builtins.h" #include "clang/Basic/DiagnosticSema.h" @@ -168,6 +169,9 @@ void CodeGenFunction::EmitStmt(const Stmt *S, ArrayRef Attrs) { case Stmt::CoreturnStmtClass: EmitCoreturnStmt(cast(*S)); break; + case Stmt::ContractStmtClass: + EmitContractStmt(cast(*S)); + break; case Stmt::CapturedStmtClass: { const CapturedStmt *CS = cast(S); EmitCapturedStmt(*CS, CS->getCapturedRegionKind()); @@ -1455,6 +1459,13 @@ static bool isSwiftAsyncCallee(const CallExpr *CE) { return calleeType->getCallConv() == CallingConv::CC_SwiftAsync; } +void CodeGenFunction::EmitContractStmt(const ContractStmt &S) { + // Emit the contract expression. + const Expr *expr = S.getCond(); + EmitCXXContractCheck(expr); + EmitCXXContractImply(expr); +} + /// EmitReturnStmt - Note that due to GCC extensions, this can have an operand /// if the function returns void, or may be missing one if the function returns /// non-void. Fun stuff :). diff --git a/clang/lib/CodeGen/CodeGenFunction.cpp b/clang/lib/CodeGen/CodeGenFunction.cpp index cea0d84c64bc47..68914c70ec0f00 100644 --- a/clang/lib/CodeGen/CodeGenFunction.cpp +++ b/clang/lib/CodeGen/CodeGenFunction.cpp @@ -336,6 +336,53 @@ llvm::DebugLoc CodeGenFunction::EmitReturnBlock() { return llvm::DebugLoc(); } +void CodeGenFunction::EmitCXXContractCheck(const Expr* Expr) { + llvm::Value *ArgValue = EmitScalarExpr(Expr); + llvm::BasicBlock *Begin = Builder.GetInsertBlock(); + llvm::BasicBlock *End = createBasicBlock("contract_assert_end", this->CurFn); + llvm::BasicBlock *Violation = createBasicBlock("contract_assert_violation", this->CurFn); + + Builder.SetInsertPoint(Begin); + Builder.CreateCondBr(ArgValue, End, Violation); + + Builder.SetInsertPoint(Violation); + +/* + SourceRange range = Expr->getSourceRange(); + PresumedLoc PLoc = Ctx.getSourceManager().getPresumedLoc(range->getBegin()); + clang::StringLiteral* Filename = PLoc->getFilename(); + llvm::Value* LineNo = PLoc->getLine(); + clang::StringLiteral* ExpressionText = range.print(os, Ctx.getSourceManager()); +*/ + const char *VLibCallName = + "_ZNSt9contracts41invoke_default_contract_violation_handlerEv"; // const + // char* + CallArgList Args; +/* + Args.add(EmitLoadOfLValue(EmitStringLiteralLValue(Filename), Expr->getExprLoc()), getContext().VoidPtrTy); + Args.add(RValue::get(LineNo), getContext().getSizeType()); + Args.add(EmitLoadOfLValue(EmitStringLiteralLValue(ExpressionText), Expr->getExprLoc()), getContext().VoidPtrTy); +*/ + const CGFunctionInfo &VFuncInfo = CGM.getTypes().arrangeBuiltinFunctionCall(getContext().VoidTy, {}); + llvm::FunctionType *VFTy = CGM.getTypes().GetFunctionType(VFuncInfo); + llvm::FunctionCallee VFunc = CGM.CreateRuntimeFunction(VFTy, VLibCallName); + EmitCall(VFuncInfo, CGCallee::forDirect(VFunc), ReturnValueSlot(), Args); + + llvm::CallInst *TrapCall = EmitTrapCall(llvm::Intrinsic::trap); + TrapCall->setDoesNotReturn(); + TrapCall->setDoesNotThrow(); + Builder.CreateUnreachable(); + Builder.ClearInsertionPoint(); + + Builder.SetInsertPoint(End); +} + +void CodeGenFunction::EmitCXXContractImply(const Expr* expr) { + llvm::Value *ArgValue = EmitScalarExpr(expr); + llvm::Function *FnAssume = CGM.getIntrinsic(llvm::Intrinsic::assume); + Builder.CreateCall(FnAssume, ArgValue); +} + static void EmitIfUsed(CodeGenFunction &CGF, llvm::BasicBlock *BB) { if (!BB) return; if (!BB->use_empty()) { @@ -1481,6 +1528,10 @@ void CodeGenFunction::GenerateCode(GlobalDecl GD, llvm::Function *Fn, // Emit the standard function prologue. StartFunction(GD, ResTy, Fn, FnInfo, Args, Loc, BodyRange.getBegin()); + // FIXME(EricWF): I don't think this should go here. + for (ContractStmt *S : FD->getPreContracts()) + EmitContractStmt(*S); + // Save parameters for coroutine function. if (Body && isa_and_nonnull(Body)) llvm::append_range(FnArgs, FD->parameters()); @@ -1556,6 +1607,11 @@ void CodeGenFunction::GenerateCode(GlobalDecl GD, llvm::Function *Fn, } } + // FIXME(EricWF): I don't think this should go here. + // Also we'll need to figure out how to reference the return value + for (ContractStmt *S : FD->getPostContracts()) + EmitContractStmt(*S); + // Emit the standard function epilogue. FinishFunction(BodyRange.getEnd()); diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h index 8525f66082a4ed..1bf24052c7c8bf 100644 --- a/clang/lib/CodeGen/CodeGenFunction.h +++ b/clang/lib/CodeGen/CodeGenFunction.h @@ -4348,6 +4348,10 @@ class CodeGenFunction : public CodeGenTypeCache { LValue EmitObjCSelectorLValue(const ObjCSelectorExpr *E); void EmitDeclRefExprDbgValue(const DeclRefExpr *E, const APValue &Init); + void EmitContractStmt(const ContractStmt &S); + void EmitCXXContractCheck(const Expr* expr); + void EmitCXXContractImply(const Expr* expr); + //===--------------------------------------------------------------------===// // Scalar Expression Emission //===--------------------------------------------------------------------===// diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp index c5289174373327..243d81084f9938 100644 --- a/clang/lib/Parse/ParseDecl.cpp +++ b/clang/lib/Parse/ParseDecl.cpp @@ -2288,6 +2288,8 @@ Parser::DeclGroupPtrTy Parser::ParseDeclGroup(ParsingDeclSpec &DS, if (Tok.is(tok::kw_requires)) ParseTrailingRequiresClause(D); + MaybeParseFunctionContractSpecifierSeq(D); + // Save late-parsed attributes for now; they need to be parsed in the // appropriate function scope after the function Decl has been constructed. // These will be parsed in ParseFunctionDefinition or ParseLexedAttrList. @@ -2537,6 +2539,10 @@ Parser::DeclGroupPtrTy Parser::ParseDeclGroup(ParsingDeclSpec &DS, // init-declarator: // declarator initializer[opt] // declarator requires-clause + + // FIXME(EricWF): Is this the correct place? + MaybeParseFunctionContractSpecifierSeq(D); + if (Tok.is(tok::kw_requires)) ParseTrailingRequiresClause(D); Decl *ThisDecl = ParseDeclarationAfterDeclarator(D, TemplateInfo); diff --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp index d02548f6441f93..e65db4d6d412f8 100644 --- a/clang/lib/Parse/ParseDeclCXX.cpp +++ b/clang/lib/Parse/ParseDeclCXX.cpp @@ -13,6 +13,7 @@ #include "clang/AST/ASTContext.h" #include "clang/AST/DeclTemplate.h" #include "clang/AST/PrettyDeclStackTrace.h" +#include "clang/AST/StmtCXX.h" #include "clang/Basic/AttributeCommonInfo.h" #include "clang/Basic/Attributes.h" #include "clang/Basic/CharInfo.h" @@ -2005,6 +2006,12 @@ void Parser::ParseClassSpecifier(tok::TokenKind TagTokKind, ConsumeBracket(); if (!SkipUntil(tok::r_square, StopAtSemi)) break; + } else if (isContractSpecifier() != ContractKeyword::None && + NextToken().is(tok::l_paren)) { + ConsumeToken(); + ConsumeParen(); + if (!SkipUntil(tok::r_paren, StopAtSemi)) + break; } else if (Tok.is(tok::kw_alignas) && NextToken().is(tok::l_paren)) { ConsumeToken(); ConsumeParen(); @@ -2514,6 +2521,26 @@ void Parser::HandleMemberFunctionDeclDelays(Declarator &DeclaratorInfo, } } +Parser::ContractKeyword Parser::isContractSpecifier(const Token &Tok) const { + if (!getLangOpts().Contracts || Tok.isNot(tok::identifier)) + return ContractKeyword::None; + + const IdentifierInfo *II = Tok.getIdentifierInfo(); + + if (!Ident_pre) { + Ident_pre = &PP.getIdentifierTable().get("pre"); + Ident_post = &PP.getIdentifierTable().get("post"); + } + + if (II == Ident_pre) + return ContractKeyword::Pre; + + if (II == Ident_post) + return ContractKeyword::Post; + + return ContractKeyword::None; +} + /// isCXX11VirtSpecifier - Determine whether the given token is a C++11 /// virt-specifier. /// @@ -2663,6 +2690,8 @@ bool Parser::ParseCXXMemberDeclaratorBeforeInitializer( VS); } + MaybeParseFunctionContractSpecifierSeq(DeclaratorInfo); + // If a simple-asm-expr is present, parse it. if (Tok.is(tok::kw_asm)) { SourceLocation Loc; @@ -4248,6 +4277,187 @@ ExceptionSpecificationType Parser::ParseDynamicExceptionSpecification( Exceptions.empty()); return Exceptions.empty() ? EST_DynamicNone : EST_Dynamic; } +/// ParseFunctionContractSpecifierSeq - Parse a series of pre/post contracts on +/// a +/// function declaration. +/// +/// function-contract-specifier-seq : +/// function-contract-specifier function-contract-specifier-seq +// +/// function-contract-specifier: +/// precondition-specifier +/// postcondition-specifier +// +/// precondition-specifier: +/// pre attribute-specifier-seq[opt] ( conditional-expression ) +/// +/// postcondition-specifier: +/// post attribute-specifier-seq[opt] ( result-name-introducer[opt] +/// conditional-expression ) +/// +/// result-name-introducer: +/// attributed-identifier : +void Parser::MaybeParseFunctionContractSpecifierSeq( + Declarator &DeclaratorInfo) { + ContractKeyword CKK; + while ((CKK = isContractSpecifier(Tok)) != ContractKeyword::None) { + StmtResult Contract = ParseFunctionContractSpecifier(DeclaratorInfo); + if (Contract.isUsable()) { + auto *CS = Contract.getAs(); + if (CKK == ContractKeyword::Pre) { + DeclaratorInfo.addPreContract(CS); + } else { + DeclaratorInfo.addPostContract(CS); + } + } + } +} + +StmtResult Parser::ParseFunctionContractSpecifier(Declarator &DeclaratorInfo) { + auto [CK, CKStr] = [&]() -> std::pair { + switch (isContractSpecifier(Tok)) { + case ContractKeyword::Pre: + return std::make_pair(ContractKind::Pre, "pre"); + case ContractKeyword::Post: + return std::make_pair(ContractKind::Post, "post"); + default: + llvm_unreachable("unhandled case"); + } + }(); + SourceLocation KeywordLoc = Tok.getLocation(); + ConsumeToken(); + + if (Tok.isNot(tok::l_paren)) { + Diag(Tok, diag::err_expected_lparen_after) << "contract_assert"; + SkipUntil({tok::equal, tok::l_brace, tok::arrow, tok::kw_try, tok::comma, + tok::l_paren}, + StopAtSemi | StopBeforeMatch); + return StmtError(); + } + + BalancedDelimiterTracker T(*this, tok::l_paren); + if (T.expectAndConsume(diag::err_expected_lparen_after, CKStr, + tok::r_paren)) { + return StmtError(); + } + + if (Tok.is(tok::identifier) && NextToken().is(tok::colon)) { + if (CK != ContractKind::Post) { + // Only post contracts can have a result name + Diag(Tok.getLocation(), diag::err_expected) << tok::identifier; + } + + IdentifierInfo *Id = Tok.getIdentifierInfo(); + SourceLocation IdLoc = ConsumeToken(); + + // FIXME(ericwf): Actually build the result name introducer + } + + SourceLocation Start = Tok.getLocation(); + + ParseScope ParamScope(this, Scope::DeclScope | + Scope::FunctionDeclarationScope | + Scope::FunctionPrototypeScope); + + DeclaratorChunk::FunctionTypeInfo FTI = DeclaratorInfo.getFunctionTypeInfo(); + for (unsigned i = 0; i != FTI.NumParams; ++i) { + ParmVarDecl *Param = cast(FTI.Params[i].Param); + Actions.ActOnReenterCXXMethodParameter(getCurScope(), Param); + } + + ExprResult Cond = ParseConditionalExpression(); + if (Cond.isUsable()) { + Cond = Actions.CorrectDelayedTyposInExpr(Cond, /*InitDecl=*/nullptr, + /*RecoverUncorrectedTypos=*/true); + } else { + if (!Tok.is(tok::r_paren)) + SkipUntil(tok::r_paren, StopAtSemi | StopBeforeMatch); + Cond = Actions.CreateRecoveryExpr( + Start, Start == Tok.getLocation() ? Start : PrevTokLocation, {}, + Actions.getASTContext().BoolTy); + } + + T.consumeClose(); + + if (CK == ContractKind::Pre) { + return Actions.ActOnPreContractAssert(KeywordLoc, Cond.get()); + } else { + return Actions.ActOnPostContractAssert(KeywordLoc, Cond.get()); + } +} + +void Parser::ParsePostContract(Declarator &DeclaratorInfo) { + ConsumeToken(); + + ParseScope ParamScope(this, Scope::DeclScope | + Scope::FunctionDeclarationScope | + Scope::FunctionPrototypeScope); + + DeclaratorChunk::FunctionTypeInfo FTI = DeclaratorInfo.getFunctionTypeInfo(); + for (unsigned i = 0; i != FTI.NumParams; ++i) { + ParmVarDecl *Param = cast(FTI.Params[i].Param); + Actions.ActOnReenterCXXMethodParameter(getCurScope(), Param); + } + + if (Tok.isNot(tok::l_paren)) { + Diag(Tok.getLocation(), diag::err_expected) << tok::l_paren; + return; + } + ConsumeParen(); + + // Post contracts start with colon + // As we have to support the "auto f() post (r : r > 42) {...}" case, we cannot parse here + // the return type is not guaranteed to be known until after the function body parses + +/* + if (Tok.isNot(tok::identifier)) { + Diag(Tok.getLocation(), diag::err_expected) << tok::identifier; + return; + } + + ParsingDeclSpec DS(*this); + + ParsedTemplateInfo TemplateInfo; + DeclSpecContext DSContext = getDeclSpecContextFromDeclaratorContext(DeclaratorContext::Block); + ParseDeclarationSpecifiers(DS, TemplateInfo, AS_none, DSContext); + + ParsedAttributes LocalAttrs(AttrFactory); + ParsingDeclarator D(*this, DS, LocalAttrs, DeclaratorContext::Block); + + D.setObjectType(getAsFunction().getReturnType()); + IdentifierInfo *Id = Tok.getIdentifierInfo(); + SourceLocation IdLoc = ConsumeToken(); + D.setIdentifier(Id, IdLoc); + + Decl* ThisDecl = Actions.ActOnDeclarator(getCurScope(), D); + Actions.ActOnUninitializedDecl(ThisDecl); + Actions.FinalizeDeclaration(ThisDecl); + D.complete(ThisDecl); + if (Tok.isNot(tok::colon)) { + Diag(Tok.getLocation(), diag::err_expected) << tok::colon; + return; + } + + ExprResult Expr = ParseExpression(); + if (Expr.isInvalid()) { + Diag(Tok.getLocation(), diag::err_invalid_pcs); + return; + } + DeclaratorInfo.addPostContract(Expr.get()); +*/ + ExprResult Expr = ParseExpression(); + if (Expr.isInvalid()) { + Diag(Tok.getLocation(), diag::err_invalid_pcs); + return; + } + // DeclaratorInfo.addPostContract(Expr.get()); + + if (Tok.isNot(tok::r_paren)) { + Diag(Tok.getLocation(), diag::err_expected) << tok::r_paren; + return; + } + ConsumeParen(); +} /// ParseTrailingReturnType - Parse a trailing return type on a new-style /// function declaration. diff --git a/clang/lib/Parse/ParseExprCXX.cpp b/clang/lib/Parse/ParseExprCXX.cpp index 1d364f77a81464..51a7f402f21b9f 100644 --- a/clang/lib/Parse/ParseExprCXX.cpp +++ b/clang/lib/Parse/ParseExprCXX.cpp @@ -1488,6 +1488,7 @@ ExprResult Parser::ParseLambdaExpressionAfterIntroducer( tok::kw___private, tok::kw___global, tok::kw___local, tok::kw___constant, tok::kw___generic, tok::kw_groupshared, tok::kw_requires, tok::kw_noexcept) || + isContractSpecifier() != ContractKeyword::None || Tok.isRegularKeywordAttribute() || (Tok.is(tok::l_square) && NextToken().is(tok::l_square)); @@ -1588,6 +1589,8 @@ ExprResult Parser::ParseLambdaExpressionAfterIntroducer( if (HasParentheses && Tok.is(tok::kw_requires)) ParseTrailingRequiresClause(D); + + MaybeParseFunctionContractSpecifierSeq(D); } // Emit a warning if we see a CUDA host/device/global attribute diff --git a/clang/lib/Parse/ParseStmt.cpp b/clang/lib/Parse/ParseStmt.cpp index 16a5b7483ec1c3..698737fe08c917 100644 --- a/clang/lib/Parse/ParseStmt.cpp +++ b/clang/lib/Parse/ParseStmt.cpp @@ -343,6 +343,11 @@ StmtResult Parser::ParseStatementOrDeclarationAfterAttributes( SemiError = "co_return"; break; + case tok::kw_contract_assert: // C++20 contract-assert-statement + Res = ParseContractAssertStatement(); + SemiError = "contract_assert"; + break; + case tok::kw_asm: { for (const ParsedAttr &AL : CXX11Attrs) // Could be relaxed if asm-related regular keyword attributes are @@ -2421,6 +2426,52 @@ StmtResult Parser::ParseBreakStatement() { return Actions.ActOnBreakStmt(BreakLoc, getCurScope()); } +/// ParseContractAssertStatement +/// +/// assertion-statement: +/// 'contract_assert' attribute-specifier-seq[opt] '(' +/// conditional-expression ')' ';' +/// +StmtResult Parser::ParseContractAssertStatement() { + assert((Tok.is(tok::kw_contract_assert)) && + "Not a contract asssert statement"); + SourceLocation KeywordLoc = ConsumeToken(); // eat the 'contract_assert'. + + // FIXME(EricWF): This seems really worg. + ParsedAttributes attrs(AttrFactory); + MaybeParseCXX11Attributes(attrs); + + if (Tok.isNot(tok::l_paren)) { + Diag(Tok, diag::err_expected_lparen_after) << "contract_assert"; + SkipUntil(tok::semi); + return StmtError(); + } + + BalancedDelimiterTracker T(*this, tok::l_paren); + T.consumeOpen(); + + SourceLocation Start = Tok.getLocation(); + + ExprResult Cond = ParseConditionalExpression(); + if (Cond.isUsable()) { + Cond = Actions.CorrectDelayedTyposInExpr(Cond, /*InitDecl=*/nullptr, + /*RecoverUncorrectedTypos=*/true); + } else { + if (!Tok.is(tok::r_paren)) + SkipUntil(tok::semi); + Cond = Actions.CreateRecoveryExpr( + Start, Start == Tok.getLocation() ? Start : PrevTokLocation, {}, + Actions.getASTContext().BoolTy); + } + + T.consumeClose(); + + if (Cond.isInvalid()) + return StmtError(); + + return Actions.ActOnContractAssert(KeywordLoc, Cond.get()); +} + /// ParseReturnStatement /// jump-statement: /// 'return' expression[opt] ';' diff --git a/clang/lib/Parse/Parser.cpp b/clang/lib/Parse/Parser.cpp index 6d0cf7b174e50e..f8e0876433cd73 100644 --- a/clang/lib/Parse/Parser.cpp +++ b/clang/lib/Parse/Parser.cpp @@ -516,6 +516,8 @@ void Parser::Initialize() { Ident_GNU_final = nullptr; Ident_import = nullptr; Ident_module = nullptr; + Ident_pre = nullptr; + Ident_post = nullptr; Ident_super = &PP.getIdentifierTable().get("super"); diff --git a/clang/lib/Sema/CMakeLists.txt b/clang/lib/Sema/CMakeLists.txt index f152d243d39a5b..32f0c0dca4f894 100644 --- a/clang/lib/Sema/CMakeLists.txt +++ b/clang/lib/Sema/CMakeLists.txt @@ -41,6 +41,7 @@ add_clang_library(clangSema SemaCodeComplete.cpp SemaConcept.cpp SemaConsumer.cpp + SemaContract.cpp SemaCoroutine.cpp SemaCUDA.cpp SemaDecl.cpp diff --git a/clang/lib/Sema/SemaContract.cpp b/clang/lib/Sema/SemaContract.cpp new file mode 100644 index 00000000000000..75a1637af0f8c4 --- /dev/null +++ b/clang/lib/Sema/SemaContract.cpp @@ -0,0 +1,89 @@ +//===--- SemaContract.cpp - Semantic Analysis for Contracts ---------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file implements semantic analysis for contracts. +// +//===----------------------------------------------------------------------===// + +#include "clang/AST/ASTContext.h" +#include "clang/AST/ASTDiagnostic.h" +#include "clang/AST/ASTLambda.h" +#include "clang/AST/CXXInheritance.h" +#include "clang/AST/CharUnits.h" +#include "clang/AST/DeclObjC.h" +#include "clang/AST/EvaluatedExprVisitor.h" +#include "clang/AST/ExprCXX.h" +#include "clang/AST/ExprObjC.h" +#include "clang/AST/IgnoreExpr.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/AST/StmtCXX.h" +#include "clang/AST/StmtObjC.h" +#include "clang/AST/TypeLoc.h" +#include "clang/AST/TypeOrdering.h" +#include "clang/Basic/TargetInfo.h" +#include "clang/Lex/Preprocessor.h" +#include "clang/Sema/EnterExpressionEvaluationContext.h" +#include "clang/Sema/Initialization.h" +#include "clang/Sema/Lookup.h" +#include "clang/Sema/Ownership.h" +#include "clang/Sema/Scope.h" +#include "clang/Sema/ScopeInfo.h" +#include "clang/Sema/SemaCUDA.h" +#include "clang/Sema/SemaInternal.h" +#include "clang/Sema/SemaObjC.h" +#include "clang/Sema/SemaOpenMP.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/STLForwardCompat.h" +#include "llvm/ADT/SmallPtrSet.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringExtras.h" + +using namespace clang; +using namespace sema; + +ExprResult Sema::ActOnContractAssertCondition(Expr *Cond) { + return PerformContextuallyConvertToBool(Cond); +} + +StmtResult Sema::BuildContractStmt(ContractKind CK, SourceLocation KeywordLoc, + Expr *Cond, DeclStmt *ResultNameDecl) { + return ContractStmt::Create(Context, CK, KeywordLoc, Cond, ResultNameDecl); +} + +StmtResult Sema::ActOnContractAssert(SourceLocation KeywordLoc, Expr *Cond) { + ExprResult CheckedCond = ActOnContractAssertCondition(Cond); + if (CheckedCond.isInvalid()) + return StmtError(); + Cond = CheckedCond.get(); + + return BuildContractStmt(ContractKind::Assert, KeywordLoc, Cond, nullptr); +} +StmtResult Sema::ActOnPreContractAssert(SourceLocation KeywordLoc, Expr *Cond) { + ExprResult CheckedCond = ActOnContractAssertCondition(Cond); + if (CheckedCond.isInvalid()) + return StmtError(); + Cond = CheckedCond.get(); + + return BuildContractStmt(ContractKind::Pre, KeywordLoc, Cond, nullptr); +} + +StmtResult Sema::ActOnPostContractAssert(SourceLocation KeywordLoc, Expr *Cond, + DeclStmt *ResultNameDecl) { + assert(ResultNameDecl == nullptr && "Result name decl not supported yet"); + + ExprResult CheckedCond = ActOnContractAssertCondition(Cond); + if (CheckedCond.isInvalid()) + return StmtError(); + Cond = CheckedCond.get(); + + return BuildContractStmt(ContractKind::Post, KeywordLoc, Cond, + ResultNameDecl); +} diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index e28e5c56c11a7b..cc6fa9fb2b59cb 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -9352,7 +9352,7 @@ static FunctionDecl *CreateNewFunctionDecl(Sema &SemaRef, Declarator &D, SemaRef.Context, DC, D.getBeginLoc(), NameInfo, R, TInfo, SC, SemaRef.getCurFPFeatures().isFPConstrained(), isInline, HasPrototype, ConstexprSpecKind::Unspecified, - /*TrailingRequiresClause=*/nullptr); + /*TrailingRequiresClause=*/nullptr, {}, {}); if (D.isInvalidType()) NewFD->setInvalidDecl(); @@ -9361,6 +9361,8 @@ static FunctionDecl *CreateNewFunctionDecl(Sema &SemaRef, Declarator &D, ExplicitSpecifier ExplicitSpecifier = D.getDeclSpec().getExplicitSpecifier(); Expr *TrailingRequiresClause = D.getTrailingRequiresClause(); + SmallVector PreContracts = D.getPreContracts(); + SmallVector PostContracts = D.getPostContracts(); SemaRef.CheckExplicitObjectMemberFunction(DC, D, Name, R); @@ -9374,7 +9376,7 @@ static FunctionDecl *CreateNewFunctionDecl(Sema &SemaRef, Declarator &D, SemaRef.Context, cast(DC), D.getBeginLoc(), NameInfo, R, TInfo, ExplicitSpecifier, SemaRef.getCurFPFeatures().isFPConstrained(), isInline, /*isImplicitlyDeclared=*/false, ConstexprKind, - InheritedConstructor(), TrailingRequiresClause); + InheritedConstructor(), TrailingRequiresClause, PreContracts, PostContracts); } else if (Name.getNameKind() == DeclarationName::CXXDestructorName) { // This is a C++ destructor declaration. @@ -9409,7 +9411,7 @@ static FunctionDecl *CreateNewFunctionDecl(Sema &SemaRef, Declarator &D, return FunctionDecl::Create( SemaRef.Context, DC, D.getBeginLoc(), D.getIdentifierLoc(), Name, R, TInfo, SC, SemaRef.getCurFPFeatures().isFPConstrained(), isInline, - /*hasPrototype=*/true, ConstexprKind, TrailingRequiresClause); + /*hasPrototype=*/true, ConstexprKind, TrailingRequiresClause, PreContracts, PostContracts); } } else if (Name.getNameKind() == DeclarationName::CXXConversionFunctionName) { @@ -9428,7 +9430,7 @@ static FunctionDecl *CreateNewFunctionDecl(Sema &SemaRef, Declarator &D, SemaRef.Context, cast(DC), D.getBeginLoc(), NameInfo, R, TInfo, SemaRef.getCurFPFeatures().isFPConstrained(), isInline, ExplicitSpecifier, ConstexprKind, SourceLocation(), - TrailingRequiresClause); + TrailingRequiresClause, PreContracts, PostContracts); } else if (Name.getNameKind() == DeclarationName::CXXDeductionGuideName) { if (TrailingRequiresClause) @@ -9457,7 +9459,7 @@ static FunctionDecl *CreateNewFunctionDecl(Sema &SemaRef, Declarator &D, CXXMethodDecl *Ret = CXXMethodDecl::Create( SemaRef.Context, cast(DC), D.getBeginLoc(), NameInfo, R, TInfo, SC, SemaRef.getCurFPFeatures().isFPConstrained(), isInline, - ConstexprKind, SourceLocation(), TrailingRequiresClause); + ConstexprKind, SourceLocation(), TrailingRequiresClause, PreContracts, PostContracts); IsVirtualOkay = !Ret->isStatic(); return Ret; } else { @@ -9472,7 +9474,7 @@ static FunctionDecl *CreateNewFunctionDecl(Sema &SemaRef, Declarator &D, return FunctionDecl::Create( SemaRef.Context, DC, D.getBeginLoc(), NameInfo, R, TInfo, SC, SemaRef.getCurFPFeatures().isFPConstrained(), isInline, - true /*HasPrototype*/, ConstexprKind, TrailingRequiresClause); + true /*HasPrototype*/, ConstexprKind, TrailingRequiresClause, PreContracts, PostContracts); } } diff --git a/clang/lib/Sema/SemaExceptionSpec.cpp b/clang/lib/Sema/SemaExceptionSpec.cpp index 67e0c7c63909ec..e7c1a5d99c8fcb 100644 --- a/clang/lib/Sema/SemaExceptionSpec.cpp +++ b/clang/lib/Sema/SemaExceptionSpec.cpp @@ -1422,6 +1422,7 @@ CanThrowResult Sema::canThrow(const Stmt *S) { case Stmt::CaseStmtClass: case Stmt::CompoundStmtClass: case Stmt::ContinueStmtClass: + case Stmt::ContractStmtClass: case Stmt::CoreturnStmtClass: case Stmt::CoroutineBodyStmtClass: case Stmt::CXXCatchStmtClass: diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp index 0681520764d9a0..469060e00bf1b6 100644 --- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp +++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp @@ -2165,6 +2165,8 @@ Decl *TemplateDeclInstantiator::VisitFunctionDecl( } Expr *TrailingRequiresClause = D->getTrailingRequiresClause(); + SmallVector PreContracts = D->getPreContracts(); + SmallVector PostContracts = D->getPostContracts(); // If we're instantiating a local function declaration, put the result // in the enclosing namespace; otherwise we need to find the instantiated @@ -2202,7 +2204,7 @@ Decl *TemplateDeclInstantiator::VisitFunctionDecl( SemaRef.Context, DC, D->getInnerLocStart(), NameInfo, T, TInfo, D->getCanonicalDecl()->getStorageClass(), D->UsesFPIntrin(), D->isInlineSpecified(), D->hasWrittenPrototype(), D->getConstexprKind(), - TrailingRequiresClause); + TrailingRequiresClause, PreContracts, PostContracts); Function->setFriendConstraintRefersToEnclosingTemplate( D->FriendConstraintRefersToEnclosingTemplate()); Function->setRangeEnd(D->getSourceRange().getEnd()); diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h index 94eee1cb3f0777..9a837cab479785 100644 --- a/clang/lib/Sema/TreeTransform.h +++ b/clang/lib/Sema/TreeTransform.h @@ -1603,6 +1603,14 @@ class TreeTransform { return getSema().BuildCoroutineBodyStmt(Args); } + /// Build a new contract_assert, pre, or post statement + // + // + StmtResult RebuildContractStmt(ContractKind K, SourceLocation KeywordLoc, + Expr *Cond) { + return getSema().BuildContractStmt(K, KeywordLoc, Cond); + } + /// Build a new Objective-C \@try statement. /// /// By default, performs semantic analysis to build the new statement. @@ -8525,6 +8533,20 @@ TreeTransform::TransformCoyieldExpr(CoyieldExpr *E) { return getDerived().RebuildCoyieldExpr(E->getKeywordLoc(), Result.get()); } +// C++ Contract Statements + +template +StmtResult TreeTransform::TransformContractStmt(ContractStmt *S) { + ExprResult OperandResult = getDerived().TransformExpr(S->getCond()); + if (OperandResult.isInvalid()) + return StmtError(); + + // Always rebuild; we don't know if this needs to be injected into a new + // context or if the promise type has changed. + return getDerived().RebuildContractStmt( + S->getContractKind(), S->getKeywordLoc(), OperandResult.get()); +} + // Objective-C Statements. template diff --git a/clang/lib/Serialization/ASTReaderStmt.cpp b/clang/lib/Serialization/ASTReaderStmt.cpp index 67ef170251914e..b6ff3bc010c2ce 100644 --- a/clang/lib/Serialization/ASTReaderStmt.cpp +++ b/clang/lib/Serialization/ASTReaderStmt.cpp @@ -502,6 +502,16 @@ void ASTStmtReader::VisitDependentCoawaitExpr(DependentCoawaitExpr *E) { SubExpr = Record.readSubStmt(); } +void ASTStmtReader::VisitContractStmt(ContractStmt *S) { + VisitStmt(S); + Record.skipInts(1); + + S->KeywordLoc = Record.readSourceLocation(); + S->setCondition(Record.readExpr()); + if (S->hasResultNameDecl()) + S->setResultNameDecl(cast(Record.readStmt())); +} + void ASTStmtReader::VisitCapturedStmt(CapturedStmt *S) { VisitStmt(S); Record.skipInts(1); @@ -4232,6 +4242,15 @@ Stmt *ASTReader::ReadStmtFromStream(ModuleFile &F) { S = new (Context) DependentCoawaitExpr(Empty); break; + case STMT_CXX_CONTRACT: { + BitsUnpacker ContractBits(Record[ASTStmtReader::NumStmtFields]); + + ContractKind CK = static_cast(ContractBits.getNextBits(2)); + bool HasResultName = ContractBits.getNextBit(); + S = ContractStmt::CreateEmpty(Context, CK, HasResultName); + break; + } + case EXPR_CONCEPT_SPECIALIZATION: { S = new (Context) ConceptSpecializationExpr(Empty); break; diff --git a/clang/lib/Serialization/ASTWriterStmt.cpp b/clang/lib/Serialization/ASTWriterStmt.cpp index 1ba6d5501fd102..b1f73953980915 100644 --- a/clang/lib/Serialization/ASTWriterStmt.cpp +++ b/clang/lib/Serialization/ASTWriterStmt.cpp @@ -465,6 +465,22 @@ void ASTStmtWriter::VisitDependentCoawaitExpr(DependentCoawaitExpr *E) { Code = serialization::EXPR_DEPENDENT_COAWAIT; } +void ASTStmtWriter::VisitContractStmt(ContractStmt *S) { + VisitStmt(S); + + CurrentPackingBits.updateBits(); + CurrentPackingBits.addBits(S->ContractAssertBits.ContractKind, + /*BitsWidth=*/2); + CurrentPackingBits.addBit(S->ContractAssertBits.HasResultName); + + Record.AddSourceLocation(S->getKeywordLoc()); + Record.AddStmt(S->getCond()); + if (S->hasResultNameDecl()) + Record.AddStmt(S->getResultNameDecl()); + + Code = serialization::STMT_CXX_CONTRACT; +} + static void addConstraintSatisfaction(ASTRecordWriter &Record, const ASTConstraintSatisfaction &Satisfaction) { diff --git a/clang/test/SemaCXX/contracts.cpp b/clang/test/SemaCXX/contracts.cpp new file mode 100644 index 00000000000000..6c962e9d41509f --- /dev/null +++ b/clang/test/SemaCXX/contracts.cpp @@ -0,0 +1,39 @@ +// RUN: %clang_cc1 -std=c++26 -fsyntax-only -verify=expected %s -fcontracts + + +void test_pre_parse(int x) pre(x != 0) { + +} + +void test_post_parse(int x) post(x != 0) { +} + +struct ImpBC { + operator bool() const; +}; + +struct ExpBC { + explicit operator bool() const; +}; + +struct ImpBC2 { + operator int() const; +}; + +void test_attributes(int x) { + [[foo::dummy]] contract_assert + [[clang::annotate("bar")]] (x != 0); // FIXME(EricWF): diagnose this + +} + +void test_converted_to_bool(int x) + pre((void)true) // expected-error {{value of type 'void' is not contextually convertible to 'bool'}} + post((void)true) // expected-error {{value of type 'void' is not contextually convertible to 'bool'}} + { + contract_assert(x != 0); + contract_assert((void)true); // expected-error {{value of type 'void' is not contextually convertible to 'bool'}} + contract_assert(ExpBC{}); + contract_assert(ImpBC{}); + contract_assert(ImpBC2{}); + +} diff --git a/clang/tools/libclang/CXCursor.cpp b/clang/tools/libclang/CXCursor.cpp index 38002052227cd2..1fbae5a3ff6dd4 100644 --- a/clang/tools/libclang/CXCursor.cpp +++ b/clang/tools/libclang/CXCursor.cpp @@ -651,7 +651,12 @@ CXCursor cxcursor::MakeCXCursor(const Stmt *S, const Decl *Parent, K = CXCursor_CXXParenListInitExpr; break; - case Stmt::MSDependentExistsStmtClass: + // FIXME(EricWF): Add support for the following Stmt classes. + case Stmt::ContractStmtClass: + K = CXCursor_UnexposedStmt; + break + + case Stmt::MSDependentExistsStmtClass: K = CXCursor_UnexposedStmt; break; case Stmt::OMPCanonicalLoopClass: diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt index 8d0ffd6ed725bd..4feb20c8b2e6a6 100644 --- a/libcxx/include/CMakeLists.txt +++ b/libcxx/include/CMakeLists.txt @@ -896,6 +896,7 @@ set(files complex.h concepts condition_variable + contracts coroutine csetjmp csignal diff --git a/libcxx/include/contracts b/libcxx/include/contracts new file mode 100644 index 00000000000000..203e40e90c6ed2 --- /dev/null +++ b/libcxx/include/contracts @@ -0,0 +1,67 @@ +#ifndef _LIBCPP_CONTRACTS +#define _LIBCPP_CONTRACTS + +#include <__config> +#include +#include + +namespace std::contracts { + +enum class assertion_kind : unsigned char { + __unknown = 0, + prec = 1, + postc = 2, + assert = 3 +}; +using _AssertKind = assertion_kind; +enum class evaluation_semantic : unsigned char { + __unknown = 0, + enforce = 1, + observe = 2 +}; +using _EvaluationSemantic = evaluation_semantic; +enum class detection_mode : unsigned char { + __unknown = 0, + predicate_false = 1, + evaluation_exception = 2 +}; +using _DetectionMode = detection_mode; + +class contract_violation { +// no user−accessible constructor +public: + // cannot be copied or moved + contract_violation(const contract_violation&) = delete; + // cannot be assigned to + contract_violation& operator=(const contract_violation&) = delete; + ~contract_violation() = default; + const char* comment() const noexcept { return __comment_; } + + _DetectionMode detection_mode() const noexcept { return __detection_mode_; } + assertion_kind kind() const noexcept { return __kind_; } + source_location location() const noexcept { return __location_; } + evaluation_semantic semantic() const noexcept { return __semantic_;} + + + contract_violation(_DetectionMode __dm = _DetectionMode::__unknown, _AssertKind __k = _AssertKind::__unknown, _EvaluationSemantic __es = _EvaluationSemantic::__unknown, const char* __c = nullptr, + source_location __loc = std::source_location::current()) noexcept + : __detection_mode_(__dm), __kind_(__k), __semantic_(__es), __comment_(__c), __location_(__loc) {} + + _DetectionMode __detection_mode_; + _AssertKind __kind_; + _EvaluationSemantic __semantic_; + const char* __comment_; + source_location __location_; +}; + + + +_LIBCPP_EXPORTED_FROM_ABI void invoke_default_contract_violation_handler(const contract_violation&) noexcept; + +_LIBCPP_EXPORTED_FROM_ABI void invoke_default_contract_violation_handler(); + +} // namespace std::contracts + +_LIBCPP_WEAK void handle_contract_violation(const std::contracts::contract_violation&) noexcept; + +#endif // _LIBCPP_CONTRACTS diff --git a/libcxx/src/CMakeLists.txt b/libcxx/src/CMakeLists.txt index 9e6c70335a7940..e19f12e920a322 100644 --- a/libcxx/src/CMakeLists.txt +++ b/libcxx/src/CMakeLists.txt @@ -8,6 +8,7 @@ set(LIBCXX_SOURCES call_once.cpp charconv.cpp chrono.cpp + contracts.cpp error_category.cpp exception.cpp expected.cpp diff --git a/libcxx/src/contracts.cpp b/libcxx/src/contracts.cpp new file mode 100644 index 00000000000000..15573e3c1a9164 --- /dev/null +++ b/libcxx/src/contracts.cpp @@ -0,0 +1,23 @@ +#include <__config> +#include + +_LIBCPP_WEAK void handle_contract_violation() noexcept { + __builtin_abort(); +} +_LIBCPP_WEAK void handle_contract_violation(const std::contracts::contract_violation&) noexcept { + __builtin_abort(); +} + +namespace std::contracts { + +void invoke_default_contract_violation_handler(const contract_violation& violation) noexcept { + ::handle_contract_violation(violation); +} + +void invoke_default_contract_violation_handler() { + invoke_default_contract_violation_handler(contract_violation()); +} + +} // namespace std::contracts + + diff --git a/libcxx/src/system_error.cpp b/libcxx/src/system_error.cpp index f518b480a27820..db145327cc2e7c 100644 --- a/libcxx/src/system_error.cpp +++ b/libcxx/src/system_error.cpp @@ -220,4 +220,8 @@ void __throw_system_error(int ev, const char* what_arg) { #endif } +void contract_violation() { + std::abort(); +} + _LIBCPP_END_NAMESPACE_STD diff --git a/libcxx/test/CMakeLists.txt b/libcxx/test/CMakeLists.txt index 3c54a4edccff38..670e628dc20cf0 100644 --- a/libcxx/test/CMakeLists.txt +++ b/libcxx/test/CMakeLists.txt @@ -1,5 +1,5 @@ include(HandleLitArguments) -add_subdirectory(tools) + # By default, libcxx and libcxxabi share a library directory. if (NOT LIBCXX_CXX_ABI_LIBRARY_PATH) diff --git a/libcxx/test/configs/llvm-libc++-shared.cfg.in b/libcxx/test/configs/llvm-libc++-shared.cfg.in index 0c059f0c7ff328..c6b3a722c88610 100644 --- a/libcxx/test/configs/llvm-libc++-shared.cfg.in +++ b/libcxx/test/configs/llvm-libc++-shared.cfg.in @@ -7,7 +7,7 @@ config.substitutions.append(('%{flags}', '-pthread' + (' -isysroot {}'.format('@CMAKE_OSX_SYSROOT@') if '@CMAKE_OSX_SYSROOT@' else '') )) config.substitutions.append(('%{compile_flags}', - '-nostdinc++ -I %{target-include-dir} -I %{include-dir} -I %{libcxx-dir}/test/support' + '-Xclang -fcontracts -nostdinc++ -I %{target-include-dir} -I %{include-dir} -I %{libcxx-dir}/test/support' )) config.substitutions.append(('%{link_flags}', '-nostdlib++ -L %{lib-dir} -Wl,-rpath,%{lib-dir} -lc++' diff --git a/libcxx/test/std/contracts/breathing_test.pass.cpp b/libcxx/test/std/contracts/breathing_test.pass.cpp new file mode 100644 index 00000000000000..3f14ee7771de9b --- /dev/null +++ b/libcxx/test/std/contracts/breathing_test.pass.cpp @@ -0,0 +1,14 @@ +#include +#include + +using std::contracts::contract_violation; + +void test(int x) pre(x != 1) { + contract_assert(x != 0); +} + + +int main() { + test(2); + test(1); +} diff --git a/libcxx/utils/libcxx/test/params.py b/libcxx/utils/libcxx/test/params.py index 4c8590a2135d9e..0517a225aaf077 100644 --- a/libcxx/utils/libcxx/test/params.py +++ b/libcxx/utils/libcxx/test/params.py @@ -73,7 +73,7 @@ "-Wno-self-move", ] -_allStandards = ["c++03", "c++11", "c++14", "c++17", "c++20", "c++23", "c++26"] +_allStandards = ["c++03", "c++11", "c++14", "c++17", "c++20", "c++23", "c++26", "c++2c"] def getStdFlag(cfg, std): From de839295e10c899e98366f731cea5233ef13222b Mon Sep 17 00:00:00 2001 From: Eric Fiselier Date: Wed, 19 Jun 2024 16:30:05 -0500 Subject: [PATCH 2/9] fix todo --- CONTRACTS_TODO.txt | 15 +++++++++++++++ clang/lib/Sema/SemaContract.cpp | 1 + clang/tools/libclang/CXCursor.cpp | 4 ++-- 3 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 CONTRACTS_TODO.txt diff --git a/CONTRACTS_TODO.txt b/CONTRACTS_TODO.txt new file mode 100644 index 00000000000000..4a97987400b8d9 --- /dev/null +++ b/CONTRACTS_TODO.txt @@ -0,0 +1,15 @@ +* Parse result name in post contracts + * auto declared functions. + * templates. + * Late parsing in general +* Diagnose +* Test parsing failures +* What happens when pre/post throws from a noexcept function? Can we even callee-emit contracts? +* Start constification +* Check 'this' access in pre/post +* Codegen: + * Make it work + +* Required headers? + + diff --git a/clang/lib/Sema/SemaContract.cpp b/clang/lib/Sema/SemaContract.cpp index 75a1637af0f8c4..b3f90cb580f957 100644 --- a/clang/lib/Sema/SemaContract.cpp +++ b/clang/lib/Sema/SemaContract.cpp @@ -66,6 +66,7 @@ StmtResult Sema::ActOnContractAssert(SourceLocation KeywordLoc, Expr *Cond) { return BuildContractStmt(ContractKind::Assert, KeywordLoc, Cond, nullptr); } + StmtResult Sema::ActOnPreContractAssert(SourceLocation KeywordLoc, Expr *Cond) { ExprResult CheckedCond = ActOnContractAssertCondition(Cond); if (CheckedCond.isInvalid()) diff --git a/clang/tools/libclang/CXCursor.cpp b/clang/tools/libclang/CXCursor.cpp index 1fbae5a3ff6dd4..f6965f2a0a9955 100644 --- a/clang/tools/libclang/CXCursor.cpp +++ b/clang/tools/libclang/CXCursor.cpp @@ -654,9 +654,9 @@ CXCursor cxcursor::MakeCXCursor(const Stmt *S, const Decl *Parent, // FIXME(EricWF): Add support for the following Stmt classes. case Stmt::ContractStmtClass: K = CXCursor_UnexposedStmt; - break + break; - case Stmt::MSDependentExistsStmtClass: + case Stmt::MSDependentExistsStmtClass: K = CXCursor_UnexposedStmt; break; case Stmt::OMPCanonicalLoopClass: From 3a3d504940026eeb01dc586a2572414ed1aa533d Mon Sep 17 00:00:00 2001 From: Eric Fiselier Date: Fri, 21 Jun 2024 20:19:01 -0500 Subject: [PATCH 3/9] get it working end-to-end --- clang/include/clang/AST/Decl.h | 29 +++---- clang/include/clang/AST/DeclCXX.h | 32 +++----- clang/include/clang/AST/StmtCXX.h | 10 +-- clang/include/clang/Basic/Builtins.td | 6 -- clang/include/clang/Sema/DeclSpec.h | 19 +---- clang/lib/AST/ASTImporter.cpp | 14 ++-- clang/lib/AST/Decl.cpp | 31 ++------ clang/lib/AST/DeclCXX.cpp | 43 +++++------ clang/lib/AST/ExprConstant.cpp | 52 ++++++------- clang/lib/CodeGen/CGBuiltin.cpp | 5 -- clang/lib/CodeGen/CGStmt.cpp | 3 + clang/lib/CodeGen/CodeGenFunction.cpp | 15 ++-- clang/lib/Parse/ParseDeclCXX.cpp | 75 +++++++++---------- clang/lib/Sema/SemaContract.cpp | 12 ++- clang/lib/Sema/SemaDecl.cpp | 23 +++--- .../lib/Sema/SemaTemplateInstantiateDecl.cpp | 5 +- clang/lib/StaticAnalyzer/Core/ExprEngine.cpp | 3 +- 17 files changed, 158 insertions(+), 219 deletions(-) diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h index 96b70411940696..eebd7fc546e106 100644 --- a/clang/include/clang/AST/Decl.h +++ b/clang/include/clang/AST/Decl.h @@ -775,8 +775,7 @@ class DeclaratorDecl : public ValueDecl { struct ExtInfo : public QualifierInfo { TypeSourceInfo *TInfo; Expr *TrailingRequiresClause = nullptr; - SmallVector PreContracts = {}; - SmallVector PostContracts = {}; + SmallVector Contracts = {}; }; llvm::PointerUnion DeclInfo; @@ -856,21 +855,15 @@ class DeclaratorDecl : public ValueDecl { : nullptr; } - SmallVector getPreContracts() const { - if (hasExtInfo()) return getExtInfo()->PreContracts; - return {}; - } - - SmallVector getPostContracts() const { - if (hasExtInfo()) return getExtInfo()->PostContracts; + SmallVector getContracts() const { + if (hasExtInfo()) + return getExtInfo()->Contracts; return {}; } void setTrailingRequiresClause(Expr *TrailingRequiresClause); - void setPreContracts(SmallVector PreContracts); - - void setPostContracts(SmallVector PostContracts); + void setContracts(SmallVector Contracts); unsigned getNumTemplateParameterLists() const { return hasExtInfo() ? getExtInfo()->NumTemplParamLists : 0; @@ -2145,8 +2138,7 @@ class FunctionDecl : public DeclaratorDecl, TypeSourceInfo *TInfo, StorageClass S, bool UsesFPIntrin, bool isInlineSpecified, ConstexprSpecKind ConstexprKind, Expr *TrailingRequiresClause = nullptr, - SmallVector PreContracts = {}, - SmallVector PostContracts = {}); + SmallVector Contracts = {}); using redeclarable_base = Redeclarable; @@ -2183,13 +2175,13 @@ class FunctionDecl : public DeclaratorDecl, bool isInlineSpecified = false, bool hasWrittenPrototype = true, ConstexprSpecKind ConstexprKind = ConstexprSpecKind::Unspecified, Expr *TrailingRequiresClause = nullptr, - SmallVector PreContracts = {}, - SmallVector PostContracts = {}) { + SmallVector Contracts = {}) { + DeclarationNameInfo NameInfo(N, NLoc); return FunctionDecl::Create(C, DC, StartLoc, NameInfo, T, TInfo, SC, UsesFPIntrin, isInlineSpecified, hasWrittenPrototype, ConstexprKind, - TrailingRequiresClause, PreContracts, PostContracts); + TrailingRequiresClause, Contracts); } static FunctionDecl * @@ -2197,8 +2189,7 @@ class FunctionDecl : public DeclaratorDecl, const DeclarationNameInfo &NameInfo, QualType T, TypeSourceInfo *TInfo, StorageClass SC, bool UsesFPIntrin, bool isInlineSpecified, bool hasWrittenPrototype, ConstexprSpecKind ConstexprKind, - Expr *TrailingRequiresClause, SmallVector PreContracts, - SmallVector PostContracts); + Expr *TrailingRequiresClause, SmallVector Contracts); static FunctionDecl *CreateDeserialized(ASTContext &C, GlobalDeclID ID); diff --git a/clang/include/clang/AST/DeclCXX.h b/clang/include/clang/AST/DeclCXX.h index 314daaa061dc45..88fb1184b01f19 100644 --- a/clang/include/clang/AST/DeclCXX.h +++ b/clang/include/clang/AST/DeclCXX.h @@ -2067,11 +2067,10 @@ class CXXMethodDecl : public FunctionDecl { bool UsesFPIntrin, bool isInline, ConstexprSpecKind ConstexprKind, SourceLocation EndLocation, Expr *TrailingRequiresClause = nullptr, - SmallVector PreContracts = {}, - SmallVector PostContracts = {}) + SmallVector Contracts = {}) : FunctionDecl(DK, C, RD, StartLoc, NameInfo, T, TInfo, SC, UsesFPIntrin, isInline, ConstexprKind, TrailingRequiresClause, - PreContracts, PostContracts) { + Contracts) { if (EndLocation.isValid()) setRangeEnd(EndLocation); } @@ -2083,8 +2082,7 @@ class CXXMethodDecl : public FunctionDecl { StorageClass SC, bool UsesFPIntrin, bool isInline, ConstexprSpecKind ConstexprKind, SourceLocation EndLocation, Expr *TrailingRequiresClause = nullptr, - SmallVector PreContracts = {}, - SmallVector PostContracts = {}); + SmallVector Contracts = {}); static CXXMethodDecl *CreateDeserialized(ASTContext &C, GlobalDeclID ID); @@ -2553,8 +2551,7 @@ class CXXConstructorDecl final bool isImplicitlyDeclared, ConstexprSpecKind ConstexprKind, InheritedConstructor Inherited, Expr *TrailingRequiresClause, - SmallVector PreContracts, - SmallVector PostContracts); + SmallVector Contracts); void anchor() override; @@ -2598,8 +2595,7 @@ class CXXConstructorDecl final bool isImplicitlyDeclared, ConstexprSpecKind ConstexprKind, InheritedConstructor Inherited = InheritedConstructor(), Expr *TrailingRequiresClause = nullptr, - SmallVector PreContracts = {}, - SmallVector PostContracts = {}); + SmallVector Contracts = {}); void setExplicitSpecifier(ExplicitSpecifier ES) { assert((!ES.getExpr() || @@ -2819,12 +2815,10 @@ class CXXDestructorDecl : public CXXMethodDecl { TypeSourceInfo *TInfo, bool UsesFPIntrin, bool isInline, bool isImplicitlyDeclared, ConstexprSpecKind ConstexprKind, Expr *TrailingRequiresClause = nullptr, - SmallVector PreContracts = {}, - SmallVector PostContracts = {}) + SmallVector Contracts = {}) : CXXMethodDecl(CXXDestructor, C, RD, StartLoc, NameInfo, T, TInfo, SC_None, UsesFPIntrin, isInline, ConstexprKind, - SourceLocation(), TrailingRequiresClause, PreContracts, - PostContracts) { + SourceLocation(), TrailingRequiresClause, Contracts) { setImplicit(isImplicitlyDeclared); } @@ -2837,8 +2831,7 @@ class CXXDestructorDecl : public CXXMethodDecl { bool UsesFPIntrin, bool isInline, bool isImplicitlyDeclared, ConstexprSpecKind ConstexprKind, Expr *TrailingRequiresClause = nullptr, - SmallVector PreContracts = {}, - SmallVector PostContracts = {}); + SmallVector Contracts = {}); static CXXDestructorDecl *CreateDeserialized(ASTContext &C, GlobalDeclID ID); void setOperatorDelete(FunctionDecl *OD, Expr *ThisArg); @@ -2880,12 +2873,10 @@ class CXXConversionDecl : public CXXMethodDecl { ExplicitSpecifier ES, ConstexprSpecKind ConstexprKind, SourceLocation EndLocation, Expr *TrailingRequiresClause = nullptr, - SmallVector PreContracts = {}, - SmallVector PostContracts = {}) + SmallVector Contracts = {}) : CXXMethodDecl(CXXConversion, C, RD, StartLoc, NameInfo, T, TInfo, SC_None, UsesFPIntrin, isInline, ConstexprKind, - EndLocation, TrailingRequiresClause, PreContracts, - PostContracts), + EndLocation, TrailingRequiresClause, Contracts), ExplicitSpec(ES) {} void anchor() override; @@ -2901,8 +2892,7 @@ class CXXConversionDecl : public CXXMethodDecl { bool UsesFPIntrin, bool isInline, ExplicitSpecifier ES, ConstexprSpecKind ConstexprKind, SourceLocation EndLocation, Expr *TrailingRequiresClause = nullptr, - SmallVector PreContracts = {}, - SmallVector PostContracts = {}); + SmallVector Contracts = {}); static CXXConversionDecl *CreateDeserialized(ASTContext &C, GlobalDeclID ID); ExplicitSpecifier getExplicitSpecifier() { diff --git a/clang/include/clang/AST/StmtCXX.h b/clang/include/clang/AST/StmtCXX.h index 0d2b3e9d073b9c..3e0f92dac9acd4 100644 --- a/clang/include/clang/AST/StmtCXX.h +++ b/clang/include/clang/AST/StmtCXX.h @@ -531,10 +531,7 @@ class ContractStmt final : public Stmt, friend class ASTStmtReader; friend TrailingObjects; - enum { ResultNameDeclOffset = 0, ConditionOffset = 1, Count = 2 }; - -public: - // These types correspond to the three C++ 'await_suspend' return variants + enum { ResultNameDeclOffset = 0, Count = 1 }; private: unsigned condOffset() const { @@ -552,6 +549,7 @@ class ContractStmt final : public Stmt, : Stmt(ContractStmtClass), KeywordLoc(KeywordLoc) { ContractAssertBits.ContractKind = static_cast(CK); ContractAssertBits.HasResultName = false; + setCondition(Condition); } ContractStmt(EmptyShell Empty, ContractKind Kind, bool HasResultName = false) @@ -588,9 +586,7 @@ class ContractStmt final : public Stmt, getTrailingObjects()[ResultNameDeclOffset] = D; } - void setCondition(Expr *E) { - getTrailingObjects()[ConditionOffset] = E; - } + void setCondition(Expr *E) { getTrailingObjects()[condOffset()] = E; } Expr *getCond() { return reinterpret_cast(getTrailingObjects()[condOffset()]); diff --git a/clang/include/clang/Basic/Builtins.td b/clang/include/clang/Basic/Builtins.td index 381050ea22a5f4..33b2c8b77142fb 100644 --- a/clang/include/clang/Basic/Builtins.td +++ b/clang/include/clang/Basic/Builtins.td @@ -2582,12 +2582,6 @@ def ContractViolation : Builtin { let Attributes = [NoThrow, NoReturn, Constexpr]; } -def ContractAssert : Builtin { - let Spellings = ["__builtin_contract_assert"]; - let Attributes = [NoThrow, Constexpr]; - - let Prototype = "void(bool)"; -} // C99 library functions // C99 stdarg.h diff --git a/clang/include/clang/Sema/DeclSpec.h b/clang/include/clang/Sema/DeclSpec.h index f249d1f27cb786..3ef7a09c12e91d 100644 --- a/clang/include/clang/Sema/DeclSpec.h +++ b/clang/include/clang/Sema/DeclSpec.h @@ -1971,11 +1971,8 @@ class Declarator { /// requires-clause, or null if no such clause was specified. Expr *TrailingRequiresClause; - /// \brief All pre contracts specified by the function declaration - SmallVector PreContracts; - - /// \brief All post contracts specified by the function declaration - SmallVector PostContracts; + /// \brief All pre and post contracts specified by the function declaration + SmallVector Contracts; /// If this declarator declares a template, its template parameter lists. ArrayRef TemplateParameterLists; @@ -2648,18 +2645,10 @@ class Declarator { } /// \brief Add a pre contract for this declarator - void addPreContract(ContractStmt *TRC) { PreContracts.push_back(TRC); } + void addContract(ContractStmt *TRC) { Contracts.push_back(TRC); } /// \brief Get all pre contracts for this declarator - const SmallVector &getPreContracts() { return PreContracts; } - - /// \brief Add a post contract for this declarator - void addPostContract(ContractStmt *TRC) { PostContracts.push_back(TRC); } - - /// \brief Get all post contracts for this declarator - const SmallVector &getPostContracts() { - return PostContracts; - } + const SmallVector &getContracts() { return Contracts; } /// Sets the template parameter lists that preceded the declarator. void setTemplateParameterLists(ArrayRef TPLs) { diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp index 7bcaa6faa5486b..3819ec23f97238 100644 --- a/clang/lib/AST/ASTImporter.cpp +++ b/clang/lib/AST/ASTImporter.cpp @@ -3861,8 +3861,7 @@ ExpectedDecl ASTNodeImporter::VisitFunctionDecl(FunctionDecl *D) { auto ToQualifierLoc = importChecked(Err, D->getQualifierLoc()); auto TrailingRequiresClause = importChecked(Err, D->getTrailingRequiresClause()); - auto PreContracts = D->getPreContracts(); - auto PostContracts = D->getPostContracts(); + auto Contracts = D->getContracts(); if (Err) return std::move(Err); @@ -3894,7 +3893,7 @@ ExpectedDecl ASTNodeImporter::VisitFunctionDecl(FunctionDecl *D) { ToFunction, D, Importer.getToContext(), cast(DC), ToInnerLocStart, NameInfo, T, TInfo, ESpec, D->UsesFPIntrin(), D->isInlineSpecified(), D->isImplicit(), D->getConstexprKind(), - ToInheritedConstructor, TrailingRequiresClause, PreContracts, PostContracts)) + ToInheritedConstructor, TrailingRequiresClause, Contracts)) return ToFunction; } else if (CXXDestructorDecl *FromDtor = dyn_cast(D)) { @@ -3909,7 +3908,7 @@ ExpectedDecl ASTNodeImporter::VisitFunctionDecl(FunctionDecl *D) { ToFunction, D, Importer.getToContext(), cast(DC), ToInnerLocStart, NameInfo, T, TInfo, D->UsesFPIntrin(), D->isInlineSpecified(), D->isImplicit(), D->getConstexprKind(), - TrailingRequiresClause, PreContracts, PostContracts)) + TrailingRequiresClause, Contracts)) return ToFunction; CXXDestructorDecl *ToDtor = cast(ToFunction); @@ -3925,14 +3924,15 @@ ExpectedDecl ASTNodeImporter::VisitFunctionDecl(FunctionDecl *D) { ToFunction, D, Importer.getToContext(), cast(DC), ToInnerLocStart, NameInfo, T, TInfo, D->UsesFPIntrin(), D->isInlineSpecified(), ESpec, D->getConstexprKind(), - SourceLocation(), TrailingRequiresClause, PreContracts, PostContracts)) + SourceLocation(), TrailingRequiresClause, Contracts)) return ToFunction; } else if (auto *Method = dyn_cast(D)) { if (GetImportedOrCreateDecl( ToFunction, D, Importer.getToContext(), cast(DC), ToInnerLocStart, NameInfo, T, TInfo, Method->getStorageClass(), Method->UsesFPIntrin(), Method->isInlineSpecified(), - D->getConstexprKind(), SourceLocation(), TrailingRequiresClause, PreContracts, PostContracts)) + D->getConstexprKind(), SourceLocation(), TrailingRequiresClause, + Contracts)) return ToFunction; } else if (auto *Guide = dyn_cast(D)) { ExplicitSpecifier ESpec = @@ -3952,7 +3952,7 @@ ExpectedDecl ASTNodeImporter::VisitFunctionDecl(FunctionDecl *D) { ToFunction, D, Importer.getToContext(), DC, ToInnerLocStart, NameInfo, T, TInfo, D->getStorageClass(), D->UsesFPIntrin(), D->isInlineSpecified(), D->hasWrittenPrototype(), - D->getConstexprKind(), TrailingRequiresClause, PreContracts, PostContracts)) + D->getConstexprKind(), TrailingRequiresClause, Contracts)) return ToFunction; } diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp index 3ecce1dfb1263d..540b8163a170cf 100644 --- a/clang/lib/AST/Decl.cpp +++ b/clang/lib/AST/Decl.cpp @@ -2021,7 +2021,7 @@ void DeclaratorDecl::setTrailingRequiresClause(Expr *TrailingRequiresClause) { getExtInfo()->TrailingRequiresClause = TrailingRequiresClause; } -void DeclaratorDecl::setPreContracts(SmallVector PreContracts) { +void DeclaratorDecl::setContracts(SmallVector Contracts) { // Make sure the extended decl info is allocated. if (!hasExtInfo()) { // Save (non-extended) type source info pointer. @@ -2032,22 +2032,7 @@ void DeclaratorDecl::setPreContracts(SmallVector PreContracts) { getExtInfo()->TInfo = savedTInfo; } // Set requires clause info. - getExtInfo()->PreContracts = PreContracts; -} - -void DeclaratorDecl::setPostContracts( - SmallVector PostContracts) { - // Make sure the extended decl info is allocated. - if (!hasExtInfo()) { - // Save (non-extended) type source info pointer. - auto *savedTInfo = DeclInfo.get(); - // Allocate external info struct. - DeclInfo = new (getASTContext()) ExtInfo; - // Restore savedTInfo into (extended) decl info. - getExtInfo()->TInfo = savedTInfo; - } - // Set requires clause info. - getExtInfo()->PostContracts = PostContracts; + getExtInfo()->Contracts = Contracts; } void DeclaratorDecl::setTemplateParameterListsInfo( @@ -3066,8 +3051,7 @@ FunctionDecl::FunctionDecl(Kind DK, ASTContext &C, DeclContext *DC, bool UsesFPIntrin, bool isInlineSpecified, ConstexprSpecKind ConstexprKind, Expr *TrailingRequiresClause, - SmallVector PreContracts, - SmallVector PostContracts) + SmallVector Contracts) : DeclaratorDecl(DK, DC, NameInfo.getLoc(), NameInfo.getName(), T, TInfo, StartLoc), DeclContext(DK), redeclarable_base(C), Body(), ODRHash(0), @@ -3103,8 +3087,8 @@ FunctionDecl::FunctionDecl(Kind DK, ASTContext &C, DeclContext *DC, FunctionDeclBits.FriendConstraintRefersToEnclosingTemplate = false; if (TrailingRequiresClause) setTrailingRequiresClause(TrailingRequiresClause); - setPreContracts(PreContracts); - setPostContracts(PostContracts); + // FIXME(EricWF): Make this conditional? + setContracts(Contracts); } void FunctionDecl::getNameForDiagnostic( @@ -5427,11 +5411,10 @@ FunctionDecl *FunctionDecl::Create( const DeclarationNameInfo &NameInfo, QualType T, TypeSourceInfo *TInfo, StorageClass SC, bool UsesFPIntrin, bool isInlineSpecified, bool hasWrittenPrototype, ConstexprSpecKind ConstexprKind, - Expr *TrailingRequiresClause, SmallVector PreContracts, - SmallVector PostContracts) { + Expr *TrailingRequiresClause, SmallVector Contracts) { FunctionDecl *New = new (C, DC) FunctionDecl( Function, C, DC, StartLoc, NameInfo, T, TInfo, SC, UsesFPIntrin, - isInlineSpecified, ConstexprKind, TrailingRequiresClause, PreContracts, PostContracts); + isInlineSpecified, ConstexprKind, TrailingRequiresClause, Contracts); New->setHasWrittenPrototype(hasWrittenPrototype); return New; } diff --git a/clang/lib/AST/DeclCXX.cpp b/clang/lib/AST/DeclCXX.cpp index 0147d860326b59..e74951c8e6e4dc 100644 --- a/clang/lib/AST/DeclCXX.cpp +++ b/clang/lib/AST/DeclCXX.cpp @@ -2276,19 +2276,19 @@ CXXMethodDecl::Create(ASTContext &C, CXXRecordDecl *RD, SourceLocation StartLoc, TypeSourceInfo *TInfo, StorageClass SC, bool UsesFPIntrin, bool isInline, ConstexprSpecKind ConstexprKind, SourceLocation EndLocation, Expr *TrailingRequiresClause, - SmallVector PreContracts, - SmallVector PostContracts) { + SmallVector Contracts) { return new (C, RD) CXXMethodDecl( CXXMethod, C, RD, StartLoc, NameInfo, T, TInfo, SC, UsesFPIntrin, - isInline, ConstexprKind, EndLocation, TrailingRequiresClause, PreContracts, PostContracts); + isInline, ConstexprKind, EndLocation, TrailingRequiresClause, Contracts); } CXXMethodDecl *CXXMethodDecl::CreateDeserialized(ASTContext &C, GlobalDeclID ID) { - return new (C, ID) CXXMethodDecl( - CXXMethod, C, nullptr, SourceLocation(), DeclarationNameInfo(), - QualType(), nullptr, SC_None, false, false, - ConstexprSpecKind::Unspecified, SourceLocation(), nullptr, {}, {}); + return new (C, ID) + CXXMethodDecl(CXXMethod, C, nullptr, SourceLocation(), + DeclarationNameInfo(), QualType(), nullptr, SC_None, false, + false, ConstexprSpecKind::Unspecified, SourceLocation(), + nullptr, /*Contracts=*/{}); } CXXMethodDecl *CXXMethodDecl::getDevirtualizedMethod(const Expr *Base, @@ -2687,12 +2687,11 @@ CXXConstructorDecl::CXXConstructorDecl( ExplicitSpecifier ES, bool UsesFPIntrin, bool isInline, bool isImplicitlyDeclared, ConstexprSpecKind ConstexprKind, InheritedConstructor Inherited, Expr *TrailingRequiresClause, - SmallVector PreContracts, - SmallVector PostContracts) + + SmallVector Contracts) : CXXMethodDecl(CXXConstructor, C, RD, StartLoc, NameInfo, T, TInfo, SC_None, UsesFPIntrin, isInline, ConstexprKind, - SourceLocation(), TrailingRequiresClause, PreContracts, - PostContracts) { + SourceLocation(), TrailingRequiresClause, Contracts) { setNumCtorInitializers(0); setInheritingConstructor(static_cast(Inherited)); setImplicit(isImplicitlyDeclared); @@ -2716,7 +2715,7 @@ CXXConstructorDecl *CXXConstructorDecl::CreateDeserialized(ASTContext &C, auto *Result = new (C, ID, Extra) CXXConstructorDecl( C, nullptr, SourceLocation(), DeclarationNameInfo(), QualType(), nullptr, ExplicitSpecifier(), false, false, false, ConstexprSpecKind::Unspecified, - InheritedConstructor(), nullptr, {}, {}); + InheritedConstructor(), nullptr, /*Contracts=*/{}); Result->setInheritingConstructor(isInheritingConstructor); Result->CXXConstructorDeclBits.HasTrailingExplicitSpecifier = hasTrailingExplicit; @@ -2730,17 +2729,17 @@ CXXConstructorDecl *CXXConstructorDecl::Create( ExplicitSpecifier ES, bool UsesFPIntrin, bool isInline, bool isImplicitlyDeclared, ConstexprSpecKind ConstexprKind, InheritedConstructor Inherited, Expr *TrailingRequiresClause, - SmallVector PreContracts, - SmallVector PostContracts) { + SmallVector Contracts) { assert(NameInfo.getName().getNameKind() == DeclarationName::CXXConstructorName && "Name must refer to a constructor"); unsigned Extra = additionalSizeToAlloc( Inherited ? 1 : 0, ES.getExpr() ? 1 : 0); - return new (C, RD, Extra) CXXConstructorDecl( - C, RD, StartLoc, NameInfo, T, TInfo, ES, UsesFPIntrin, isInline, - isImplicitlyDeclared, ConstexprKind, Inherited, TrailingRequiresClause, PreContracts, PostContracts); + return new (C, RD, Extra) + CXXConstructorDecl(C, RD, StartLoc, NameInfo, T, TInfo, ES, UsesFPIntrin, + isInline, isImplicitlyDeclared, ConstexprKind, + Inherited, TrailingRequiresClause, Contracts); } CXXConstructorDecl::init_const_iterator CXXConstructorDecl::init_begin() const { @@ -2865,14 +2864,13 @@ CXXDestructorDecl *CXXDestructorDecl::Create( const DeclarationNameInfo &NameInfo, QualType T, TypeSourceInfo *TInfo, bool UsesFPIntrin, bool isInline, bool isImplicitlyDeclared, ConstexprSpecKind ConstexprKind, Expr *TrailingRequiresClause, - SmallVector PreContracts, - SmallVector PostContracts) { + SmallVector Contracts) { assert(NameInfo.getName().getNameKind() == DeclarationName::CXXDestructorName && "Name must refer to a destructor"); return new (C, RD) CXXDestructorDecl( C, RD, StartLoc, NameInfo, T, TInfo, UsesFPIntrin, isInline, - isImplicitlyDeclared, ConstexprKind, TrailingRequiresClause, PreContracts, PostContracts); + isImplicitlyDeclared, ConstexprKind, TrailingRequiresClause, Contracts); } void CXXDestructorDecl::setOperatorDelete(FunctionDecl *OD, Expr *ThisArg) { @@ -2900,14 +2898,13 @@ CXXConversionDecl *CXXConversionDecl::Create( const DeclarationNameInfo &NameInfo, QualType T, TypeSourceInfo *TInfo, bool UsesFPIntrin, bool isInline, ExplicitSpecifier ES, ConstexprSpecKind ConstexprKind, SourceLocation EndLocation, - Expr *TrailingRequiresClause, SmallVector PreContracts, - SmallVector PostContracts) { + Expr *TrailingRequiresClause, SmallVector Contracts) { assert(NameInfo.getName().getNameKind() == DeclarationName::CXXConversionFunctionName && "Name must refer to a conversion function"); return new (C, RD) CXXConversionDecl( C, RD, StartLoc, NameInfo, T, TInfo, UsesFPIntrin, isInline, ES, - ConstexprKind, EndLocation, TrailingRequiresClause, PreContracts, PostContracts); + ConstexprKind, EndLocation, TrailingRequiresClause, Contracts); } bool CXXConversionDecl::isLambdaToBlockPointerConversion() const { diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 7e15a68aa03261..2e72e2b1b5c27f 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -6294,32 +6294,35 @@ static bool handleTrivialCopy(EvalInfo &Info, const ParmVarDecl *Param, CopyObjectRepresentation); } -static bool EvaluatePreContracts(const FunctionDecl* Callee, EvalInfo& Info) { - for (ContractStmt *S : Callee->getPreContracts()) { - Expr *E = S->getCond(); - APSInt Desired; - if (!EvaluateInteger(E, Desired, Info)){ - return false; - } - if (!Desired) { - Info.CCEDiag(E, diag::note_constexpr_contract_failure); +static bool EvaluateContract(const ContractStmt *S, EvalInfo &Info) { + const Expr *E = S->getCond(); + APSInt Desired; + if (!EvaluateInteger(E, Desired, Info)) { + return false; + } + if (!Desired) { + Info.CCEDiag(E, diag::note_constexpr_contract_failure); + return false; + } + return true; +} + +static bool EvaluatePreContracts(const FunctionDecl *Callee, EvalInfo &Info) { + for (ContractStmt *S : Callee->getContracts()) { + if (S->getContractKind() != ContractKind::Pre) + continue; + if (!EvaluateContract(S, Info)) return false; - } } return true; } static bool EvaluatePostContracts(const FunctionDecl* Callee, EvalInfo& Info) { - for (ContractStmt *S : Callee->getPostContracts()) { - Expr *E = S->getCond(); - APSInt Desired; - if (!EvaluateInteger(E, Desired, Info)){ - return false; - } - if (!Desired) { - Info.CCEDiag(E, diag::note_constexpr_contract_failure); + for (ContractStmt *S : Callee->getContracts()) { + if (S->getContractKind() != ContractKind::Post) + continue; + if (!EvaluateContract(S, Info)) return false; - } } return true; } @@ -15586,17 +15589,6 @@ class VoidExprEvaluator case Builtin::BI__builtin_operator_delete: return HandleOperatorDeleteCall(Info, E); - case Builtin::BI__builtin_contract_assert: { - APSInt Desired; - if (!EvaluateInteger(E->getArg(0), Desired, Info)){ - return false; - } - if (!Desired) { - Info.CCEDiag(E->getArg(0), diag::note_constexpr_contract_failure); - return false; - } - return true; - } default: return false; } diff --git a/clang/lib/CodeGen/CGBuiltin.cpp b/clang/lib/CodeGen/CGBuiltin.cpp index 48251bc38cc479..4f393ac1d18058 100644 --- a/clang/lib/CodeGen/CGBuiltin.cpp +++ b/clang/lib/CodeGen/CGBuiltin.cpp @@ -3359,11 +3359,6 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID, Builder.CreateCall(FnAssume, ArgValue); return RValue::get(nullptr); } - case Builtin::BI__builtin_contract_assert: { - EmitCXXContractCheck(E->getArg(0)); - EmitCXXContractImply(E->getArg(0)); - return RValue::get(nullptr); - } case Builtin::BI__builtin_assume_separate_storage: { const Expr *Arg0 = E->getArg(0); const Expr *Arg1 = E->getArg(1); diff --git a/clang/lib/CodeGen/CGStmt.cpp b/clang/lib/CodeGen/CGStmt.cpp index 07032e065c5d35..419c21e0e475d9 100644 --- a/clang/lib/CodeGen/CGStmt.cpp +++ b/clang/lib/CodeGen/CGStmt.cpp @@ -170,6 +170,7 @@ void CodeGenFunction::EmitStmt(const Stmt *S, ArrayRef Attrs) { EmitCoreturnStmt(cast(*S)); break; case Stmt::ContractStmtClass: + assert(S && isa(S) && "Expected ContractStmt"); EmitContractStmt(cast(*S)); break; case Stmt::CapturedStmtClass: { @@ -1463,6 +1464,8 @@ void CodeGenFunction::EmitContractStmt(const ContractStmt &S) { // Emit the contract expression. const Expr *expr = S.getCond(); EmitCXXContractCheck(expr); + // TODO(EricWF): This call doesn't reuse the evaluation of the expression + // for the check. EmitCXXContractImply(expr); } diff --git a/clang/lib/CodeGen/CodeGenFunction.cpp b/clang/lib/CodeGen/CodeGenFunction.cpp index 68914c70ec0f00..8fdccab17e72ab 100644 --- a/clang/lib/CodeGen/CodeGenFunction.cpp +++ b/clang/lib/CodeGen/CodeGenFunction.cpp @@ -354,9 +354,8 @@ void CodeGenFunction::EmitCXXContractCheck(const Expr* Expr) { llvm::Value* LineNo = PLoc->getLine(); clang::StringLiteral* ExpressionText = range.print(os, Ctx.getSourceManager()); */ - const char *VLibCallName = - "_ZNSt9contracts41invoke_default_contract_violation_handlerEv"; // const - // char* + const char *VLibCallName = "_ZNSt9contracts41invoke_default_contract_" + "violation_handlerEv"; // void(void) CallArgList Args; /* Args.add(EmitLoadOfLValue(EmitStringLiteralLValue(Filename), Expr->getExprLoc()), getContext().VoidPtrTy); @@ -1529,8 +1528,11 @@ void CodeGenFunction::GenerateCode(GlobalDecl GD, llvm::Function *Fn, StartFunction(GD, ResTy, Fn, FnInfo, Args, Loc, BodyRange.getBegin()); // FIXME(EricWF): I don't think this should go here. - for (ContractStmt *S : FD->getPreContracts()) + for (ContractStmt *S : FD->getContracts()) { + if (S->getContractKind() != ContractKind::Pre) + continue; EmitContractStmt(*S); + } // Save parameters for coroutine function. if (Body && isa_and_nonnull(Body)) @@ -1609,8 +1611,11 @@ void CodeGenFunction::GenerateCode(GlobalDecl GD, llvm::Function *Fn, // FIXME(EricWF): I don't think this should go here. // Also we'll need to figure out how to reference the return value - for (ContractStmt *S : FD->getPostContracts()) + for (ContractStmt *S : FD->getContracts()) { + if (S->getContractKind() != ContractKind::Post) + continue; EmitContractStmt(*S); + } // Emit the standard function epilogue. FinishFunction(BodyRange.getEnd()); diff --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp index e65db4d6d412f8..a0be76545ee98a 100644 --- a/clang/lib/Parse/ParseDeclCXX.cpp +++ b/clang/lib/Parse/ParseDeclCXX.cpp @@ -4303,12 +4303,7 @@ void Parser::MaybeParseFunctionContractSpecifierSeq( while ((CKK = isContractSpecifier(Tok)) != ContractKeyword::None) { StmtResult Contract = ParseFunctionContractSpecifier(DeclaratorInfo); if (Contract.isUsable()) { - auto *CS = Contract.getAs(); - if (CKK == ContractKeyword::Pre) { - DeclaratorInfo.addPreContract(CS); - } else { - DeclaratorInfo.addPostContract(CS); - } + DeclaratorInfo.addContract(Contract.getAs()); } } } @@ -4349,7 +4344,8 @@ StmtResult Parser::ParseFunctionContractSpecifier(Declarator &DeclaratorInfo) { IdentifierInfo *Id = Tok.getIdentifierInfo(); SourceLocation IdLoc = ConsumeToken(); - + (void)Id; + (void)IdLoc; // FIXME(ericwf): Actually build the result name introducer } @@ -4409,48 +4405,49 @@ void Parser::ParsePostContract(Declarator &DeclaratorInfo) { // As we have to support the "auto f() post (r : r > 42) {...}" case, we cannot parse here // the return type is not guaranteed to be known until after the function body parses -/* - if (Tok.isNot(tok::identifier)) { - Diag(Tok.getLocation(), diag::err_expected) << tok::identifier; - return; - } + /* + if (Tok.isNot(tok::identifier)) { + Diag(Tok.getLocation(), diag::err_expected) << tok::identifier; + return; + } - ParsingDeclSpec DS(*this); + ParsingDeclSpec DS(*this); - ParsedTemplateInfo TemplateInfo; - DeclSpecContext DSContext = getDeclSpecContextFromDeclaratorContext(DeclaratorContext::Block); - ParseDeclarationSpecifiers(DS, TemplateInfo, AS_none, DSContext); - - ParsedAttributes LocalAttrs(AttrFactory); - ParsingDeclarator D(*this, DS, LocalAttrs, DeclaratorContext::Block); + ParsedTemplateInfo TemplateInfo; + DeclSpecContext DSContext = + getDeclSpecContextFromDeclaratorContext(DeclaratorContext::Block); + ParseDeclarationSpecifiers(DS, TemplateInfo, AS_none, DSContext); - D.setObjectType(getAsFunction().getReturnType()); - IdentifierInfo *Id = Tok.getIdentifierInfo(); - SourceLocation IdLoc = ConsumeToken(); - D.setIdentifier(Id, IdLoc); + ParsedAttributes LocalAttrs(AttrFactory); + ParsingDeclarator D(*this, DS, LocalAttrs, DeclaratorContext::Block); - Decl* ThisDecl = Actions.ActOnDeclarator(getCurScope(), D); - Actions.ActOnUninitializedDecl(ThisDecl); - Actions.FinalizeDeclaration(ThisDecl); - D.complete(ThisDecl); - if (Tok.isNot(tok::colon)) { - Diag(Tok.getLocation(), diag::err_expected) << tok::colon; - return; - } + D.setObjectType(getAsFunction().getReturnType()); + IdentifierInfo *Id = Tok.getIdentifierInfo(); + SourceLocation IdLoc = ConsumeToken(); + D.setIdentifier(Id, IdLoc); - ExprResult Expr = ParseExpression(); - if (Expr.isInvalid()) { - Diag(Tok.getLocation(), diag::err_invalid_pcs); - return; - } - DeclaratorInfo.addPostContract(Expr.get()); -*/ + Decl* ThisDecl = Actions.ActOnDeclarator(getCurScope(), D); + Actions.ActOnUninitializedDecl(ThisDecl); + Actions.FinalizeDeclaration(ThisDecl); + D.complete(ThisDecl); + if (Tok.isNot(tok::colon)) { + Diag(Tok.getLocation(), diag::err_expected) << tok::colon; + return; + } + + ExprResult Expr = ParseExpression(); + if (Expr.isInvalid()) { + Diag(Tok.getLocation(), diag::err_invalid_pcs); + return; + } + DeclaratorInfo.addContract(Expr.get()); + */ ExprResult Expr = ParseExpression(); if (Expr.isInvalid()) { Diag(Tok.getLocation(), diag::err_invalid_pcs); return; } - // DeclaratorInfo.addPostContract(Expr.get()); + // DeclaratorInfo.addContract(Expr.get()); if (Tok.isNot(tok::r_paren)) { Diag(Tok.getLocation(), diag::err_expected) << tok::r_paren; diff --git a/clang/lib/Sema/SemaContract.cpp b/clang/lib/Sema/SemaContract.cpp index b3f90cb580f957..576009e0b3a636 100644 --- a/clang/lib/Sema/SemaContract.cpp +++ b/clang/lib/Sema/SemaContract.cpp @@ -50,7 +50,12 @@ using namespace clang; using namespace sema; ExprResult Sema::ActOnContractAssertCondition(Expr *Cond) { - return PerformContextuallyConvertToBool(Cond); + assert(Cond); + ExprResult E = PerformContextuallyConvertToBool(Cond); + if (E.isInvalid()) { + return E; + } + return ActOnFinishFullExpr(E.get(), /*DiscardedValue=*/false); } StmtResult Sema::BuildContractStmt(ContractKind CK, SourceLocation KeywordLoc, @@ -63,7 +68,7 @@ StmtResult Sema::ActOnContractAssert(SourceLocation KeywordLoc, Expr *Cond) { if (CheckedCond.isInvalid()) return StmtError(); Cond = CheckedCond.get(); - + assert(Cond); return BuildContractStmt(ContractKind::Assert, KeywordLoc, Cond, nullptr); } @@ -79,12 +84,13 @@ StmtResult Sema::ActOnPreContractAssert(SourceLocation KeywordLoc, Expr *Cond) { StmtResult Sema::ActOnPostContractAssert(SourceLocation KeywordLoc, Expr *Cond, DeclStmt *ResultNameDecl) { assert(ResultNameDecl == nullptr && "Result name decl not supported yet"); + assert(Cond); ExprResult CheckedCond = ActOnContractAssertCondition(Cond); if (CheckedCond.isInvalid()) return StmtError(); Cond = CheckedCond.get(); - + assert(Cond); return BuildContractStmt(ContractKind::Post, KeywordLoc, Cond, ResultNameDecl); } diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index cc6fa9fb2b59cb..1a294c3bb30ff8 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -9352,7 +9352,7 @@ static FunctionDecl *CreateNewFunctionDecl(Sema &SemaRef, Declarator &D, SemaRef.Context, DC, D.getBeginLoc(), NameInfo, R, TInfo, SC, SemaRef.getCurFPFeatures().isFPConstrained(), isInline, HasPrototype, ConstexprSpecKind::Unspecified, - /*TrailingRequiresClause=*/nullptr, {}, {}); + /*TrailingRequiresClause=*/nullptr, /*Contracts=*/{}); if (D.isInvalidType()) NewFD->setInvalidDecl(); @@ -9361,8 +9361,7 @@ static FunctionDecl *CreateNewFunctionDecl(Sema &SemaRef, Declarator &D, ExplicitSpecifier ExplicitSpecifier = D.getDeclSpec().getExplicitSpecifier(); Expr *TrailingRequiresClause = D.getTrailingRequiresClause(); - SmallVector PreContracts = D.getPreContracts(); - SmallVector PostContracts = D.getPostContracts(); + SmallVector Contracts = D.getContracts(); SemaRef.CheckExplicitObjectMemberFunction(DC, D, Name, R); @@ -9376,7 +9375,7 @@ static FunctionDecl *CreateNewFunctionDecl(Sema &SemaRef, Declarator &D, SemaRef.Context, cast(DC), D.getBeginLoc(), NameInfo, R, TInfo, ExplicitSpecifier, SemaRef.getCurFPFeatures().isFPConstrained(), isInline, /*isImplicitlyDeclared=*/false, ConstexprKind, - InheritedConstructor(), TrailingRequiresClause, PreContracts, PostContracts); + InheritedConstructor(), TrailingRequiresClause, Contracts); } else if (Name.getNameKind() == DeclarationName::CXXDestructorName) { // This is a C++ destructor declaration. @@ -9411,7 +9410,8 @@ static FunctionDecl *CreateNewFunctionDecl(Sema &SemaRef, Declarator &D, return FunctionDecl::Create( SemaRef.Context, DC, D.getBeginLoc(), D.getIdentifierLoc(), Name, R, TInfo, SC, SemaRef.getCurFPFeatures().isFPConstrained(), isInline, - /*hasPrototype=*/true, ConstexprKind, TrailingRequiresClause, PreContracts, PostContracts); + /*hasPrototype=*/true, ConstexprKind, TrailingRequiresClause, + Contracts); } } else if (Name.getNameKind() == DeclarationName::CXXConversionFunctionName) { @@ -9430,7 +9430,7 @@ static FunctionDecl *CreateNewFunctionDecl(Sema &SemaRef, Declarator &D, SemaRef.Context, cast(DC), D.getBeginLoc(), NameInfo, R, TInfo, SemaRef.getCurFPFeatures().isFPConstrained(), isInline, ExplicitSpecifier, ConstexprKind, SourceLocation(), - TrailingRequiresClause, PreContracts, PostContracts); + TrailingRequiresClause, Contracts); } else if (Name.getNameKind() == DeclarationName::CXXDeductionGuideName) { if (TrailingRequiresClause) @@ -9459,7 +9459,7 @@ static FunctionDecl *CreateNewFunctionDecl(Sema &SemaRef, Declarator &D, CXXMethodDecl *Ret = CXXMethodDecl::Create( SemaRef.Context, cast(DC), D.getBeginLoc(), NameInfo, R, TInfo, SC, SemaRef.getCurFPFeatures().isFPConstrained(), isInline, - ConstexprKind, SourceLocation(), TrailingRequiresClause, PreContracts, PostContracts); + ConstexprKind, SourceLocation(), TrailingRequiresClause, Contracts); IsVirtualOkay = !Ret->isStatic(); return Ret; } else { @@ -9471,10 +9471,11 @@ static FunctionDecl *CreateNewFunctionDecl(Sema &SemaRef, Declarator &D, // Determine whether the function was written with a // prototype. This true when: // - we're in C++ (where every function has a prototype), - return FunctionDecl::Create( - SemaRef.Context, DC, D.getBeginLoc(), NameInfo, R, TInfo, SC, - SemaRef.getCurFPFeatures().isFPConstrained(), isInline, - true /*HasPrototype*/, ConstexprKind, TrailingRequiresClause, PreContracts, PostContracts); + return FunctionDecl::Create(SemaRef.Context, DC, D.getBeginLoc(), NameInfo, + R, TInfo, SC, + SemaRef.getCurFPFeatures().isFPConstrained(), + isInline, true /*HasPrototype*/, ConstexprKind, + TrailingRequiresClause, Contracts); } } diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp index 469060e00bf1b6..93ca79fc89d76f 100644 --- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp +++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp @@ -2165,8 +2165,7 @@ Decl *TemplateDeclInstantiator::VisitFunctionDecl( } Expr *TrailingRequiresClause = D->getTrailingRequiresClause(); - SmallVector PreContracts = D->getPreContracts(); - SmallVector PostContracts = D->getPostContracts(); + SmallVector Contracts = D->getContracts(); // If we're instantiating a local function declaration, put the result // in the enclosing namespace; otherwise we need to find the instantiated @@ -2204,7 +2203,7 @@ Decl *TemplateDeclInstantiator::VisitFunctionDecl( SemaRef.Context, DC, D->getInnerLocStart(), NameInfo, T, TInfo, D->getCanonicalDecl()->getStorageClass(), D->UsesFPIntrin(), D->isInlineSpecified(), D->hasWrittenPrototype(), D->getConstexprKind(), - TrailingRequiresClause, PreContracts, PostContracts); + TrailingRequiresClause, Contracts); Function->setFriendConstraintRefersToEnclosingTemplate( D->FriendConstraintRefersToEnclosingTemplate()); Function->setRangeEnd(D->getSourceRange().getEnd()); diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp index 197d6731072851..f089928ec2bd9f 100644 --- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp @@ -1824,7 +1824,8 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, case Stmt::OpenACCComputeConstructClass: case Stmt::OpenACCLoopConstructClass: case Stmt::OMPUnrollDirectiveClass: - case Stmt::OMPMetaDirectiveClass: { + case Stmt::OMPMetaDirectiveClass: + case Stmt::ContractStmtClass: { // FIXME(EricWF): Do something here const ExplodedNode *node = Bldr.generateSink(S, Pred, Pred->getState()); Engine.addAbortedBlock(node, currBldrCtx->getBlock()); break; From 1f0e34bf3182be00e6f72ba5cff6f6174ee0d3bb Mon Sep 17 00:00:00 2001 From: Eric Fiselier Date: Tue, 2 Jul 2024 11:56:33 -0500 Subject: [PATCH 4/9] a bunch of work, most of it broken --- clang/include/clang/AST/DeclCXX.h | 28 +++++ clang/include/clang/AST/RecursiveASTVisitor.h | 2 + clang/include/clang/Basic/Attr.td | 7 ++ clang/include/clang/Basic/DeclNodes.td | 1 + clang/include/clang/Parse/Parser.h | 4 +- clang/include/clang/Sema/DeclSpec.h | 68 ++++++----- clang/include/clang/Sema/Scope.h | 7 ++ clang/include/clang/Sema/Sema.h | 6 + clang/lib/AST/DeclBase.cpp | 4 + clang/lib/AST/DeclCXX.cpp | 11 ++ clang/lib/CodeGen/CGDecl.cpp | 4 + clang/lib/CodeGen/CGStmt.cpp | 9 -- clang/lib/CodeGen/CodeGenFunction.cpp | 11 +- clang/lib/CodeGen/CodeGenFunction.h | 2 - clang/lib/Parse/ParseDecl.cpp | 8 +- clang/lib/Parse/ParseDeclCXX.cpp | 115 +++++++++++++++--- clang/lib/Sema/SemaContract.cpp | 107 +++++++++++++--- clang/lib/Sema/SemaExpr.cpp | 6 + .../lib/Sema/SemaTemplateInstantiateDecl.cpp | 4 + clang/lib/Serialization/ASTCommon.cpp | 1 + clang/test/Parser/cxx-contracts.cpp | 17 +++ clang/test/SemaCXX/contracts.cpp | 18 ++- 22 files changed, 358 insertions(+), 82 deletions(-) create mode 100644 clang/test/Parser/cxx-contracts.cpp diff --git a/clang/include/clang/AST/DeclCXX.h b/clang/include/clang/AST/DeclCXX.h index 88fb1184b01f19..74f036c530f17c 100644 --- a/clang/include/clang/AST/DeclCXX.h +++ b/clang/include/clang/AST/DeclCXX.h @@ -4397,6 +4397,34 @@ class UnnamedGlobalConstantDecl : public ValueDecl, /// into a diagnostic with <<. const StreamingDiagnostic &operator<<(const StreamingDiagnostic &DB, AccessSpecifier AS); + + +/// A result name introduces in a post condition. For instance, given: +/// +/// int foo() post(r : r > 0); +/// +/// Where `r` refers to the value returned by the function +class ResultNameDecl : public ValueDecl { + + ResultNameDecl(DeclContext *DC, SourceLocation IdLoc, IdentifierInfo *Id, QualType T) + : ValueDecl(Decl::ResultName, DC, IdLoc, Id, T) {} + + void anchor() override; + +public: + friend class ASTDeclReader; + + static ResultNameDecl *Create(ASTContext &C, DeclContext *DC, + SourceLocation IdLoc, IdentifierInfo *Id, + QualType T); + static ResultNameDecl *CreateDeserialized(ASTContext &C, GlobalDeclID ID); + + using ValueDecl::getDeclName; + using ValueDecl::setType; + + static bool classof(const Decl *D) { return classofKind(D->getKind()); } + static bool classofKind(Kind K) { return K == Decl::ResultName; } +}; } // namespace clang diff --git a/clang/include/clang/AST/RecursiveASTVisitor.h b/clang/include/clang/AST/RecursiveASTVisitor.h index c5ce6f112482e3..c302d676c360cb 100644 --- a/clang/include/clang/AST/RecursiveASTVisitor.h +++ b/clang/include/clang/AST/RecursiveASTVisitor.h @@ -2147,6 +2147,8 @@ DEF_TRAVERSE_DECL(BindingDecl, { TRY_TO(TraverseStmt(D->getBinding())); }) +DEF_TRAVERSE_DECL(ResultNameDecl, {}) + DEF_TRAVERSE_DECL(MSPropertyDecl, { TRY_TO(TraverseDeclaratorHelper(D)); }) DEF_TRAVERSE_DECL(MSGuidDecl, {}) diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td index e801c7fafd8934..d57c6add79210b 100644 --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -3228,6 +3228,13 @@ def Unavailable : InheritableAttr { let MeaningfulToClassTemplateDefinition = 1; } +def ContractGroup : StmtAttr { + let Spellings = [CXX11<"clang", "contract_group">]; + let Args = [ StringArgument<"Group">]; + let Subjects = SubjectList<[ContractStmt], ErrorDiag, "contract_assert">; + let Documentation = [Undocumented]; +} + def DiagnoseIf : InheritableAttr { // Does not have a [[]] spelling because this attribute requires the ability // to parse function arguments but the attribute is not written in the type diff --git a/clang/include/clang/Basic/DeclNodes.td b/clang/include/clang/Basic/DeclNodes.td index 48396e85c5adac..267b34d1f62404 100644 --- a/clang/include/clang/Basic/DeclNodes.td +++ b/clang/include/clang/Basic/DeclNodes.td @@ -38,6 +38,7 @@ def Named : DeclNode; def UnresolvedUsingValue : DeclNode; def IndirectField : DeclNode; def Binding : DeclNode; + def ResultName : DeclNode; def OMPDeclareReduction : DeclNode, DeclContext; def OMPDeclareMapper : DeclNode, DeclContext; def MSGuid : DeclNode; diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h index a773c48f0fa6bf..8ecc647fb44c0b 100644 --- a/clang/include/clang/Parse/Parser.h +++ b/clang/include/clang/Parse/Parser.h @@ -2125,7 +2125,9 @@ class Parser : public CodeCompletionHandler { StmtResult ParseContractAssertStatement(); void MaybeParseFunctionContractSpecifierSeq(Declarator &DeclaratorInfo); StmtResult ParseFunctionContractSpecifier(Declarator &DeclaratorInfo); - void ParsePostContract(Declarator &DeclaratorInfo); + void MaybeLateParseFunctionContractSpecifierSeq(Declarator &DeclaratorInfo); + bool LateParseFunctionContractSpecifier(Declarator &DeclaratorInfo, CachedTokens & ContractToks); + //===--------------------------------------------------------------------===// // C99 6.7.8: Initialization. diff --git a/clang/include/clang/Sema/DeclSpec.h b/clang/include/clang/Sema/DeclSpec.h index 3ef7a09c12e91d..a8a873ec135a20 100644 --- a/clang/include/clang/Sema/DeclSpec.h +++ b/clang/include/clang/Sema/DeclSpec.h @@ -1849,33 +1849,34 @@ enum class FunctionDefinitionKind { }; enum class DeclaratorContext { - File, // File scope declaration. - Prototype, // Within a function prototype. - ObjCResult, // An ObjC method result type. - ObjCParameter, // An ObjC method parameter type. - KNRTypeList, // K&R type definition list for formals. - TypeName, // Abstract declarator for types. - FunctionalCast, // Type in a C++ functional cast expression. - Member, // Struct/Union field. - Block, // Declaration within a block in a function. - ForInit, // Declaration within first part of a for loop. - SelectionInit, // Declaration within optional init stmt of if/switch. - Condition, // Condition declaration in a C++ if/switch/while/for. - TemplateParam, // Within a template parameter list. - CXXNew, // C++ new-expression. - CXXCatch, // C++ catch exception-declaration - ObjCCatch, // Objective-C catch exception-declaration - BlockLiteral, // Block literal declarator. - LambdaExpr, // Lambda-expression declarator. - LambdaExprParameter, // Lambda-expression parameter declarator. - ConversionId, // C++ conversion-type-id. - TrailingReturn, // C++11 trailing-type-specifier. - TrailingReturnVar, // C++11 trailing-type-specifier for variable. - TemplateArg, // Any template argument (in template argument list). - TemplateTypeArg, // Template type argument (in default argument). - AliasDecl, // C++11 alias-declaration. - AliasTemplate, // C++11 alias-declaration template. - RequiresExpr, // C++2a requires-expression. + File, // File scope declaration. + Prototype, // Within a function prototype. + ObjCResult, // An ObjC method result type. + ObjCParameter, // An ObjC method parameter type. + KNRTypeList, // K&R type definition list for formals. + TypeName, // Abstract declarator for types. + FunctionalCast, // Type in a C++ functional cast expression. + Member, // Struct/Union field. + Block, // Declaration within a block in a function. + ForInit, // Declaration within first part of a for loop. + SelectionInit, // Declaration within optional init stmt of if/switch. + Condition, // Condition declaration in a C++ if/switch/while/for. + TemplateParam, // Within a template parameter list. + CXXNew, // C++ new-expression. + CXXCatch, // C++ catch exception-declaration + ObjCCatch, // Objective-C catch exception-declaration + BlockLiteral, // Block literal declarator. + LambdaExpr, // Lambda-expression declarator. + LambdaExprParameter, // Lambda-expression parameter declarator. + ConversionId, // C++ conversion-type-id. + TrailingReturn, // C++11 trailing-type-specifier. + TrailingReturnVar, // C++11 trailing-type-specifier for variable. + TemplateArg, // Any template argument (in template argument list). + TemplateTypeArg, // Template type argument (in default argument). + AliasDecl, // C++11 alias-declaration. + AliasTemplate, // C++11 alias-declaration template. + RequiresExpr, // C++2a requires-expression. + //ContractPostcondition, // C++2a requires-type. FIXME(EricWF) Association // C11 _Generic selection expression association. }; @@ -1973,6 +1974,7 @@ class Declarator { /// \brief All pre and post contracts specified by the function declaration SmallVector Contracts; + SmallVector LateParsedContracts; /// If this declarator declares a template, its template parameter lists. ArrayRef TemplateParameterLists; @@ -2648,7 +2650,17 @@ class Declarator { void addContract(ContractStmt *TRC) { Contracts.push_back(TRC); } /// \brief Get all pre contracts for this declarator - const SmallVector &getContracts() { return Contracts; } + const SmallVector &getContracts() { + return Contracts; + } + + void addLateParsedContract(CachedTokens Toks) { + LateParsedContracts.push_back(Toks); + } + + const SmallVector &getLateParsedContracts() { + return LateParsedContracts; + } /// Sets the template parameter lists that preceded the declarator. void setTemplateParameterLists(ArrayRef TPLs) { diff --git a/clang/include/clang/Sema/Scope.h b/clang/include/clang/Sema/Scope.h index 084db730342191..68281b28845510 100644 --- a/clang/include/clang/Sema/Scope.h +++ b/clang/include/clang/Sema/Scope.h @@ -162,6 +162,9 @@ class Scope { /// This is a scope of friend declaration. FriendScope = 0x40000000, + + /// The scope introduced by a post condition on a function declaration + PostConditionScope = 0x80000000, }; private: @@ -563,6 +566,10 @@ class Scope { return getFlags() & ScopeFlags::ContinueScope; } + bool isPostConditionScope() const { + return getFlags() & ScopeFlags::PostConditionScope; + } + /// Determine whether this scope is a C++ 'try' block. bool isTryScope() const { return getFlags() & Scope::TryScope; } diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 6692d91457e800..937f6bb1009d7f 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -2393,6 +2393,12 @@ class Sema final : public SemaBase { StmtResult ActOnPreContractAssert(SourceLocation KeywordLoc, Expr *Cond); StmtResult ActOnPostContractAssert(SourceLocation KeywordLoc, Expr *Cond, DeclStmt *ResultNameDecl = nullptr); + StmtResult BuildResultNameDecl(DeclContext* DC, SourceLocation Loc, IdentifierInfo *Id, QualType T); + void ActOnStartContracts(Scope *S, Declarator& D); + StmtResult ActOnResultNameDeclarator(Scope *S, Declarator &FuncDecl, + SourceLocation IDLoc, + IdentifierInfo *II); + ExprResult ActOnContractAssertCondition(Expr *Cond); diff --git a/clang/lib/AST/DeclBase.cpp b/clang/lib/AST/DeclBase.cpp index 98c55d6f4eba4b..d8e1ca51ded125 100644 --- a/clang/lib/AST/DeclBase.cpp +++ b/clang/lib/AST/DeclBase.cpp @@ -890,6 +890,10 @@ unsigned Decl::getIdentifierNamespaceForKind(Kind DeclKind) { // tag types, so we include them in the tag namespace. return IDNS_Ordinary | IDNS_Tag; + // FIXME(EricWF): IDK if this is correct + case ResultName: + return IDNS_Ordinary | IDNS_Tag; + case ObjCCompatibleAlias: case ObjCInterface: return IDNS_Ordinary | IDNS_Type; diff --git a/clang/lib/AST/DeclCXX.cpp b/clang/lib/AST/DeclCXX.cpp index e74951c8e6e4dc..80cf4d03117931 100644 --- a/clang/lib/AST/DeclCXX.cpp +++ b/clang/lib/AST/DeclCXX.cpp @@ -3569,3 +3569,14 @@ const StreamingDiagnostic &clang::operator<<(const StreamingDiagnostic &DB, AccessSpecifier AS) { return DB << getAccessName(AS); } + +ResultNameDecl *ResultNameDecl::Create(ASTContext &C, DeclContext *DC, + SourceLocation IdLoc, IdentifierInfo *Id, QualType T) { + return new (C, DC) ResultNameDecl(DC, IdLoc, Id, T); +} + +ResultNameDecl *ResultNameDecl::CreateDeserialized(ASTContext &C, GlobalDeclID ID) { + return new (C, ID) ResultNameDecl(nullptr, SourceLocation(), nullptr, QualType()); +} + +void ResultNameDecl::anchor() {} diff --git a/clang/lib/CodeGen/CGDecl.cpp b/clang/lib/CodeGen/CGDecl.cpp index 4a213990d1e36e..68131b42b3eb1c 100644 --- a/clang/lib/CodeGen/CGDecl.cpp +++ b/clang/lib/CodeGen/CGDecl.cpp @@ -136,6 +136,10 @@ void CodeGenFunction::EmitDecl(const Decl &D) { // None of these decls require codegen support. return; + case Decl::ResultName: // FIXME(EricWF): This should be removed. + llvm_unreachable("result name in function"); + return; + case Decl::NamespaceAlias: if (CGDebugInfo *DI = getDebugInfo()) DI->EmitNamespaceAlias(cast(D)); diff --git a/clang/lib/CodeGen/CGStmt.cpp b/clang/lib/CodeGen/CGStmt.cpp index 419c21e0e475d9..70727b2e0ceec1 100644 --- a/clang/lib/CodeGen/CGStmt.cpp +++ b/clang/lib/CodeGen/CGStmt.cpp @@ -170,7 +170,6 @@ void CodeGenFunction::EmitStmt(const Stmt *S, ArrayRef Attrs) { EmitCoreturnStmt(cast(*S)); break; case Stmt::ContractStmtClass: - assert(S && isa(S) && "Expected ContractStmt"); EmitContractStmt(cast(*S)); break; case Stmt::CapturedStmtClass: { @@ -1460,14 +1459,6 @@ static bool isSwiftAsyncCallee(const CallExpr *CE) { return calleeType->getCallConv() == CallingConv::CC_SwiftAsync; } -void CodeGenFunction::EmitContractStmt(const ContractStmt &S) { - // Emit the contract expression. - const Expr *expr = S.getCond(); - EmitCXXContractCheck(expr); - // TODO(EricWF): This call doesn't reuse the evaluation of the expression - // for the check. - EmitCXXContractImply(expr); -} /// EmitReturnStmt - Note that due to GCC extensions, this can have an operand /// if the function returns void, or may be missing one if the function returns diff --git a/clang/lib/CodeGen/CodeGenFunction.cpp b/clang/lib/CodeGen/CodeGenFunction.cpp index 8fdccab17e72ab..62e6ddbf869110 100644 --- a/clang/lib/CodeGen/CodeGenFunction.cpp +++ b/clang/lib/CodeGen/CodeGenFunction.cpp @@ -336,7 +336,11 @@ llvm::DebugLoc CodeGenFunction::EmitReturnBlock() { return llvm::DebugLoc(); } -void CodeGenFunction::EmitCXXContractCheck(const Expr* Expr) { + +void CodeGenFunction::EmitContractStmt(const ContractStmt &S) { + // Emit the contract expression. + const Expr *Expr = S.getCond(); + llvm::Value *ArgValue = EmitScalarExpr(Expr); llvm::BasicBlock *Begin = Builder.GetInsertBlock(); llvm::BasicBlock *End = createBasicBlock("contract_assert_end", this->CurFn); @@ -374,14 +378,13 @@ void CodeGenFunction::EmitCXXContractCheck(const Expr* Expr) { Builder.ClearInsertionPoint(); Builder.SetInsertPoint(End); -} -void CodeGenFunction::EmitCXXContractImply(const Expr* expr) { - llvm::Value *ArgValue = EmitScalarExpr(expr); + // FIXME(EricWF): Maybe don't create the assume if the contract check is ignored. llvm::Function *FnAssume = CGM.getIntrinsic(llvm::Intrinsic::assume); Builder.CreateCall(FnAssume, ArgValue); } + static void EmitIfUsed(CodeGenFunction &CGF, llvm::BasicBlock *BB) { if (!BB) return; if (!BB->use_empty()) { diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h index 1bf24052c7c8bf..02b2397205b713 100644 --- a/clang/lib/CodeGen/CodeGenFunction.h +++ b/clang/lib/CodeGen/CodeGenFunction.h @@ -4349,8 +4349,6 @@ class CodeGenFunction : public CodeGenTypeCache { void EmitDeclRefExprDbgValue(const DeclRefExpr *E, const APValue &Init); void EmitContractStmt(const ContractStmt &S); - void EmitCXXContractCheck(const Expr* expr); - void EmitCXXContractImply(const Expr* expr); //===--------------------------------------------------------------------===// // Scalar Expression Emission diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp index 243d81084f9938..ec32bfe066e520 100644 --- a/clang/lib/Parse/ParseDecl.cpp +++ b/clang/lib/Parse/ParseDecl.cpp @@ -2542,7 +2542,7 @@ Parser::DeclGroupPtrTy Parser::ParseDeclGroup(ParsingDeclSpec &DS, // FIXME(EricWF): Is this the correct place? MaybeParseFunctionContractSpecifierSeq(D); - + assert(false); if (Tok.is(tok::kw_requires)) ParseTrailingRequiresClause(D); Decl *ThisDecl = ParseDeclarationAfterDeclarator(D, TemplateInfo); @@ -2565,6 +2565,7 @@ Parser::DeclGroupPtrTy Parser::ParseDeclGroup(ParsingDeclSpec &DS, if (!isDeclarationSpecifier(ImplicitTypenameContext::No)) SkipMalformedDecl(); } + assert(D.getContracts().empty()); return Actions.FinalizeDeclaratorGroup(getCurScope(), DS, DeclsInGroup); } @@ -7163,8 +7164,10 @@ void Parser::ParseDirectDeclarator(Declarator &D) { Actions.ActOnStartFunctionDeclarationDeclarator(D, TemplateParameterDepth); ParseFunctionDeclarator(D, attrs, T, IsAmbiguous); - if (IsFunctionDeclaration) + if (IsFunctionDeclaration) { + assert(D.getContracts().empty()); Actions.ActOnFinishFunctionDeclarationDeclarator(D); + } PrototypeScope.Exit(); } else if (Tok.is(tok::l_square)) { ParseBracketDeclarator(D); @@ -7652,6 +7655,7 @@ void Parser::ParseFunctionDeclarator(Declarator &D, LocalEndLoc, D, TrailingReturnType, TrailingReturnTypeLoc, &DS), std::move(FnAttrs), EndLoc); + assert(D.getContracts().empty()); } /// ParseRefQualifier - Parses a member function ref-qualifier. Returns diff --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp index a0be76545ee98a..a18b3cbc46d85c 100644 --- a/clang/lib/Parse/ParseDeclCXX.cpp +++ b/clang/lib/Parse/ParseDeclCXX.cpp @@ -4278,8 +4278,7 @@ ExceptionSpecificationType Parser::ParseDynamicExceptionSpecification( return Exceptions.empty() ? EST_DynamicNone : EST_Dynamic; } /// ParseFunctionContractSpecifierSeq - Parse a series of pre/post contracts on -/// a -/// function declaration. +/// a function declaration. /// /// function-contract-specifier-seq : /// function-contract-specifier function-contract-specifier-seq @@ -4308,6 +4307,57 @@ void Parser::MaybeParseFunctionContractSpecifierSeq( } } +void Parser::MaybeLateParseFunctionContractSpecifierSeq( + Declarator &DeclaratorInfo) { + ContractKeyword CKK; + while ((CKK = isContractSpecifier(Tok)) != ContractKeyword::None) { + CachedTokens Toks; + if (!LateParseFunctionContractSpecifier(DeclaratorInfo, Toks)) { + return; + } + + DeclaratorInfo.addLateParsedContract(Toks); + } +} + +bool Parser::LateParseFunctionContractSpecifier(Declarator &DeclaratorInfo, CachedTokens& Toks) { + auto [CK, CKStr] = [&]() -> std::pair { + switch (isContractSpecifier(Tok)) { + case ContractKeyword::Pre: + return std::make_pair(ContractKind::Pre, "pre"); + case ContractKeyword::Post: + return std::make_pair(ContractKind::Post, "post"); + default: + llvm_unreachable("unhandled case"); + } + }(); + + // Consume and cache the starting token. + Token StartTok = Tok; + SourceRange ContractRange = SourceRange(ConsumeToken()); + + // Check for a '('. + if (!Tok.is(tok::l_paren)) { + // If this is a bare 'noexcept', we're done. + + Diag(Tok, diag::err_expected_lparen_after) << CKStr; + return false; + } + + // Cache the tokens for the exception-specification. + + Toks.push_back(StartTok); // 'throw' or 'noexcept' + Toks.push_back(Tok); // '(' + ContractRange.setEnd(ConsumeParen()); // '(' + + ConsumeAndStoreUntil(tok::r_paren, Toks, + /*StopAtSemi=*/true, + /*ConsumeFinalToken=*/true); + ContractRange.setEnd(Toks.back().getLocation()); + return true; + +} + StmtResult Parser::ParseFunctionContractSpecifier(Declarator &DeclaratorInfo) { auto [CK, CKStr] = [&]() -> std::pair { switch (isContractSpecifier(Tok)) { @@ -4336,6 +4386,22 @@ StmtResult Parser::ParseFunctionContractSpecifier(Declarator &DeclaratorInfo) { return StmtError(); } + ParseScope ParamScope(this, Scope::DeclScope | + Scope::FunctionDeclarationScope | + Scope::FunctionPrototypeScope | + Scope::PostConditionScope); + + std::optional ThisScope; + InitCXXThisScopeForDeclaratorIfRelevant(DeclaratorInfo, DeclaratorInfo.getDeclSpec(), ThisScope); + + DeclaratorChunk::FunctionTypeInfo FTI = DeclaratorInfo.getFunctionTypeInfo(); + + for (unsigned i = 0; i != FTI.NumParams; ++i) { + ParmVarDecl *Param = cast(FTI.Params[i].Param); + Actions.ActOnReenterCXXMethodParameter(getCurScope(), Param); + } + + DeclStmt *ResultNameStmt = nullptr; if (Tok.is(tok::identifier) && NextToken().is(tok::colon)) { if (CK != ContractKind::Post) { // Only post contracts can have a result name @@ -4344,23 +4410,35 @@ StmtResult Parser::ParseFunctionContractSpecifier(Declarator &DeclaratorInfo) { IdentifierInfo *Id = Tok.getIdentifierInfo(); SourceLocation IdLoc = ConsumeToken(); - (void)Id; - (void)IdLoc; - // FIXME(ericwf): Actually build the result name introducer - } - SourceLocation Start = Tok.getLocation(); + //ImplicitParamDecl *D = ImplicitParamDecl::Create(Actions.getASTContext(), nullptr, IdLoc, Id, QualType(), + //Id, /* Type here*/nullptr, ImplicitParamDecl::Other); + // NamedDecl *ND = NameDecl::Create(Actions.getASTContext(), Id, IdLoc); + SourceLocation ColonLoc = ConsumeToken(); + ((void)ColonLoc); - ParseScope ParamScope(this, Scope::DeclScope | - Scope::FunctionDeclarationScope | - Scope::FunctionPrototypeScope); + auto& DI = DeclaratorInfo; + auto &DS = DI.getDeclSpec(); + DeclSpec RetNameDS(AttrFactory); + ParsedAttributes DeclAttrs(AttrFactory); - DeclaratorChunk::FunctionTypeInfo FTI = DeclaratorInfo.getFunctionTypeInfo(); - for (unsigned i = 0; i != FTI.NumParams; ++i) { - ParmVarDecl *Param = cast(FTI.Params[i].Param); - Actions.ActOnReenterCXXMethodParameter(getCurScope(), Param); + assert(DI.isFunctionDeclarator()); + if (auto DeclRep = DS.getRepAsDecl(); DeclRep != nullptr) { + DeclRep->dumpColor(); + assert(false); + } + ParsedType ParsedResultType = DS.getRepAsType(); + ParsedResultType.get().dump(); + + + StmtResult RNStmt = Actions.ActOnResultNameDeclarator(getCurScope(), DeclaratorInfo, IdLoc, Id); + if (RNStmt.isUsable()) + ResultNameStmt = cast(RNStmt.get()); } + SourceLocation Start = Tok.getLocation(); + + ExprResult Cond = ParseConditionalExpression(); if (Cond.isUsable()) { Cond = Actions.CorrectDelayedTyposInExpr(Cond, /*InitDecl=*/nullptr, @@ -4378,10 +4456,12 @@ StmtResult Parser::ParseFunctionContractSpecifier(Declarator &DeclaratorInfo) { if (CK == ContractKind::Pre) { return Actions.ActOnPreContractAssert(KeywordLoc, Cond.get()); } else { - return Actions.ActOnPostContractAssert(KeywordLoc, Cond.get()); + return Actions.ActOnPostContractAssert(KeywordLoc, Cond.get(), + ResultNameStmt); } } +/* void Parser::ParsePostContract(Declarator &DeclaratorInfo) { ConsumeToken(); @@ -4405,7 +4485,7 @@ void Parser::ParsePostContract(Declarator &DeclaratorInfo) { // As we have to support the "auto f() post (r : r > 42) {...}" case, we cannot parse here // the return type is not guaranteed to be known until after the function body parses - /* + if (Tok.isNot(tok::identifier)) { Diag(Tok.getLocation(), diag::err_expected) << tok::identifier; return; @@ -4441,7 +4521,7 @@ void Parser::ParsePostContract(Declarator &DeclaratorInfo) { return; } DeclaratorInfo.addContract(Expr.get()); - */ + ExprResult Expr = ParseExpression(); if (Expr.isInvalid()) { Diag(Tok.getLocation(), diag::err_invalid_pcs); @@ -4455,6 +4535,7 @@ void Parser::ParsePostContract(Declarator &DeclaratorInfo) { } ConsumeParen(); } +*/ /// ParseTrailingReturnType - Parse a trailing return type on a new-style /// function declaration. diff --git a/clang/lib/Sema/SemaContract.cpp b/clang/lib/Sema/SemaContract.cpp index 576009e0b3a636..d93cb0111bfc30 100644 --- a/clang/lib/Sema/SemaContract.cpp +++ b/clang/lib/Sema/SemaContract.cpp @@ -49,8 +49,10 @@ using namespace clang; using namespace sema; -ExprResult Sema::ActOnContractAssertCondition(Expr *Cond) { - assert(Cond); +ExprResult Sema::ActOnContractAssertCondition(Expr *Cond) { + if (Cond->isTypeDependent()) + return Cond; + ExprResult E = PerformContextuallyConvertToBool(Cond); if (E.isInvalid()) { return E; @@ -58,25 +60,21 @@ ExprResult Sema::ActOnContractAssertCondition(Expr *Cond) { return ActOnFinishFullExpr(E.get(), /*DiscardedValue=*/false); } + StmtResult Sema::BuildContractStmt(ContractKind CK, SourceLocation KeywordLoc, Expr *Cond, DeclStmt *ResultNameDecl) { - return ContractStmt::Create(Context, CK, KeywordLoc, Cond, ResultNameDecl); + ExprResult E = ActOnContractAssertCondition(Cond); + if (E.isInvalid()) + return StmtError(); + + return ContractStmt::Create(Context, CK, KeywordLoc, E.get(), ResultNameDecl); } StmtResult Sema::ActOnContractAssert(SourceLocation KeywordLoc, Expr *Cond) { - ExprResult CheckedCond = ActOnContractAssertCondition(Cond); - if (CheckedCond.isInvalid()) - return StmtError(); - Cond = CheckedCond.get(); - assert(Cond); return BuildContractStmt(ContractKind::Assert, KeywordLoc, Cond, nullptr); } StmtResult Sema::ActOnPreContractAssert(SourceLocation KeywordLoc, Expr *Cond) { - ExprResult CheckedCond = ActOnContractAssertCondition(Cond); - if (CheckedCond.isInvalid()) - return StmtError(); - Cond = CheckedCond.get(); return BuildContractStmt(ContractKind::Pre, KeywordLoc, Cond, nullptr); } @@ -86,11 +84,86 @@ StmtResult Sema::ActOnPostContractAssert(SourceLocation KeywordLoc, Expr *Cond, assert(ResultNameDecl == nullptr && "Result name decl not supported yet"); assert(Cond); - ExprResult CheckedCond = ActOnContractAssertCondition(Cond); - if (CheckedCond.isInvalid()) - return StmtError(); - Cond = CheckedCond.get(); - assert(Cond); return BuildContractStmt(ContractKind::Post, KeywordLoc, Cond, ResultNameDecl); } + +void Sema::ActOnStartContracts(Scope *S, Declarator &D) { + if (!D.isFunctionDeclarator()) + return; + auto &FTI = D.getFunctionTypeInfo(); + if (!FTI.Params) + return; + for (auto &Param : ArrayRef(FTI.Params, + FTI.NumParams)) { + auto *ParamDecl = cast(Param.Param); + if (ParamDecl->getDeclName()) + PushOnScopeChains(ParamDecl, S, /*AddToContext=*/false); + } +} + + +/// ActOnParamDeclarator - Called from Parser::ParseFunctionDeclarator() +/// to introduce parameters into function prototype scope. +StmtResult Sema::ActOnResultNameDeclarator(Scope *S, Declarator &FuncDecl, + SourceLocation IDLoc, + IdentifierInfo *II) { + const DeclSpec &DS = FuncDecl.getDeclSpec(); + + + DiagnoseFunctionSpecifiers(DS); + + //CheckFunctionOrTemplateParamDeclarator(S, D); + + TypeSourceInfo *TInfo = GetTypeForDeclarator(FuncDecl); + QualType parmDeclType = TInfo->getType(); + + // Check for redeclaration of parameters, e.g. int foo(int x, int x); + if (II) { + LookupResult R(*this, II, IDLoc, LookupOrdinaryName, + RedeclarationKind::ForVisibleRedeclaration); // FIXME(EricWF) + LookupName(R, S); + if (!R.empty()) { + NamedDecl *PrevDecl = *R.begin(); + if (R.isSingleResult() && PrevDecl->isTemplateParameter()) { + // Maybe we will complain about the shadowed template parameter. + //DiagnoseTemplateParameterShadow(D.getIdentifierLoc(), PrevDecl); + // Just pretend that we didn't see the previous declaration. + PrevDecl = nullptr; + } + // FIXME(EricWF): Diagnose lookup conflicts with lambda captures and parameter declarations. + if (auto* PVD = dyn_cast(PrevDecl)) { + Diag(IDLoc, diag::err_param_redefinition) << II; // FIXME(EricWF): Change the diagnostic here. + Diag(PVD->getLocation(), diag::note_previous_declaration); + } else if (auto *CD = dyn_cast(PrevDecl)) { + Diag(IDLoc, diag::err_redefinition_different_kind) << II; + Diag(PrevDecl->getLocation(), diag::note_previous_declaration); + } + } + } + + // Temporarily put parameter variables in the translation unit, not + // the enclosing context. This prevents them from accidentally + // looking like class members in C++. + CurContext->dumpDeclContext(); + ResultNameDecl *New = + ResultNameDecl::Create(Context, CurContext, + IDLoc, II, parmDeclType); + + if (FuncDecl.isInvalidType()) + New->setInvalidDecl(); + + //CheckExplicitObjectParameter(*this, New, ExplicitThisLoc); + assert(S->isPostConditionScope()); + assert(S->isFunctionPrototypeScope()); + assert(S->getFunctionPrototypeDepth() >= 1); + //New->setScopeInfo(S->getFunctionPrototypeDepth() - 1, + // S->getNextFunctionPrototypeIndex()); + + // Add the parameter declaration into this scope. + S->AddDecl(New); + if (II) + IdResolver.AddDecl(New); + + return ActOnDeclStmt(ConvertDeclToDeclGroup(New), IDLoc, IDLoc); +} diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index e48c5954e2a054..e954460aa104e4 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -3406,6 +3406,7 @@ ExprResult Sema::BuildDeclarationNameExpr( } [[fallthrough]]; + case Decl::ImplicitParam: case Decl::ParmVar: { // These are always l-values. @@ -3424,6 +3425,11 @@ ExprResult Sema::BuildDeclarationNameExpr( break; } + case Decl::ResultName: // FIXME(EricWF): Is this even close to correct? + valueKind = VK_LValue; + type = type.getNonReferenceType(); + break; + case Decl::Binding: // These are always lvalues. valueKind = VK_LValue; diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp index 93ca79fc89d76f..3bc07425dc80b3 100644 --- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp +++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp @@ -2901,6 +2901,10 @@ Decl *TemplateDeclInstantiator::VisitCXXConversionDecl(CXXConversionDecl *D) { return VisitCXXMethodDecl(D); } +Decl *TemplateDeclInstantiator::VisitResultNameDecl(ResultNameDecl *D) { + llvm_unreachable("Shouldnt be instantiated"); +} + Decl *TemplateDeclInstantiator::VisitParmVarDecl(ParmVarDecl *D) { return SemaRef.SubstParmVarDecl(D, TemplateArgs, /*indexAdjustment*/ 0, std::nullopt, diff --git a/clang/lib/Serialization/ASTCommon.cpp b/clang/lib/Serialization/ASTCommon.cpp index 3385cb8aad7e49..ed8be3162db60a 100644 --- a/clang/lib/Serialization/ASTCommon.cpp +++ b/clang/lib/Serialization/ASTCommon.cpp @@ -448,6 +448,7 @@ bool serialization::isRedeclarableDeclKind(unsigned Kind) { case Decl::RequiresExprBody: case Decl::UnresolvedUsingIfExists: case Decl::HLSLBuffer: + case Decl::ResultName: return false; // These indirectly derive from Redeclarable but are not actually diff --git a/clang/test/Parser/cxx-contracts.cpp b/clang/test/Parser/cxx-contracts.cpp new file mode 100644 index 00000000000000..710c9bbbe6aec5 --- /dev/null +++ b/clang/test/Parser/cxx-contracts.cpp @@ -0,0 +1,17 @@ +// RUN: %clang_cc1 -std=c++26 -fcontracts %s -verify +// expected-no-diagnostics + +int f(int x) pre(x != 0) + post(r : x > 0) { + return x; +} + +struct Foo { + int x; + Foo(int x) pre(x != 0) : x(x) {} + int get() post(this->x > 0) { + return x; + } +}; + +auto lam = [](int x) pre(x != 0) post(r : x > 0) { return x; }; diff --git a/clang/test/SemaCXX/contracts.cpp b/clang/test/SemaCXX/contracts.cpp index 6c962e9d41509f..988a498bf3a68e 100644 --- a/clang/test/SemaCXX/contracts.cpp +++ b/clang/test/SemaCXX/contracts.cpp @@ -8,6 +8,10 @@ void test_pre_parse(int x) pre(x != 0) { void test_post_parse(int x) post(x != 0) { } +int test_return_parse(int x) post(r : r == x) { + return x; +} + struct ImpBC { operator bool() const; }; @@ -20,12 +24,22 @@ struct ImpBC2 { operator int() const; }; +struct NoBool { +}; + void test_attributes(int x) { - [[foo::dummy]] contract_assert - [[clang::annotate("bar")]] (x != 0); // FIXME(EricWF): diagnose this + contract_assert [[clang::contract_group("bar")]] (x != 0); +} +template +void foo() pre(T{}) { + contract_assert(T{}); // expected-error {{'NoBool' is not contextually convertible to 'bool'}} } +template void foo(); +template void foo(); +template void foo(); // expected-note {{requested here}} + void test_converted_to_bool(int x) pre((void)true) // expected-error {{value of type 'void' is not contextually convertible to 'bool'}} post((void)true) // expected-error {{value of type 'void' is not contextually convertible to 'bool'}} From d63f11e095c7a9af48dd776e0937ce112dfb19b3 Mon Sep 17 00:00:00 2001 From: Eric Fiselier Date: Tue, 2 Jul 2024 20:25:18 -0500 Subject: [PATCH 5/9] Get templates working. The template stuff is a bit of a hack. I'm considering simply inserting the statements into the function body and letting the existing code handle the rest. Also add some cheaky diagnostics, fix a few name lookup bugs, and delete some old code. --- clang/include/clang/AST/StmtCXX.h | 5 +- .../clang/Basic/DiagnosticSemaKinds.td | 22 ++++ clang/include/clang/Sema/Sema.h | 1 + clang/lib/AST/DeclBase.cpp | 1 + clang/lib/AST/StmtCXX.cpp | 8 +- clang/lib/Parse/ParseDecl.cpp | 2 +- clang/lib/Parse/ParseDeclCXX.cpp | 101 +----------------- clang/lib/Sema/SemaContract.cpp | 43 +++++--- clang/lib/Sema/SemaDecl.cpp | 2 + .../lib/Sema/SemaTemplateInstantiateDecl.cpp | 14 +++ clang/lib/Serialization/ASTWriterStmt.cpp | 2 +- clang/test/SemaCXX/contracts-min.cpp | 5 + clang/test/SemaCXX/contracts-template.cpp | 43 ++++++++ clang/test/SemaCXX/contracts.cpp | 29 ++++- clang/tools/libclang/CIndex.cpp | 1 + 15 files changed, 158 insertions(+), 121 deletions(-) create mode 100644 clang/test/SemaCXX/contracts-min.cpp create mode 100644 clang/test/SemaCXX/contracts-template.cpp diff --git a/clang/include/clang/AST/StmtCXX.h b/clang/include/clang/AST/StmtCXX.h index 3e0f92dac9acd4..5d675f193e2710 100644 --- a/clang/include/clang/AST/StmtCXX.h +++ b/clang/include/clang/AST/StmtCXX.h @@ -22,6 +22,7 @@ namespace clang { class VarDecl; +class ResultNameDecl; /// CXXCatchStmt - This represents a C++ catch block. /// @@ -574,13 +575,15 @@ class ContractStmt final : public Stmt, bool hasResultNameDecl() const { return ContractAssertBits.HasResultName; } - DeclStmt *getResultNameDecl() const { + DeclStmt *getResultNameDeclStmt() const { return hasResultNameDecl() ? static_cast( getTrailingObjects()[ResultNameDeclOffset]) : nullptr; } + ResultNameDecl *getResultNameDecl() const; + void setResultNameDecl(DeclStmt *D) { assert(hasResultNameDecl() && "no result name decl"); getTrailingObjects()[ResultNameDeclOffset] = D; diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 14736784cff5fa..1f7e50b1c0bc3c 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -11823,6 +11823,28 @@ def err_coroutine_return_type : Error< >; } // end of coroutines issue category +let CategoryName = "Contract Issue" in { +def err_ericwf_unimplemented : Error< + "contract feature %0 is not yet implemented (complain to eric@efcs.ca)" +>; +def warn_ericwf_unimplemented : Warning< + "contract feature %0 is not yet _correctly_ implemented (Danm it Eric!)" +>; + +def err_ericwf_fixme : Error< + "woops... FIXME(EricWF): %0" +>; +def warn_ericwf_fixme : Warning< + "woops... FIXME(EricWF): %0" +>; +def err_result_name_shadows_param : Error< + "declaration of result name %0 shadows parameter">; +def err_void_result_name : Error< + "result name %0 cannot be used with a void return type">; +def err_deduced_auto_result_name_without_body : Error< + "result name on function with deduced return type requires a body">; + +} // end of contracts issues let CategoryName = "Documentation Issue" in { def warn_not_a_doxygen_trailing_member_comment : Warning< "not a Doxygen trailing comment">, InGroup, DefaultIgnore; diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 937f6bb1009d7f..3d2228077efcbd 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -2390,6 +2390,7 @@ class Sema final : public SemaBase { public: StmtResult ActOnContractAssert(SourceLocation KeywordLoc, Expr *Cond); + StmtResult ActOnPreContractAssert(SourceLocation KeywordLoc, Expr *Cond); StmtResult ActOnPostContractAssert(SourceLocation KeywordLoc, Expr *Cond, DeclStmt *ResultNameDecl = nullptr); diff --git a/clang/lib/AST/DeclBase.cpp b/clang/lib/AST/DeclBase.cpp index d8e1ca51ded125..ec5b05979febe2 100644 --- a/clang/lib/AST/DeclBase.cpp +++ b/clang/lib/AST/DeclBase.cpp @@ -1103,6 +1103,7 @@ bool Decl::AccessDeclContextCheck() const { // FIXME: a ParmVarDecl can have ClassTemplateSpecialization // as DeclContext (?). isa(this) || + isa(this) || // FIXME: a ClassTemplateSpecialization or CXXRecordDecl can have // AS_none as access specifier. isa(this) || isa(this)) diff --git a/clang/lib/AST/StmtCXX.cpp b/clang/lib/AST/StmtCXX.cpp index c4197b3b1daa53..28795190e5cc68 100644 --- a/clang/lib/AST/StmtCXX.cpp +++ b/clang/lib/AST/StmtCXX.cpp @@ -11,7 +11,7 @@ //===----------------------------------------------------------------------===// #include "clang/AST/StmtCXX.h" - +#include "clang/AST/DeclCXX.h" #include "clang/AST/ASTContext.h" using namespace clang; @@ -146,3 +146,9 @@ ContractStmt *ContractStmt::Create(const ASTContext &C, ContractKind Kind, S->setResultNameDecl(ResultNameDecl); return S; } + +ResultNameDecl *ContractStmt::getResultNameDecl() const { + DeclStmt* D = getResultNameDeclStmt(); + assert(D); + return cast(D->getSingleDecl()); +} diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp index ec32bfe066e520..462e6c980a628f 100644 --- a/clang/lib/Parse/ParseDecl.cpp +++ b/clang/lib/Parse/ParseDecl.cpp @@ -2565,7 +2565,6 @@ Parser::DeclGroupPtrTy Parser::ParseDeclGroup(ParsingDeclSpec &DS, if (!isDeclarationSpecifier(ImplicitTypenameContext::No)) SkipMalformedDecl(); } - assert(D.getContracts().empty()); return Actions.FinalizeDeclaratorGroup(getCurScope(), DS, DeclsInGroup); } @@ -7164,6 +7163,7 @@ void Parser::ParseDirectDeclarator(Declarator &D) { Actions.ActOnStartFunctionDeclarationDeclarator(D, TemplateParameterDepth); ParseFunctionDeclarator(D, attrs, T, IsAmbiguous); + assert(D.getContracts().empty()); if (IsFunctionDeclaration) { assert(D.getContracts().empty()); Actions.ActOnFinishFunctionDeclarationDeclarator(D); diff --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp index a18b3cbc46d85c..a5ce2ec1b288b0 100644 --- a/clang/lib/Parse/ParseDeclCXX.cpp +++ b/clang/lib/Parse/ParseDeclCXX.cpp @@ -4357,6 +4357,7 @@ bool Parser::LateParseFunctionContractSpecifier(Declarator &DeclaratorInfo, Cach return true; } +#pragma clang diagnostic ignored "-Wunused-variable" StmtResult Parser::ParseFunctionContractSpecifier(Declarator &DeclaratorInfo) { auto [CK, CKStr] = [&]() -> std::pair { @@ -4387,7 +4388,7 @@ StmtResult Parser::ParseFunctionContractSpecifier(Declarator &DeclaratorInfo) { } ParseScope ParamScope(this, Scope::DeclScope | - Scope::FunctionDeclarationScope | + // Scope::FunctionDeclarationScope | Scope::FunctionPrototypeScope | Scope::PostConditionScope); @@ -4411,35 +4412,19 @@ StmtResult Parser::ParseFunctionContractSpecifier(Declarator &DeclaratorInfo) { IdentifierInfo *Id = Tok.getIdentifierInfo(); SourceLocation IdLoc = ConsumeToken(); - //ImplicitParamDecl *D = ImplicitParamDecl::Create(Actions.getASTContext(), nullptr, IdLoc, Id, QualType(), - //Id, /* Type here*/nullptr, ImplicitParamDecl::Other); - // NamedDecl *ND = NameDecl::Create(Actions.getASTContext(), Id, IdLoc); SourceLocation ColonLoc = ConsumeToken(); ((void)ColonLoc); - auto& DI = DeclaratorInfo; - auto &DS = DI.getDeclSpec(); - DeclSpec RetNameDS(AttrFactory); - ParsedAttributes DeclAttrs(AttrFactory); - - assert(DI.isFunctionDeclarator()); - if (auto DeclRep = DS.getRepAsDecl(); DeclRep != nullptr) { - DeclRep->dumpColor(); - assert(false); - } - ParsedType ParsedResultType = DS.getRepAsType(); - ParsedResultType.get().dump(); - - StmtResult RNStmt = Actions.ActOnResultNameDeclarator(getCurScope(), DeclaratorInfo, IdLoc, Id); if (RNStmt.isUsable()) ResultNameStmt = cast(RNStmt.get()); + else + return StmtError(); } SourceLocation Start = Tok.getLocation(); - - ExprResult Cond = ParseConditionalExpression(); + if (Cond.isUsable()) { Cond = Actions.CorrectDelayedTyposInExpr(Cond, /*InitDecl=*/nullptr, /*RecoverUncorrectedTypos=*/true); @@ -4461,82 +4446,6 @@ StmtResult Parser::ParseFunctionContractSpecifier(Declarator &DeclaratorInfo) { } } -/* -void Parser::ParsePostContract(Declarator &DeclaratorInfo) { - ConsumeToken(); - - ParseScope ParamScope(this, Scope::DeclScope | - Scope::FunctionDeclarationScope | - Scope::FunctionPrototypeScope); - - DeclaratorChunk::FunctionTypeInfo FTI = DeclaratorInfo.getFunctionTypeInfo(); - for (unsigned i = 0; i != FTI.NumParams; ++i) { - ParmVarDecl *Param = cast(FTI.Params[i].Param); - Actions.ActOnReenterCXXMethodParameter(getCurScope(), Param); - } - - if (Tok.isNot(tok::l_paren)) { - Diag(Tok.getLocation(), diag::err_expected) << tok::l_paren; - return; - } - ConsumeParen(); - - // Post contracts start with colon - // As we have to support the "auto f() post (r : r > 42) {...}" case, we cannot parse here - // the return type is not guaranteed to be known until after the function body parses - - - if (Tok.isNot(tok::identifier)) { - Diag(Tok.getLocation(), diag::err_expected) << tok::identifier; - return; - } - - ParsingDeclSpec DS(*this); - - ParsedTemplateInfo TemplateInfo; - DeclSpecContext DSContext = - getDeclSpecContextFromDeclaratorContext(DeclaratorContext::Block); - ParseDeclarationSpecifiers(DS, TemplateInfo, AS_none, DSContext); - - ParsedAttributes LocalAttrs(AttrFactory); - ParsingDeclarator D(*this, DS, LocalAttrs, DeclaratorContext::Block); - - D.setObjectType(getAsFunction().getReturnType()); - IdentifierInfo *Id = Tok.getIdentifierInfo(); - SourceLocation IdLoc = ConsumeToken(); - D.setIdentifier(Id, IdLoc); - - Decl* ThisDecl = Actions.ActOnDeclarator(getCurScope(), D); - Actions.ActOnUninitializedDecl(ThisDecl); - Actions.FinalizeDeclaration(ThisDecl); - D.complete(ThisDecl); - if (Tok.isNot(tok::colon)) { - Diag(Tok.getLocation(), diag::err_expected) << tok::colon; - return; - } - - ExprResult Expr = ParseExpression(); - if (Expr.isInvalid()) { - Diag(Tok.getLocation(), diag::err_invalid_pcs); - return; - } - DeclaratorInfo.addContract(Expr.get()); - - ExprResult Expr = ParseExpression(); - if (Expr.isInvalid()) { - Diag(Tok.getLocation(), diag::err_invalid_pcs); - return; - } - // DeclaratorInfo.addContract(Expr.get()); - - if (Tok.isNot(tok::r_paren)) { - Diag(Tok.getLocation(), diag::err_expected) << tok::r_paren; - return; - } - ConsumeParen(); -} -*/ - /// ParseTrailingReturnType - Parse a trailing return type on a new-style /// function declaration. TypeResult Parser::ParseTrailingReturnType(SourceRange &Range, diff --git a/clang/lib/Sema/SemaContract.cpp b/clang/lib/Sema/SemaContract.cpp index d93cb0111bfc30..be96c3e5b4ff2f 100644 --- a/clang/lib/Sema/SemaContract.cpp +++ b/clang/lib/Sema/SemaContract.cpp @@ -81,9 +81,8 @@ StmtResult Sema::ActOnPreContractAssert(SourceLocation KeywordLoc, Expr *Cond) { StmtResult Sema::ActOnPostContractAssert(SourceLocation KeywordLoc, Expr *Cond, DeclStmt *ResultNameDecl) { - assert(ResultNameDecl == nullptr && "Result name decl not supported yet"); - assert(Cond); + assert(Cond); return BuildContractStmt(ContractKind::Post, KeywordLoc, Cond, ResultNameDecl); } @@ -103,20 +102,32 @@ void Sema::ActOnStartContracts(Scope *S, Declarator &D) { } -/// ActOnParamDeclarator - Called from Parser::ParseFunctionDeclarator() +/// ActOnResultNameDeclarator - Called from Parser::ParseFunctionDeclarator() /// to introduce parameters into function prototype scope. StmtResult Sema::ActOnResultNameDeclarator(Scope *S, Declarator &FuncDecl, SourceLocation IDLoc, IdentifierInfo *II) { - const DeclSpec &DS = FuncDecl.getDeclSpec(); - - - DiagnoseFunctionSpecifiers(DS); - + assert(S && S->isPostConditionScope() && "Invalid scope for result name"); + assert(II && "ResultName requires an identifier"); //CheckFunctionOrTemplateParamDeclarator(S, D); TypeSourceInfo *TInfo = GetTypeForDeclarator(FuncDecl); - QualType parmDeclType = TInfo->getType(); + assert(TInfo && "no type from declarator in ActOnParamDeclarator"); + QualType FuncType = TInfo->getType(); + assert(FuncType->isFunctionType()); + auto *FT = FuncType->getAs(); + assert(FT && "FunctionType is null"); + QualType RetType = FT->getReturnType(); + + if (RetType->isVoidType()) { + Diag(IDLoc, diag::err_void_result_name) << II; + return StmtError(); + } + + if (RetType->isUndeducedAutoType()) { + Diag(IDLoc, diag::err_ericwf_unimplemented) << "Undeduced Auto Result Name"; + } + // Check for redeclaration of parameters, e.g. int foo(int x, int x); if (II) { @@ -133,11 +144,13 @@ StmtResult Sema::ActOnResultNameDeclarator(Scope *S, Declarator &FuncDecl, } // FIXME(EricWF): Diagnose lookup conflicts with lambda captures and parameter declarations. if (auto* PVD = dyn_cast(PrevDecl)) { - Diag(IDLoc, diag::err_param_redefinition) << II; // FIXME(EricWF): Change the diagnostic here. + Diag(IDLoc, diag::err_result_name_shadows_param) << II; // FIXME(EricWF): Change the diagnostic here. Diag(PVD->getLocation(), diag::note_previous_declaration); } else if (auto *CD = dyn_cast(PrevDecl)) { Diag(IDLoc, diag::err_redefinition_different_kind) << II; Diag(PrevDecl->getLocation(), diag::note_previous_declaration); + } else { + Diag(IDLoc, diag::err_ericwf_fixme) << "Add A Diagnostic Here"; } } } @@ -145,10 +158,9 @@ StmtResult Sema::ActOnResultNameDeclarator(Scope *S, Declarator &FuncDecl, // Temporarily put parameter variables in the translation unit, not // the enclosing context. This prevents them from accidentally // looking like class members in C++. - CurContext->dumpDeclContext(); - ResultNameDecl *New = - ResultNameDecl::Create(Context, CurContext, - IDLoc, II, parmDeclType); + + auto *New = ResultNameDecl::Create(Context, CurContext, + IDLoc, II, RetType); if (FuncDecl.isInvalidType()) New->setInvalidDecl(); @@ -162,8 +174,7 @@ StmtResult Sema::ActOnResultNameDeclarator(Scope *S, Declarator &FuncDecl, // Add the parameter declaration into this scope. S->AddDecl(New); - if (II) - IdResolver.AddDecl(New); + IdResolver.AddDecl(New); return ActOnDeclStmt(ConvertDeclToDeclGroup(New), IDLoc, IDLoc); } diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index 1a294c3bb30ff8..ee8cf1a43ecf95 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -9889,6 +9889,8 @@ Sema::ActOnFunctionDeclarator(Scope *S, Declarator &D, DeclContext *DC, isVirtualOkay); if (!NewFD) return nullptr; + NewFD->dumpDeclContext(); + if (OriginalLexicalContext && OriginalLexicalContext->isObjCContainer()) NewFD->setTopLevelDeclInObjCContainer(); diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp index 3bc07425dc80b3..e894c309e17e74 100644 --- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp +++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp @@ -5201,6 +5201,20 @@ void Sema::InstantiateFunctionDefinition(SourceLocation PointOfInstantiation, InstantiateDefaultCtorDefaultArgs(Ctor); } } + // If the function has contracts, instantiate them now + SmallVector Contracts = Function->getContracts(); + if (!Contracts.empty()) { + SmallVector NewContracts; + for (auto *C : Contracts) { + StmtResult NewStmt = SubstStmt(C, TemplateArgs); + if (NewStmt.isInvalid()) { + Function->setInvalidDecl(); + } else { + NewContracts.push_back(NewStmt.getAs()); + } + } + Function->setContracts(NewContracts); + } // Instantiate the function body. Body = SubstStmt(Pattern, TemplateArgs); diff --git a/clang/lib/Serialization/ASTWriterStmt.cpp b/clang/lib/Serialization/ASTWriterStmt.cpp index b1f73953980915..423ba46f3213cb 100644 --- a/clang/lib/Serialization/ASTWriterStmt.cpp +++ b/clang/lib/Serialization/ASTWriterStmt.cpp @@ -476,7 +476,7 @@ void ASTStmtWriter::VisitContractStmt(ContractStmt *S) { Record.AddSourceLocation(S->getKeywordLoc()); Record.AddStmt(S->getCond()); if (S->hasResultNameDecl()) - Record.AddStmt(S->getResultNameDecl()); + Record.AddStmt(S->getResultNameDeclStmt()); Code = serialization::STMT_CXX_CONTRACT; } diff --git a/clang/test/SemaCXX/contracts-min.cpp b/clang/test/SemaCXX/contracts-min.cpp new file mode 100644 index 00000000000000..e7b5d6d31f289c --- /dev/null +++ b/clang/test/SemaCXX/contracts-min.cpp @@ -0,0 +1,5 @@ +// RUN: %clang_cc1 -std=c++26 -fsyntax-only -verify=expected %s -fcontracts + + +void test_pre_parse(int x) pre(x != 0); + diff --git a/clang/test/SemaCXX/contracts-template.cpp b/clang/test/SemaCXX/contracts-template.cpp new file mode 100644 index 00000000000000..675c569cf1fd36 --- /dev/null +++ b/clang/test/SemaCXX/contracts-template.cpp @@ -0,0 +1,43 @@ +// RUN: %clang_cc1 -std=c++26 -fsyntax-only -verify=expected %s -fcontracts + + + +struct ImpBC { + operator bool() const; +}; + +struct ExpBC { + explicit operator bool() const; +}; + +struct ImpBC2 { + operator int() const; +}; + +struct NoBool { +}; + +template +void foo() pre(T{}) // expected-error {{'NoBool' is not contextually convertible to 'bool'}} +{ + contract_assert(T{}); // expected-error {{'NoBool' is not contextually convertible to 'bool'}} +} + +template void foo(); +template void foo(); +template void foo(); // expected-note {{requested here}} + + +template +struct Foo { + template + void foo(U u = {}) pre(u) {} +}; + +void test_it() { + Foo f; + f.foo(); + + Foo f2; + f2.foo(); +} diff --git a/clang/test/SemaCXX/contracts.cpp b/clang/test/SemaCXX/contracts.cpp index 988a498bf3a68e..7cb8865dcd069a 100644 --- a/clang/test/SemaCXX/contracts.cpp +++ b/clang/test/SemaCXX/contracts.cpp @@ -1,12 +1,26 @@ // RUN: %clang_cc1 -std=c++26 -fsyntax-only -verify=expected %s -fcontracts -void test_pre_parse(int x) pre(x != 0) { +void test_pre_parse(int x) pre(x != 0); +void test_post_parse(int x) post(x != 0); +int test_dup_names(int x) // expected-note {{previous declaration is here}} + post(x : x != 0); // expected-error {{declaration of result name 'x' shadows parameter}} -} -void test_post_parse(int x) post(x != 0) { -} +auto test_trailing_return() -> int post(r : r != 0); + +int test_can_redecl_result_name() + post(r : r != 0) // OK + post(r : r != 0) // OK + post(r != 0); // expected-error {{use of undeclared identifier 'r'}} +struct A { + int xx; + + int test_member(int x) pre(x != 0) post(r : r != 0) post(rx : rx != 0); + void test_this_access() post(r != 0); // FIXME + + int r; +}; int test_return_parse(int x) post(r : r == x) { return x; @@ -32,7 +46,7 @@ void test_attributes(int x) { } template -void foo() pre(T{}) { +void foo() pre(T{}) { // expected-error {{'NoBool' is not contextually convertible to 'bool'}} contract_assert(T{}); // expected-error {{'NoBool' is not contextually convertible to 'bool'}} } @@ -51,3 +65,8 @@ void test_converted_to_bool(int x) contract_assert(ImpBC2{}); } + +int test_result_name_scope() post(r : r != 0) { + ((void)r); // expected-error {{use of undeclared identifier 'r'}} + return 42; +} diff --git a/clang/tools/libclang/CIndex.cpp b/clang/tools/libclang/CIndex.cpp index 35312e3d2ae702..788fce6677cfa0 100644 --- a/clang/tools/libclang/CIndex.cpp +++ b/clang/tools/libclang/CIndex.cpp @@ -7017,6 +7017,7 @@ CXCursor clang_getCursorDefinition(CXCursor C) { case Decl::LifetimeExtendedTemporary: case Decl::RequiresExprBody: case Decl::UnresolvedUsingIfExists: + case Decl::ResultName: return C; // Declaration kinds that don't make any sense here, but are From bb979cd3c9450f92ef11278191c5481fd582a6ea Mon Sep 17 00:00:00 2001 From: Eric Fiselier Date: Thu, 4 Jul 2024 14:02:10 -0500 Subject: [PATCH 6/9] Improve codegen, Start on constification. There are also various experiements that need cleaning up, specifically around builtins and source location. --- clang/include/clang/AST/ASTContext.h | 21 +++ clang/include/clang/AST/DeclCXX.h | 52 +++++++ clang/include/clang/AST/DeclID.h | 5 +- clang/include/clang/AST/Expr.h | 7 +- clang/include/clang/AST/Stmt.h | 2 +- clang/include/clang/Basic/Builtins.td | 11 ++ .../clang/Basic/DiagnosticSemaKinds.td | 5 + clang/include/clang/Basic/LangOptions.h | 41 +++--- clang/include/clang/Basic/TokenKinds.def | 2 + clang/include/clang/Driver/Options.td | 28 ++-- clang/include/clang/Sema/Scope.h | 37 ++++- clang/include/clang/Sema/Sema.h | 11 +- clang/lib/AST/ASTContext.cpp | 61 ++++++++ clang/lib/AST/Expr.cpp | 5 +- clang/lib/AST/ExprConstant.cpp | 96 ++++++++++++- clang/lib/CodeGen/CodeGenFunction.cpp | 96 ++++++++++--- clang/lib/CodeGen/CodeGenFunction.h | 8 ++ clang/lib/Driver/ToolChains/Clang.cpp | 16 +++ clang/lib/Parse/ParseDecl.cpp | 2 +- clang/lib/Parse/ParseDeclCXX.cpp | 22 ++- clang/lib/Parse/ParseExpr.cpp | 6 +- clang/lib/Parse/ParseStmt.cpp | 8 +- clang/lib/Sema/Scope.cpp | 17 ++- clang/lib/Sema/SemaContract.cpp | 48 ++++--- clang/lib/Sema/SemaDeclCXX.cpp | 1 + clang/lib/Sema/SemaExpr.cpp | 30 +++- clang/lib/Sema/TreeTransform.h | 1 + clang/lib/Serialization/ASTReader.cpp | 3 + clang/test/SemaCXX/contracts-expr.cpp | 15 ++ clang/test/SemaCXX/contracts-stolen-tests.cpp | 130 ++++++++++++++++++ clang/test/SemaCXX/ericwf.cpp | 8 ++ libcxx/include/contracts | 48 ++++--- libcxx/src/contracts.cpp | 59 ++++++-- 33 files changed, 758 insertions(+), 144 deletions(-) create mode 100644 clang/test/SemaCXX/contracts-expr.cpp create mode 100644 clang/test/SemaCXX/contracts-stolen-tests.cpp create mode 100644 clang/test/SemaCXX/ericwf.cpp diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h index de86cb5e9d7fcf..d4340c6abb1145 100644 --- a/clang/include/clang/AST/ASTContext.h +++ b/clang/include/clang/AST/ASTContext.h @@ -359,6 +359,10 @@ class ASTContext : public RefCountedBase { /// __builtin_va_list type. mutable TypedefDecl *BuiltinVaListDecl = nullptr; + /// The typedef for the predefined __builtin_source_loc_impl_type + /// type. + mutable TypedefDecl *BuiltinSourceLocImplDecl = nullptr; + /// The typedef for the predefined \c __builtin_ms_va_list type. mutable TypedefDecl *BuiltinMSVaListDecl = nullptr; @@ -1158,6 +1162,9 @@ class ASTContext : public RefCountedBase { // The decl is built when constructing 'BuiltinVaListDecl'. mutable Decl *VaListTagDecl = nullptr; + // The implicit record decl __builtin_source_loc_impl_type + mutable Decl *BuiltinSourceLocImplRecordDecl = nullptr; + // Implicitly-declared type 'struct _GUID'. mutable TagDecl *MSGuidTagDecl = nullptr; @@ -2161,6 +2168,20 @@ class ASTContext : public RefCountedBase { return getTagDeclType(MSGuidTagDecl); } + /// Retrieve the C type declaration corresponding to the predefined + /// \c __builtin_source_loc_impl_t type. + TypedefDecl *getBuiltinSourceLocImplDecl() const; + + /// Retrieve the type of the \c __builtin_source_loc_impl_t type. + QualType getBuiltinSourceLocImplType() const { + return getTypeDeclType(getBuiltinSourceLocImplDecl()); + } + + /// Retrieve the C type declaration corresponding to the predefined + /// \c __builtin_source_loc_t type. + // FIXME(ERICWF): Is this needed + Decl *getBuiltinSourceLocImplRecord() const; + /// Return whether a declaration to a builtin is allowed to be /// overloaded/redeclared. bool canBuiltinBeRedeclared(const FunctionDecl *) const; diff --git a/clang/include/clang/AST/DeclCXX.h b/clang/include/clang/AST/DeclCXX.h index 74f036c530f17c..4c9f1b244db41e 100644 --- a/clang/include/clang/AST/DeclCXX.h +++ b/clang/include/clang/AST/DeclCXX.h @@ -4399,12 +4399,15 @@ const StreamingDiagnostic &operator<<(const StreamingDiagnostic &DB, AccessSpecifier AS); +class MaterializedResultNameDecl; + /// A result name introduces in a post condition. For instance, given: /// /// int foo() post(r : r > 0); /// /// Where `r` refers to the value returned by the function class ResultNameDecl : public ValueDecl { + MaterializedResultNameDecl *MaterializedDecl = nullptr; ResultNameDecl(DeclContext *DC, SourceLocation IdLoc, IdentifierInfo *Id, QualType T) : ValueDecl(Decl::ResultName, DC, IdLoc, Id, T) {} @@ -4422,10 +4425,59 @@ class ResultNameDecl : public ValueDecl { using ValueDecl::getDeclName; using ValueDecl::setType; + void setMaterializedResultNameDecl(MaterializedResultNameDecl *D) { + + } + static bool classof(const Decl *D) { return classofKind(D->getKind()); } static bool classofKind(Kind K) { return K == Decl::ResultName; } }; +/* +class MaterializedResultNameDecl final + : public VarDecl, + private llvm::TrailingObjects { + /// The number of BindingDecl*s following this object. + unsigned NumBindings; + + MaterializedResultNameDecl(ASTContext &C, DeclContext *DC, SourceLocation StartLoc, + SourceLocation LSquareLoc, QualType T, + TypeSourceInfo *TInfo, StorageClass SC, + ArrayRef Bindings) + : VarDecl(Decomposition, C, DC, StartLoc, LSquareLoc, nullptr, T, TInfo, + SC), + NumBindings(Bindings.size()) { + std::uninitialized_copy(Bindings.begin(), Bindings.end(), + getTrailingObjects()); + for (auto *B : Bindings) + B->setDecomposedDecl(this); + } + + void anchor() override; + +public: + friend class ASTDeclReader; + friend TrailingObjects; + + static MaterializedResultNameDecl *Create(ASTContext &C, DeclContext *DC, + SourceLocation StartLoc, + SourceLocation LSquareLoc, + QualType T, TypeSourceInfo *TInfo, + StorageClass S, + ArrayRef Bindings); + static MaterializedResultNameDecl *CreateDeserialized(ASTContext &C, GlobalDeclID ID, + unsigned NumBindings); + + ArrayRef bindings() const { + return llvm::ArrayRef(getTrailingObjects(), NumBindings); + } + + void printName(raw_ostream &OS, const PrintingPolicy &Policy) const override; + + static bool classof(const Decl *D) { return classofKind(D->getKind()); } + static bool classofKind(Kind K) { return K == Decomposition; } +}; +*/ } // namespace clang #endif // LLVM_CLANG_AST_DECLCXX_H diff --git a/clang/include/clang/AST/DeclID.h b/clang/include/clang/AST/DeclID.h index e8f4860e13f1f8..3ef7c85c8a7498 100644 --- a/clang/include/clang/AST/DeclID.h +++ b/clang/include/clang/AST/DeclID.h @@ -84,13 +84,16 @@ enum PredefinedDeclIDs { /// The internal '__type_pack_element' template. PREDEF_DECL_TYPE_PACK_ELEMENT_ID = 17, + + /// The internal '__builtin_source_location_impl_type' typedef. + PREDEF_DECL_BUILTIN_SOURCE_LOCATION_IMPL_T_ID = 18, }; /// The number of declaration IDs that are predefined. /// /// For more information about predefined declarations, see the /// \c PredefinedDeclIDs type and the PREDEF_DECL_*_ID constants. -const unsigned int NUM_PREDEF_DECL_IDS = 18; +const unsigned int NUM_PREDEF_DECL_IDS = 19; /// GlobalDeclID means DeclID in the current ASTContext and LocalDeclID means /// DeclID specific to a certain ModuleFile. Specially, in ASTWriter, the diff --git a/clang/include/clang/AST/Expr.h b/clang/include/clang/AST/Expr.h index f2bf667636dc9b..423c7456b0fe57 100644 --- a/clang/include/clang/AST/Expr.h +++ b/clang/include/clang/AST/Expr.h @@ -4718,12 +4718,13 @@ enum class SourceLocIdentKind { FileName, Line, Column, - SourceLocStruct + SourceLocStruct, + BuiltinSourceLocStruct }; /// Represents a function call to one of __builtin_LINE(), __builtin_COLUMN(), /// __builtin_FUNCTION(), __builtin_FUNCSIG(), __builtin_FILE(), -/// __builtin_FILE_NAME() or __builtin_source_location(). +/// __builtin_FILE_NAME(), __builtin_source_location(), or __builtin_source_location()2. class SourceLocExpr final : public Expr { SourceLocation BuiltinLoc, RParenLoc; DeclContext *ParentContext; @@ -4755,6 +4756,7 @@ class SourceLocExpr final : public Expr { case SourceLocIdentKind::Function: case SourceLocIdentKind::FuncSig: case SourceLocIdentKind::SourceLocStruct: + case SourceLocIdentKind::BuiltinSourceLocStruct: return false; case SourceLocIdentKind::Line: case SourceLocIdentKind::Column: @@ -4789,6 +4791,7 @@ class SourceLocExpr final : public Expr { case SourceLocIdentKind::Function: case SourceLocIdentKind::FuncSig: case SourceLocIdentKind::SourceLocStruct: + case SourceLocIdentKind::BuiltinSourceLocStruct: return true; default: return false; diff --git a/clang/include/clang/AST/Stmt.h b/clang/include/clang/AST/Stmt.h index 92102a710eadc1..be5a6da66a6e0e 100644 --- a/clang/include/clang/AST/Stmt.h +++ b/clang/include/clang/AST/Stmt.h @@ -709,7 +709,7 @@ class alignas(void *) Stmt { /// The kind of source location builtin represented by the SourceLocExpr. /// Ex. __builtin_LINE, __builtin_FUNCTION, etc. LLVM_PREFERRED_TYPE(SourceLocIdentKind) - unsigned Kind : 3; + unsigned Kind : 4; }; class StmtExprBitfields { diff --git a/clang/include/clang/Basic/Builtins.td b/clang/include/clang/Basic/Builtins.td index 33b2c8b77142fb..7b887d47611c3d 100644 --- a/clang/include/clang/Basic/Builtins.td +++ b/clang/include/clang/Basic/Builtins.td @@ -2582,6 +2582,17 @@ def ContractViolation : Builtin { let Attributes = [NoThrow, NoReturn, Constexpr]; } +def UnnamedConstant : Builtin { + let Spellings = ["__builtin_unnamed_constant"]; + let Prototype = "void(...)"; + let Attributes = [NoThrow, CustomTypeChecking, Constexpr]; +} + +def CurrentContractEvaluationSemantic : Builtin { + let Spellings = ["__builtin_contract_evaluation_semantic"]; + let Prototype = "int()"; + let Attributes = [NoThrow, Constexpr]; +} // C99 library functions // C99 stdarg.h diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 1f7e50b1c0bc3c..7746c50e07782a 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -11823,6 +11823,9 @@ def err_coroutine_return_type : Error< >; } // end of coroutines issue category +def select_contract_kind : TextSubstitution< + "%select{pre|post|contract_assert}0">; + let CategoryName = "Contract Issue" in { def err_ericwf_unimplemented : Error< "contract feature %0 is not yet implemented (complain to eric@efcs.ca)" @@ -11843,6 +11846,8 @@ def err_void_result_name : Error< "result name %0 cannot be used with a void return type">; def err_deduced_auto_result_name_without_body : Error< "result name on function with deduced return type requires a body">; +def err_result_name_not_allowed : Error< + "result name %0 not allowed outside of post condition specifier">; } // end of contracts issues let CategoryName = "Documentation Issue" in { diff --git a/clang/include/clang/Basic/LangOptions.h b/clang/include/clang/Basic/LangOptions.h index 8d26d11d7727a0..d2e78e58cd6dc5 100644 --- a/clang/include/clang/Basic/LangOptions.h +++ b/clang/include/clang/Basic/LangOptions.h @@ -443,35 +443,29 @@ class LangOptionsBase { /// Contract evaluation mode. Determines whether to check contracts, and // whether contract failures cause compile errors. - enum class ContractEvalMode { + enum class ContractEvaluationSemantic { // Contracts are parsed, syntax checked and type checked, but never evaluated. + // FIXME(EricWF): This doesn't yet map to an actual enumerator in + // std::contracts::evaluation_semantic Ignore = 0, - // Contracts are run, and failures are reported, but contract failures do not - // logically stop execution of the program, nor can the compiler assume - // contracts are true for optimizing. - Observe = 1, - // Contracts are run, failures are reported, and when a contract fails the // program is terminated. The compiler can assume after contracts statements // that the contracts hold. - Enforce = 2, - }; + Enforce = 1, - /// Determines the strategy to implement contract evaluation - enum class ContractImplStrategy { - // Contracts are checked after entering and before exiting a function. - Callee = 0, - - // Precontracts are checked before making a function call and after entering the function - // Postcontracts are checked before returning from a function, and after returning from a function call. - Both = 1, - - // Precontracts are checked before making a function call, postcontracts - // are checked before returning from a function call. - Split = 2, + // Contracts are run, and failures are reported, but contract failures do not + // logically stop execution of the program, nor can the compiler assume + // contracts are true for optimizing. + Observe = 2, + + // Contracts are run, failures cause an immediate trap + // FIXME(EricWF): This doesn't yet map to an actual enumerator in + // std::contracts::evaluation_semantic + QuickEnforce = 3, }; + // Define simple language options (with no accessors). #define LANGOPT(Name, Bits, Default, Description) unsigned Name : Bits; #define ENUM_LANGOPT(Name, Type, Bits, Default, Description) @@ -587,10 +581,11 @@ class LangOptions : public LangOptionsBase { GPUDefaultStreamKind GPUDefaultStream; /// C++ contracts evaluation mode - ContractEvalMode ContractEvaluation; + ContractEvaluationSemantic ContractEvalSemantic; - /// C++ contracts implementation strategy - ContractImplStrategy ContractStrategy; + /// A List of + or - prefixed contract groups to enable or disable + /// FIXME(EricWF): Implement this + std::vector ClangContractGroups; /// The seed used by the randomize structure layout feature. std::string RandstructSeed; diff --git a/clang/include/clang/Basic/TokenKinds.def b/clang/include/clang/Basic/TokenKinds.def index f67bd102ab8da6..70e3ee078482f5 100644 --- a/clang/include/clang/Basic/TokenKinds.def +++ b/clang/include/clang/Basic/TokenKinds.def @@ -455,6 +455,8 @@ KEYWORD(__builtin_FUNCSIG , KEYMS) KEYWORD(__builtin_LINE , KEYALL) KEYWORD(__builtin_COLUMN , KEYALL) KEYWORD(__builtin_source_location , KEYCXX) +KEYWORD(__builtin_source_location2 , KEYCXX) + // __builtin_types_compatible_p is a GNU C extension that we handle like a C++ // type trait. diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td index 2ab7b9810703c0..ce5f8757a05bce 100644 --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -1662,27 +1662,27 @@ def offload_EQ : CommaJoined<["--"], "offload=">, Flags<[NoXarchOption]>, HelpText<"Specify comma-separated list of offloading target triples (CUDA and HIP only)">; // C++ Contracts -defm fcontracts : BoolFOption<"contracts", +defm contracts : BoolFOption<"contracts", LangOpts<"Contracts">, DefaultFalse, - PosFlag, NegFlag>; -def fcontracts_eval_mode : Joined<["-"], "fcontracts_evaluation=">, - HelpText<"Specify contract evaluation mode. The default value is 'ignore'.">, - Values<"0,1,2">, + +def fcontract_evaluation_semantic_EQ : Joined<["-"], "fcontract-evaluation-semantic=">, + HelpText<"Specify contract evaluation semantic. The default value is 'enforce'.">, + Values<"ignore,enforce,observe,quick_enforce">, Visibility<[ClangOption, CC1Option]>, - NormalizedValuesScope<"LangOptions::ContractEvalMode">, - NormalizedValues<["Ignore", "Observe", "Enforce"]>, - MarshallingInfoEnum, "Ignore">; + NormalizedValuesScope<"LangOptions::ContractEvaluationSemantic">, + NormalizedValues<["Ignore", "Enforce", "Observe", "QuickEnforce"]>, + MarshallingInfoEnum, "Enforce">; -def fcontracts_impl_strategy : Joined<["-"], "fcontracts_strategy=">, - HelpText<"Specify contract implementation strategy. The default value is 'callee'.">, - Values<"0,1,2">, +def fclang_contract_groups_EQ : CommaJoined<["-"], "fcontract-groups=">, Visibility<[ClangOption, CC1Option]>, - NormalizedValuesScope<"LangOptions::ContractImplStrategy">, - NormalizedValues<["Callee", "Both", "Split"]>, - MarshallingInfoEnum, "Callee">; + HelpText<"Clang extension. Enable or disable contracts by group. The argument is a comma-separated " + "sequence of one or more group names, each prefixed by '+' or '-'.">, + MarshallingInfoStringVector>; + // C++ Coroutines defm coroutines : BoolFOption<"coroutines", diff --git a/clang/include/clang/Sema/Scope.h b/clang/include/clang/Sema/Scope.h index 68281b28845510..9675412c6a0c02 100644 --- a/clang/include/clang/Sema/Scope.h +++ b/clang/include/clang/Sema/Scope.h @@ -163,9 +163,22 @@ class Scope { /// This is a scope of friend declaration. FriendScope = 0x40000000, - /// The scope introduced by a post condition on a function declaration - PostConditionScope = 0x80000000, + /// The scope introduced by a pre, post, or contract_assert. + /// The result name of a post-condition is declared within this scope. + /// It also affects the type of DeclRefExpr's where constification is applied. + /// + /// When the scope is introduced by a contract_assert statement, it doesn't + /// push a new scope, but rather adjusts the flags of the current scope. + /// This needs to be undone at the end of c + // + // FIXME(Ericwf): This scope in unlike others, where it doesn't applied + // to the entire scope, but only to the statement that introduced it. + // This is a bit of a hack, but it's the simplest way to get the + // functionality we need. + ContractAssertScope = 0x80000000, }; + using UT = std::underlying_type_t; + static_assert(std::is_unsigned_v, "ScopeFlags must be an unsigned type"); private: /// The parent scope for this scope. This is null for the translation-unit @@ -566,8 +579,8 @@ class Scope { return getFlags() & ScopeFlags::ContinueScope; } - bool isPostConditionScope() const { - return getFlags() & ScopeFlags::PostConditionScope; + bool isContractAssertScope() const { + return getFlags() & ScopeFlags::ContractAssertScope; } /// Determine whether this scope is a C++ 'try' block. @@ -636,6 +649,22 @@ class Scope { void dump() const; }; +struct EnterContractAssertScopeRAII { + Scope *S; + bool WasContractAssertScope; + + EnterContractAssertScopeRAII(Scope *S) + : S(S), WasContractAssertScope(S->isContractAssertScope()) { + S->setFlags(S->getFlags() | Scope::ContractAssertScope); + } + ~EnterContractAssertScopeRAII() { + assert(S->getFlags() & Scope::ContractAssertScope); + if (!WasContractAssertScope) { + S->setFlags(S->getFlags() & ~Scope::ContractAssertScope); + } + } +}; + } // namespace clang #endif // LLVM_CLANG_SEMA_SCOPE_H diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 3d2228077efcbd..068d7bced2058e 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -2389,18 +2389,13 @@ class Sema final : public SemaBase { ///@{ public: - StmtResult ActOnContractAssert(SourceLocation KeywordLoc, Expr *Cond); + StmtResult ActOnContractAssert(ContractKind CK, SourceLocation KeywordLoc, Expr *Cond, + DeclStmt *ResultNameDecl = nullptr); - StmtResult ActOnPreContractAssert(SourceLocation KeywordLoc, Expr *Cond); - StmtResult ActOnPostContractAssert(SourceLocation KeywordLoc, Expr *Cond, - DeclStmt *ResultNameDecl = nullptr); - StmtResult BuildResultNameDecl(DeclContext* DC, SourceLocation Loc, IdentifierInfo *Id, QualType T); - void ActOnStartContracts(Scope *S, Declarator& D); StmtResult ActOnResultNameDeclarator(Scope *S, Declarator &FuncDecl, SourceLocation IDLoc, IdentifierInfo *II); - ExprResult ActOnContractAssertCondition(Expr *Cond); StmtResult BuildContractStmt(ContractKind CK, SourceLocation KeywordLoc, @@ -6507,6 +6502,8 @@ class Sema final : public SemaBase { /// \. RecordDecl *StdSourceLocationImplDecl; + RecordDecl *BuiltinSourceLocationImplDecl; + /// A stack of expression evaluation contexts. SmallVector ExprEvalContexts; diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp index a4e6d3b108c8a5..aefd89f2d29d1f 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -9175,6 +9175,49 @@ CreateSystemZBuiltinVaListDecl(const ASTContext *Context) { return Context->buildImplicitTypedef(VaListTagArrayType, "__builtin_va_list"); } +static TypedefDecl *CreateBuiltinSourceLocImplDecl(const ASTContext *Context) { + RecordDecl *SourceLocImplDecl = Context->buildImplicitRecord("__builtin_source_location_impl_type"); + SourceLocImplDecl->startDefinition(); + + + const size_t NumFields = 4; + QualType FieldTypes[NumFields]; + const char *FieldNames[NumFields]; + + QualType ConstStrLiteralTy = Context->getPointerType(Context->getConstType(Context->CharTy));; + + + FieldTypes[0] = ConstStrLiteralTy; + FieldNames[0] = "_M_file_name"; + + FieldTypes[1] = ConstStrLiteralTy; + FieldNames[1] = "_M_function_name"; + + FieldTypes[2] = Context->UnsignedIntTy; + FieldNames[2] = "_M_line"; + + FieldTypes[3] = Context->UnsignedIntTy; + FieldNames[3] = "_M_column"; + + // Create fields + for (unsigned i = 0; i < NumFields; ++i) { + FieldDecl *Field = FieldDecl::Create( + const_cast(*Context), SourceLocImplDecl, SourceLocation(), + SourceLocation(), &Context->Idents.get(FieldNames[i]), FieldTypes[i], + /*TInfo=*/nullptr, + /*BitWidth=*/nullptr, + /*Mutable=*/false, ICIS_NoInit); + Field->setAccess(AS_public); + SourceLocImplDecl->addDecl(Field); + } + SourceLocImplDecl->completeDefinition(); + Context->BuiltinSourceLocImplRecordDecl = SourceLocImplDecl; + QualType SourceLocImplTy = Context->getRecordType(SourceLocImplDecl); + + + return Context->buildImplicitTypedef(SourceLocImplTy, "__builtin_source_loc_impl_t"); +} + static TypedefDecl *CreateHexagonBuiltinVaListDecl(const ASTContext *Context) { // typedef struct __va_list_tag { RecordDecl *VaListTagDecl; @@ -9261,6 +9304,23 @@ TypedefDecl *ASTContext::getBuiltinVaListDecl() const { return BuiltinVaListDecl; } +TypedefDecl *ASTContext::getBuiltinSourceLocImplDecl() const { + if (!BuiltinSourceLocImplDecl) { + BuiltinSourceLocImplDecl = CreateBuiltinSourceLocImplDecl(this); + assert(BuiltinSourceLocImplDecl->isImplicit()); + } + + return BuiltinSourceLocImplDecl; +} + +Decl *ASTContext::getBuiltinSourceLocImplRecord() const { + if (!BuiltinSourceLocImplDecl) + (void)getBuiltinSourceLocImplDecl(); + + assert(BuiltinSourceLocImplRecordDecl); + return BuiltinSourceLocImplRecordDecl; +} + Decl *ASTContext::getVaListTagDecl() const { // Force the creation of VaListTagDecl by building the __builtin_va_list // declaration. @@ -9270,6 +9330,7 @@ Decl *ASTContext::getVaListTagDecl() const { return VaListTagDecl; } + TypedefDecl *ASTContext::getBuiltinMSVaListDecl() const { if (!BuiltinMSVaListDecl) BuiltinMSVaListDecl = CreateMSVaListDecl(this); diff --git a/clang/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp index 37ba5b69f446de..866fcadeb0020b 100644 --- a/clang/lib/AST/Expr.cpp +++ b/clang/lib/AST/Expr.cpp @@ -2266,6 +2266,8 @@ StringRef SourceLocExpr::getBuiltinStr() const { return "__builtin_COLUMN"; case SourceLocIdentKind::SourceLocStruct: return "__builtin_source_location"; + case SourceLocIdentKind::BuiltinSourceLocStruct: + return "__builtin_source_location2"; } llvm_unreachable("unexpected IdentKind!"); } @@ -2326,7 +2328,8 @@ APValue SourceLocExpr::EvaluateInContext(const ASTContext &Ctx, return APValue(Ctx.MakeIntValue(PLoc.getLine(), Ctx.UnsignedIntTy)); case SourceLocIdentKind::Column: return APValue(Ctx.MakeIntValue(PLoc.getColumn(), Ctx.UnsignedIntTy)); - case SourceLocIdentKind::SourceLocStruct: { + case SourceLocIdentKind::SourceLocStruct: + case SourceLocIdentKind::BuiltinSourceLocStruct: { // Fill in a std::source_location::__impl structure, by creating an // artificial file-scoped CompoundLiteralExpr, and returning a pointer to // that. diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 2e72e2b1b5c27f..b2e916babbaa76 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -549,6 +549,11 @@ namespace { /// initializer expression we're evaluating, if any. CurrentSourceLocExprScope CurSourceLocExprScope; + /// The return value of the function call used as the value of a ResultNameDecl + /// in a post contract assert. + const APValue *ReturnValue = nullptr; + const LValue *ReturnValueSlot = nullptr; + // Note that we intentionally use std::map here so that references to // values are stable. typedef std::pair MapKeyTy; @@ -1069,6 +1074,23 @@ namespace { return true; } + std::pair + tryGetReturnValue() { + // We will eventually hit BottomFrame, which has Index 1, so Frame can't + // be null in this loop. + unsigned Depth = CallStackDepth; + CallStackFrame *Frame = CurrentCall; + while (Frame) { + if (Frame->ReturnValue) + break; + Frame = Frame->Caller; + --Depth; + } + if (Frame) + return {Frame, Depth}; + return {nullptr, 0}; + } + std::pair getCallFrameAndDepth(unsigned CallIndex) { assert(CallIndex && "no call index in getCallFrameAndDepth"); @@ -4977,6 +4999,10 @@ static bool EvaluateDecl(EvalInfo &Info, const Decl *D) { if (const VarDecl *VD = dyn_cast(D)) OK &= EvaluateVarDecl(Info, VD); + if (const ResultNameDecl *RND = dyn_cast(D)) { + assert(false); + } + if (const DecompositionDecl *DD = dyn_cast(D)) for (auto *BD : DD->bindings()) if (auto *VD = BD->getHoldingVar()) @@ -5017,6 +5043,8 @@ struct StmtResult { const LValue *Slot; }; + + struct TempVersionRAII { CallStackFrame &Frame; @@ -6317,7 +6345,29 @@ static bool EvaluatePreContracts(const FunctionDecl *Callee, EvalInfo &Info) { return true; } -static bool EvaluatePostContracts(const FunctionDecl* Callee, EvalInfo& Info) { +struct ReturnValueRAIIGuard { + CallStackFrame *Frame; + ReturnValueRAIIGuard(CallStackFrame *F, StmtResult *Result) + : Frame(F) { + assert(Result); + assert(Frame && !Frame->ReturnValue && "Already have a return value"); + Frame->ReturnValue = &Result->Value; + Frame->ReturnValueSlot = Result->Slot; + } + ~ReturnValueRAIIGuard() { + assert(Frame->ReturnValue); + Frame->ReturnValue = nullptr; + Frame->ReturnValueSlot = nullptr; + } + +}; + +static bool EvaluatePostContracts(const FunctionDecl* Callee, EvalInfo& Info, StmtResult& Ret, CallStackFrame& Frame) { + assert(Info.CurrentCall); + assert(&Frame == Info.CurrentCall && "Mismatched call stack frame"); + + ReturnValueRAIIGuard Guard(&Frame, &Ret); + assert(Info.CurrentCall->ReturnValue); for (ContractStmt *S : Callee->getContracts()) { if (S->getContractKind() != ContractKind::Post) continue; @@ -6377,15 +6427,15 @@ static bool HandleFunctionCall(SourceLocation CallLoc, StmtResult Ret = {Result, ResultSlot}; EvalStmtResult ESR = EvaluateStmt(Ret, Info, Body); - if (!EvaluatePostContracts(Callee, Info)) - return false; if (ESR == ESR_Succeeded) { if (Callee->getReturnType()->isVoidType()) - return true; + return EvaluatePostContracts(Callee, Info, Ret, Frame); Info.FFDiag(Callee->getEndLoc(), diag::note_constexpr_no_return); } - return ESR == ESR_Returned; + if (ESR == ESR_Returned) + return EvaluatePostContracts(Callee, Info, Ret, Frame); + return false; } /// Evaluate a constructor call. @@ -8512,6 +8562,7 @@ class LValueExprEvaluator bool VisitCallExpr(const CallExpr *E); bool VisitDeclRefExpr(const DeclRefExpr *E); + bool VisitResultNameDecl(const ResultNameDecl *E); bool VisitPredefinedExpr(const PredefinedExpr *E) { return Success(E); } bool VisitMaterializeTemporaryExpr(const MaterializeTemporaryExpr *E); bool VisitCompoundLiteralExpr(const CompoundLiteralExpr *E); @@ -8625,6 +8676,23 @@ bool LValueExprEvaluator::VisitDeclRefExpr(const DeclRefExpr *E) { if (isa(D)) return Success(cast(D)); + if (const ResultNameDecl *RND = dyn_cast(D)) { + if (Info.checkingPotentialConstantExpression()) + return false; + auto [Frame, Index] = Info.tryGetReturnValue(); + if (!Frame) + return Error(E); + + assert(Frame->ReturnValue && + "ResultNameDecl without a return value"); + Frame->ReturnValue->dump(); + llvm::errs().flush(); + + assert(Frame->ReturnValue->isLValue() && + "ResultNameDecl with a non-lvalue return value"); + + return Success(Frame->ReturnValue->getLValueBase()); + } if (const VarDecl *VD = dyn_cast(D)) return VisitVarDecl(E, VD); if (const BindingDecl *BD = dyn_cast(D)) @@ -8632,6 +8700,24 @@ bool LValueExprEvaluator::VisitDeclRefExpr(const DeclRefExpr *E) { return Error(E); } +bool LValueExprEvaluator::VisitResultNameDecl(const ResultNameDecl *E) { + E->dumpColor(); + assert(false); + if (Info.checkingPotentialConstantExpression()) + return false; + auto [Frame, Index] = Info.tryGetReturnValue(); + assert(Frame); + + assert(Frame->ReturnValue && + "ResultNameDecl without a return value"); + Frame->ReturnValue->dump(); + llvm::errs().flush(); + + assert(Frame->ReturnValue->isLValue() && + "ResultNameDecl with a non-lvalue return value"); + + return Success(Frame->ReturnValue->getLValueBase()); +} bool LValueExprEvaluator::VisitVarDecl(const Expr *E, const VarDecl *VD) { diff --git a/clang/lib/CodeGen/CodeGenFunction.cpp b/clang/lib/CodeGen/CodeGenFunction.cpp index 62e6ddbf869110..6b0de9c35cb179 100644 --- a/clang/lib/CodeGen/CodeGenFunction.cpp +++ b/clang/lib/CodeGen/CodeGenFunction.cpp @@ -35,6 +35,7 @@ #include "clang/Basic/TargetInfo.h" #include "clang/CodeGen/CGFunctionInfo.h" #include "clang/Frontend/FrontendDiagnostic.h" +#include "clang/Basic/SourceManager.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/Frontend/OpenMP/OMPIRBuilder.h" #include "llvm/IR/DataLayout.h" @@ -48,6 +49,7 @@ #include "llvm/Support/xxhash.h" #include "llvm/Transforms/Scalar/LowerExpectIntrinsic.h" #include "llvm/Transforms/Utils/PromoteMemToReg.h" +#include "clang/Lex/Lexer.h" #include using namespace clang; @@ -337,6 +339,79 @@ llvm::DebugLoc CodeGenFunction::EmitReturnBlock() { } + +void CodeGenFunction::EmitHandleContractViolationCall(const ContractStmt &S, + ContractViolationDetection ViolationDetectionMode) { + auto &SM = getContext().getSourceManager(); + auto &Ctx = getContext(); + + auto Begin = S.hasResultNameDecl() ? S.getResultNameDecl()->getBeginLoc() : S.getCond()->getBeginLoc(); + auto End = S.getCond()->getEndLoc(); + CharSourceRange ExprRange = Lexer::getAsCharRange(SourceRange(Begin, End), SM, Ctx.getLangOpts()); + std::string AssertStr = Lexer::getSourceText(ExprRange, SM, Ctx.getLangOpts()).str(); + // FIXME(EricWF): We don't track the closing parenthesis in the source range. + + + unsigned ContractKindValue = [&]() { + switch (S.getContractKind()) { + case ContractKind::Pre: + return 1; + case ContractKind::Post: + return 2; + case ContractKind::Assert: + return 3; + } + llvm_unreachable("unhandled ContractKind"); + }(); + + + unsigned EvalSemantic = [&]() -> unsigned{ + using EST = LangOptions::ContractEvaluationSemantic; + switch (getLangOpts().ContractEvalSemantic) { + case EST::Ignore: + assert(false && "unimplemented"); + case EST::Enforce: + return 1; + case EST::Observe: + return 2; + case EST::QuickEnforce: + assert(false && "unimplemented"); + } + llvm_unreachable("unhandled ContractEvaluationSemantic"); + }(); + + unsigned DetectionMode = static_cast(ViolationDetectionMode); + + const SourceLocation Location = S.getCond()->getBeginLoc(); + + std::string Filename = [&]() -> std::string { + PresumedLoc PLoc = SM.getPresumedLoc(Location); + return PLoc.getFilename(); + }(); + + auto UnsignedTy = getContext().UnsignedIntTy; + auto VoidPtrTy = getContext().VoidPtrTy; + std::pair Constants[] = { + {llvm::ConstantInt::get(Int32Ty, ContractKindValue), UnsignedTy}, + {llvm::ConstantInt::get(Int32Ty, EvalSemantic), UnsignedTy}, + {llvm::ConstantInt::get(Int32Ty, DetectionMode), UnsignedTy}, + {CGM.GetAddrOfConstantCString(AssertStr).getPointer(), VoidPtrTy}, + {CGM.GetAddrOfConstantCString(Filename).getPointer(), VoidPtrTy}, + {CGM.EmitAnnotationLineNo(Location), UnsignedTy} + }; + + CallArgList Args; + for (auto [Const, Ty] : Constants) { + Args.add(RValue::get(Const), Ty); + } + + const CGFunctionInfo &VFuncInfo = CGM.getTypes().arrangeBuiltinFunctionCall(getContext().VoidTy, Args); + llvm::FunctionType *VFTy = CGM.getTypes().GetFunctionType(VFuncInfo); + llvm::FunctionCallee VFunc = CGM.CreateRuntimeFunction(VFTy, "__handle_contract_violation"); + + EmitCall(VFuncInfo, CGCallee::forDirect(VFunc), ReturnValueSlot(), Args); +} + void CodeGenFunction::EmitContractStmt(const ContractStmt &S) { // Emit the contract expression. const Expr *Expr = S.getCond(); @@ -351,25 +426,8 @@ void CodeGenFunction::EmitContractStmt(const ContractStmt &S) { Builder.SetInsertPoint(Violation); -/* - SourceRange range = Expr->getSourceRange(); - PresumedLoc PLoc = Ctx.getSourceManager().getPresumedLoc(range->getBegin()); - clang::StringLiteral* Filename = PLoc->getFilename(); - llvm::Value* LineNo = PLoc->getLine(); - clang::StringLiteral* ExpressionText = range.print(os, Ctx.getSourceManager()); -*/ - const char *VLibCallName = "_ZNSt9contracts41invoke_default_contract_" - "violation_handlerEv"; // void(void) - CallArgList Args; -/* - Args.add(EmitLoadOfLValue(EmitStringLiteralLValue(Filename), Expr->getExprLoc()), getContext().VoidPtrTy); - Args.add(RValue::get(LineNo), getContext().getSizeType()); - Args.add(EmitLoadOfLValue(EmitStringLiteralLValue(ExpressionText), Expr->getExprLoc()), getContext().VoidPtrTy); -*/ - const CGFunctionInfo &VFuncInfo = CGM.getTypes().arrangeBuiltinFunctionCall(getContext().VoidTy, {}); - llvm::FunctionType *VFTy = CGM.getTypes().GetFunctionType(VFuncInfo); - llvm::FunctionCallee VFunc = CGM.CreateRuntimeFunction(VFTy, VLibCallName); - EmitCall(VFuncInfo, CGCallee::forDirect(VFunc), ReturnValueSlot(), Args); + EmitHandleContractViolationCall(S, ContractViolationDetection::PredicateFailed); + llvm::CallInst *TrapCall = EmitTrapCall(llvm::Intrinsic::trap); TrapCall->setDoesNotReturn(); diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h index 02b2397205b713..9b687effd2e0da 100644 --- a/clang/lib/CodeGen/CodeGenFunction.h +++ b/clang/lib/CodeGen/CodeGenFunction.h @@ -112,6 +112,12 @@ enum TypeEvaluationKind { TEK_Aggregate }; +enum class ContractViolationDetection { + PredicateFailed = 1, + ExceptionRaised = 2 + +}; + #define LIST_SANITIZER_CHECKS \ SANITIZER_CHECK(AddOverflow, add_overflow, 0) \ SANITIZER_CHECK(BuiltinUnreachable, builtin_unreachable, 0) \ @@ -4350,6 +4356,8 @@ class CodeGenFunction : public CodeGenTypeCache { void EmitContractStmt(const ContractStmt &S); + void EmitHandleContractViolationCall(const ContractStmt &S, ContractViolationDetection); + //===--------------------------------------------------------------------===// // Scalar Expression Emission //===--------------------------------------------------------------------===// diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp index 331cf6e713d890..04148ce0739f57 100644 --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -7300,6 +7300,22 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA, CmdArgs.push_back("-faligned-allocation"); } + if (Arg *A = Args.getLastArg(options::OPT_fcontracts, options::OPT_fno_contracts)) { + [&]() { + if (A->getOption().matches(options::OPT_fno_contracts)) { + CmdArgs.push_back("-fno-contracts"); + return; + } + CmdArgs.push_back("-fcontracts"); + if ((A = Args.getLastArg(options::OPT_fcontract_evaluation_semantic_EQ))) { + CmdArgs.push_back(Args.MakeArgString( + Twine("-fcontract-evaluation-semantic=") + A->getValue())); + } + }(); + + + } + // The default new alignment can be specified using a dedicated option or via // a GCC-compatible option that also turns on aligned allocation. if (Arg *A = Args.getLastArg(options::OPT_fnew_alignment_EQ, diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp index 462e6c980a628f..d248c6cc73ac45 100644 --- a/clang/lib/Parse/ParseDecl.cpp +++ b/clang/lib/Parse/ParseDecl.cpp @@ -2542,7 +2542,7 @@ Parser::DeclGroupPtrTy Parser::ParseDeclGroup(ParsingDeclSpec &DS, // FIXME(EricWF): Is this the correct place? MaybeParseFunctionContractSpecifierSeq(D); - assert(false); + if (Tok.is(tok::kw_requires)) ParseTrailingRequiresClause(D); Decl *ThisDecl = ParseDeclarationAfterDeclarator(D, TemplateInfo); diff --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp index a5ce2ec1b288b0..5e3e12e2381442 100644 --- a/clang/lib/Parse/ParseDeclCXX.cpp +++ b/clang/lib/Parse/ParseDeclCXX.cpp @@ -4374,7 +4374,7 @@ StmtResult Parser::ParseFunctionContractSpecifier(Declarator &DeclaratorInfo) { ConsumeToken(); if (Tok.isNot(tok::l_paren)) { - Diag(Tok, diag::err_expected_lparen_after) << "contract_assert"; + Diag(Tok, diag::err_expected_lparen_after) << CKStr; SkipUntil({tok::equal, tok::l_brace, tok::arrow, tok::kw_try, tok::comma, tok::l_paren}, StopAtSemi | StopBeforeMatch); @@ -4387,10 +4387,12 @@ StmtResult Parser::ParseFunctionContractSpecifier(Declarator &DeclaratorInfo) { return StmtError(); } - ParseScope ParamScope(this, Scope::DeclScope | - // Scope::FunctionDeclarationScope | + // Don't include the Scope::FunctionDeclarationScope, since it puts the + // result name introducer into wrong scope, allowing it to be referenced + // outside of the postcondition. + ParseScope ContractScope(this, Scope::DeclScope | Scope::FunctionPrototypeScope | - Scope::PostConditionScope); + Scope::ContractAssertScope); std::optional ThisScope; InitCXXThisScopeForDeclaratorIfRelevant(DeclaratorInfo, DeclaratorInfo.getDeclSpec(), ThisScope); @@ -4404,10 +4406,7 @@ StmtResult Parser::ParseFunctionContractSpecifier(Declarator &DeclaratorInfo) { DeclStmt *ResultNameStmt = nullptr; if (Tok.is(tok::identifier) && NextToken().is(tok::colon)) { - if (CK != ContractKind::Post) { - // Only post contracts can have a result name - Diag(Tok.getLocation(), diag::err_expected) << tok::identifier; - } + // Let this parse for non-post contracts. We'll diagnose it later. IdentifierInfo *Id = Tok.getIdentifierInfo(); SourceLocation IdLoc = ConsumeToken(); @@ -4438,12 +4437,7 @@ StmtResult Parser::ParseFunctionContractSpecifier(Declarator &DeclaratorInfo) { T.consumeClose(); - if (CK == ContractKind::Pre) { - return Actions.ActOnPreContractAssert(KeywordLoc, Cond.get()); - } else { - return Actions.ActOnPostContractAssert(KeywordLoc, Cond.get(), - ResultNameStmt); - } + return Actions.ActOnContractAssert(CK, KeywordLoc, Cond.get(), ResultNameStmt); } /// ParseTrailingReturnType - Parse a trailing return type on a new-style diff --git a/clang/lib/Parse/ParseExpr.cpp b/clang/lib/Parse/ParseExpr.cpp index eb7447fa038e47..d2b86537d90147 100644 --- a/clang/lib/Parse/ParseExpr.cpp +++ b/clang/lib/Parse/ParseExpr.cpp @@ -1374,6 +1374,7 @@ ExprResult Parser::ParseCastExpression(CastParseKind ParseKind, case tok::kw___builtin_FUNCSIG: case tok::kw___builtin_LINE: case tok::kw___builtin_source_location: + case tok::kw___builtin_source_location2: if (NotPrimaryExpression) *NotPrimaryExpression = true; // This parses the complete suffix; we can return early. @@ -2890,7 +2891,8 @@ ExprResult Parser::ParseBuiltinPrimaryExpression() { case tok::kw___builtin_FUNCTION: case tok::kw___builtin_FUNCSIG: case tok::kw___builtin_LINE: - case tok::kw___builtin_source_location: { + case tok::kw___builtin_source_location: + case tok::kw___builtin_source_location2: { // Attempt to consume the r-paren. if (Tok.isNot(tok::r_paren)) { Diag(Tok, diag::err_expected) << tok::r_paren; @@ -2913,6 +2915,8 @@ ExprResult Parser::ParseBuiltinPrimaryExpression() { return SourceLocIdentKind::Column; case tok::kw___builtin_source_location: return SourceLocIdentKind::SourceLocStruct; + case tok::kw___builtin_source_location2: + return SourceLocIdentKind::BuiltinSourceLocStruct; default: llvm_unreachable("invalid keyword"); } diff --git a/clang/lib/Parse/ParseStmt.cpp b/clang/lib/Parse/ParseStmt.cpp index 698737fe08c917..9f04d7dd16fd24 100644 --- a/clang/lib/Parse/ParseStmt.cpp +++ b/clang/lib/Parse/ParseStmt.cpp @@ -2437,6 +2437,9 @@ StmtResult Parser::ParseContractAssertStatement() { "Not a contract asssert statement"); SourceLocation KeywordLoc = ConsumeToken(); // eat the 'contract_assert'. + // Adjust the scope for the purposes of constification. + EnterContractAssertScopeRAII EnterCAS(getCurScope()); + // FIXME(EricWF): This seems really worg. ParsedAttributes attrs(AttrFactory); MaybeParseCXX11Attributes(attrs); @@ -2453,6 +2456,7 @@ StmtResult Parser::ParseContractAssertStatement() { SourceLocation Start = Tok.getLocation(); ExprResult Cond = ParseConditionalExpression(); + if (Cond.isUsable()) { Cond = Actions.CorrectDelayedTyposInExpr(Cond, /*InitDecl=*/nullptr, /*RecoverUncorrectedTypos=*/true); @@ -2469,7 +2473,9 @@ StmtResult Parser::ParseContractAssertStatement() { if (Cond.isInvalid()) return StmtError(); - return Actions.ActOnContractAssert(KeywordLoc, Cond.get()); + return Actions.ActOnContractAssert(ContractKind::Assert, KeywordLoc, Cond.get()); + + } /// ParseReturnStatement diff --git a/clang/lib/Sema/Scope.cpp b/clang/lib/Sema/Scope.cpp index 5bc7e79a681869..508e730797661f 100644 --- a/clang/lib/Sema/Scope.cpp +++ b/clang/lib/Sema/Scope.cpp @@ -47,6 +47,14 @@ void Scope::setFlags(Scope *parent, unsigned flags) { // transmit the parent's 'order' flag, if exists if (parent->getFlags() & OpenMPOrderClauseScope) Flags |= OpenMPOrderClauseScope; + + // FIXME(EricWF): I don't think we need to propagate the ContractAssertScope + // to child scopes. + // The only child scopes that can occur should be that of lambda expressions + // but the constification of the captures should already have been handled + // before we get here(?) + // + // Flags |= parent->getFlags() & ContractAssertScope; } else { Depth = 0; PrototypeDepth = 0; @@ -113,7 +121,7 @@ bool Scope::containedInPrototypeScope() const { } void Scope::AddFlags(unsigned FlagsToSet) { - assert((FlagsToSet & ~(BreakScope | ContinueScope)) == 0 && + assert((FlagsToSet & ~(BreakScope | ContinueScope | ContractAssertScope)) == 0 && "Unsupported scope flags"); if (FlagsToSet & BreakScope) { assert((Flags & BreakScope) == 0 && "Already set"); @@ -123,6 +131,12 @@ void Scope::AddFlags(unsigned FlagsToSet) { assert((Flags & ContinueScope) == 0 && "Already set"); ContinueParent = this; } + if (FlagsToSet & ContractAssertScope) { + assert((Flags & ContractAssertScope) == 0 && "Already set"); + // Do I need to create and set a new parent for contract assert scope? + // FIXME(EricWF): Maybe do this? + } + Flags |= FlagsToSet; } @@ -234,6 +248,7 @@ void Scope::dumpImpl(raw_ostream &OS) const { {OpenACCComputeConstructScope, "OpenACCComputeConstructScope"}, {TypeAliasScope, "TypeAliasScope"}, {FriendScope, "FriendScope"}, + {ContractAssertScope, "ContractAssertScope"} }; for (auto Info : FlagInfo) { diff --git a/clang/lib/Sema/SemaContract.cpp b/clang/lib/Sema/SemaContract.cpp index be96c3e5b4ff2f..674c66b749fc0d 100644 --- a/clang/lib/Sema/SemaContract.cpp +++ b/clang/lib/Sema/SemaContract.cpp @@ -50,6 +50,7 @@ using namespace clang; using namespace sema; ExprResult Sema::ActOnContractAssertCondition(Expr *Cond) { + //assert(getCurScope()->isContractAssertScope() && "Incorrect scope for contract assert"); if (Cond->isTypeDependent()) return Cond; @@ -63,6 +64,8 @@ ExprResult Sema::ActOnContractAssertCondition(Expr *Cond) { StmtResult Sema::BuildContractStmt(ContractKind CK, SourceLocation KeywordLoc, Expr *Cond, DeclStmt *ResultNameDecl) { + assert((CK == ContractKind::Post || ResultNameDecl == nullptr) && + "ResultNameDecl only allowed for postconditions"); ExprResult E = ActOnContractAssertCondition(Cond); if (E.isInvalid()) return StmtError(); @@ -70,23 +73,28 @@ StmtResult Sema::BuildContractStmt(ContractKind CK, SourceLocation KeywordLoc, return ContractStmt::Create(Context, CK, KeywordLoc, E.get(), ResultNameDecl); } -StmtResult Sema::ActOnContractAssert(SourceLocation KeywordLoc, Expr *Cond) { - return BuildContractStmt(ContractKind::Assert, KeywordLoc, Cond, nullptr); +static ResultNameDecl *extractResultName(DeclStmt *DS) { + assert(DS && DS->isSingleDecl() && "Expected a single declaration"); + auto *D = DS->getSingleDecl(); + return cast(D); } -StmtResult Sema::ActOnPreContractAssert(SourceLocation KeywordLoc, Expr *Cond) { +StmtResult Sema::ActOnContractAssert(ContractKind CK, SourceLocation KeywordLoc, Expr *Cond, + DeclStmt *ResultNameDecl) { + if (CK != ContractKind::Post && ResultNameDecl) { + auto RND = extractResultName(ResultNameDecl); + auto *II = RND->getDeclName().getAsIdentifierInfo(); + assert(II && "ResultName requires an identifier"); - return BuildContractStmt(ContractKind::Pre, KeywordLoc, Cond, nullptr); -} - -StmtResult Sema::ActOnPostContractAssert(SourceLocation KeywordLoc, Expr *Cond, - DeclStmt *ResultNameDecl) { - - assert(Cond); - return BuildContractStmt(ContractKind::Post, KeywordLoc, Cond, - ResultNameDecl); + Diag(RND->getBeginLoc(), diag::err_result_name_not_allowed) + << II; + return StmtError(); + } + return BuildContractStmt(CK, KeywordLoc, Cond, ResultNameDecl); } +/* FIXME(EricWF): Is this needed? + * void Sema::ActOnStartContracts(Scope *S, Declarator &D) { if (!D.isFunctionDeclarator()) return; @@ -97,27 +105,23 @@ void Sema::ActOnStartContracts(Scope *S, Declarator &D) { FTI.NumParams)) { auto *ParamDecl = cast(Param.Param); if (ParamDecl->getDeclName()) - PushOnScopeChains(ParamDecl, S, /*AddToContext=*/false); + PushOnScopeChains(ParamDecl, S, false);//AddToContext=false); } } - +*/ /// ActOnResultNameDeclarator - Called from Parser::ParseFunctionDeclarator() /// to introduce parameters into function prototype scope. StmtResult Sema::ActOnResultNameDeclarator(Scope *S, Declarator &FuncDecl, SourceLocation IDLoc, IdentifierInfo *II) { - assert(S && S->isPostConditionScope() && "Invalid scope for result name"); + assert(S && S->isContractAssertScope() && "Invalid scope for result name"); assert(II && "ResultName requires an identifier"); //CheckFunctionOrTemplateParamDeclarator(S, D); TypeSourceInfo *TInfo = GetTypeForDeclarator(FuncDecl); - assert(TInfo && "no type from declarator in ActOnParamDeclarator"); - QualType FuncType = TInfo->getType(); - assert(FuncType->isFunctionType()); - auto *FT = FuncType->getAs(); - assert(FT && "FunctionType is null"); - QualType RetType = FT->getReturnType(); + assert(TInfo && TInfo->getType()->isFunctionType() && "no type from declarator in ActOnParamDeclarator"); + QualType RetType = TInfo->getType()->getAs()->getReturnType(); if (RetType->isVoidType()) { Diag(IDLoc, diag::err_void_result_name) << II; @@ -166,7 +170,7 @@ StmtResult Sema::ActOnResultNameDeclarator(Scope *S, Declarator &FuncDecl, New->setInvalidDecl(); //CheckExplicitObjectParameter(*this, New, ExplicitThisLoc); - assert(S->isPostConditionScope()); + assert(S->isContractAssertScope()); assert(S->isFunctionPrototypeScope()); assert(S->getFunctionPrototypeDepth() >= 1); //New->setScopeInfo(S->getFunctionPrototypeDepth() - 1, diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index d38700d56e4ff9..6ed8a37cd97149 100644 --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -10796,6 +10796,7 @@ void Sema::ActOnFinishDelayedCXXMethodDeclaration(Scope *S, Decl *MethodD) { AdjustDeclIfTemplate(MethodD); FunctionDecl *Method = cast(MethodD); + assert(Method->getContracts().empty()); // Now that we have our default arguments, check the constructor // again. It could produce additional diagnostics or affect whether diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index e954460aa104e4..d926a3abadebdf 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -3267,6 +3267,21 @@ static void diagnoseUncapturableValueReferenceOrBinding(Sema &S, SourceLocation loc, ValueDecl *var); +/// [basic.contract.general] +/// Within the predicate of a contract assertion, id-expressions referring to variables +/// with automatic storage duration are const ([expr.prim.id.unqual]) +static bool shouldAdjustTypeForContractConstification(Sema &S, const ValueDecl *VD) { + + if (!S.getCurScope()->isContractAssertScope()) + return false; + + if (auto Var = dyn_cast(VD)) { + return Var->isLocalVarDeclOrParm(); + } + + return false; +} + /// Complete semantic analysis for a reference to the given declaration. ExprResult Sema::BuildDeclarationNameExpr( const CXXScopeSpec &SS, const DeclarationNameInfo &NameInfo, NamedDecl *D, @@ -3420,6 +3435,10 @@ ExprResult Sema::BuildDeclarationNameExpr( QualType CapturedType = getCapturedDeclRefType(cast(VD), Loc); if (!CapturedType.isNull()) type = CapturedType; + else { + if (shouldAdjustTypeForContractConstification(*this, VD)) + type = type.withConst(); + } } break; @@ -3427,7 +3446,7 @@ ExprResult Sema::BuildDeclarationNameExpr( case Decl::ResultName: // FIXME(EricWF): Is this even close to correct? valueKind = VK_LValue; - type = type.getNonReferenceType(); + type = type.getNonReferenceType().withConst(); break; case Decl::Binding: @@ -16672,7 +16691,7 @@ ExprResult Sema::ActOnSourceLocExpr(SourceLocIdentKind Kind, case SourceLocIdentKind::Column: ResultTy = Context.UnsignedIntTy; break; - case SourceLocIdentKind::SourceLocStruct: + case SourceLocIdentKind::SourceLocStruct: { if (!StdSourceLocationImplDecl) { StdSourceLocationImplDecl = LookupStdSourceLocationImpl(*this, BuiltinLoc); @@ -16683,6 +16702,11 @@ ExprResult Sema::ActOnSourceLocExpr(SourceLocIdentKind Kind, Context.getRecordType(StdSourceLocationImplDecl).withConst()); break; } + case SourceLocIdentKind::BuiltinSourceLocStruct: + ResultTy = Context.getPointerType( + Context.getRecordType(cast(Context.getBuiltinSourceLocImplRecord())).withConst()); + break; + } return BuildSourceLocExpr(Kind, ResultTy, BuiltinLoc, RPLoc, CurContext); } @@ -19466,7 +19490,7 @@ static ExprResult rebuildPotentialResultsAsNonOdrUsed(Sema &S, Expr *E, ExprResult Sema::CheckLValueToRValueConversionOperand(Expr *E) { // Check whether the operand is or contains an object of non-trivial C union - // type. + // type.f if (E->getType().isVolatileQualified() && (E->getType().hasNonTrivialToPrimitiveDestructCUnion() || E->getType().hasNonTrivialToPrimitiveCopyCUnion())) diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h index 9a837cab479785..3fc95ccddd69b8 100644 --- a/clang/lib/Sema/TreeTransform.h +++ b/clang/lib/Sema/TreeTransform.h @@ -8537,6 +8537,7 @@ TreeTransform::TransformCoyieldExpr(CoyieldExpr *E) { template StmtResult TreeTransform::TransformContractStmt(ContractStmt *S) { + ExprResult OperandResult = getDerived().TransformExpr(S->getCond()); if (OperandResult.isInvalid()) return StmtError(); diff --git a/clang/lib/Serialization/ASTReader.cpp b/clang/lib/Serialization/ASTReader.cpp index 973475cf56b8cd..8789f082d38e68 100644 --- a/clang/lib/Serialization/ASTReader.cpp +++ b/clang/lib/Serialization/ASTReader.cpp @@ -7793,6 +7793,9 @@ static Decl *getPredefinedDecl(ASTContext &Context, PredefinedDeclIDs ID) { case PREDEF_DECL_TYPE_PACK_ELEMENT_ID: return Context.getTypePackElementDecl(); + + case PREDEF_DECL_BUILTIN_SOURCE_LOCATION_IMPL_T_ID: + return Context.getBuiltinSourceLocImplRecord(); } llvm_unreachable("PredefinedDeclIDs unknown enum value"); } diff --git a/clang/test/SemaCXX/contracts-expr.cpp b/clang/test/SemaCXX/contracts-expr.cpp new file mode 100644 index 00000000000000..bf9e936338aeb8 --- /dev/null +++ b/clang/test/SemaCXX/contracts-expr.cpp @@ -0,0 +1,15 @@ +// RUN: %clang_cc1 -std=c++26 -fsyntax-only -verify=expected %s -fcontracts -fexperimental-new-constant-interpreter + + + +constexpr int f(int x) post(r : r != 0) { + return x; +} + +constexpr int do_test() { + f(1); // OK + f(0); // Not OK + return 42; +} + +constexpr int test = do_test(); diff --git a/clang/test/SemaCXX/contracts-stolen-tests.cpp b/clang/test/SemaCXX/contracts-stolen-tests.cpp new file mode 100644 index 00000000000000..6ccfba51f3dff0 --- /dev/null +++ b/clang/test/SemaCXX/contracts-stolen-tests.cpp @@ -0,0 +1,130 @@ +// RUN: %clang_cc1 -std=c++26 -fsyntax-only -verify=expected %s -fcontracts + +namespace parsing_basic_test { + int f(int x) + pre (x >= 0) + post (r: r > x); // r is the return value + + struct A { + bool empty() const noexcept; + void clear() + post (empty()); // return value is optional + }; +} + +namespace parsing_trailing_return_type_test { + auto f() -> int pre(true); + + struct S1 { + auto g() const & noexcept -> int + pre(true); + }; +} + +namespace parsing_virtual_test { + struct A { + virtual void f(int i) + pre(i >= 0); + }; + + struct B : A { + void f(int i) override final + pre(i >= 0); + }; + + struct C : A { + void f(int i) override + pre(i >= 0) = 0; + }; +} + +namespace parsing_default_delete_pure_test { + const bool a = true, b = true, c = true; + + struct X { + X() pre(a) = default; + X(const X&) pre(b) = delete; + virtual void f() pre(c) = 0; + }; +} + +namespace parsing_lambda_test { + auto f1 = [] (int x) + pre(x > 0) { return x * x; }; + + auto f2 = [] (int x) -> int + pre(x > 0) { return x * x; }; +} + +namespace parsing_pre_post_names_test { + void f(bool pre, bool post) + pre(pre) pre(post); +} + +namespace parsing_requires_clause_test { + template concept C = true; + + template + auto g(T x) -> bool + requires C + pre (x > 0); +} + +namespace parsing_ambig1_test { + const bool a = true; + using pre = int; + + auto f() -> pre pre(a); + // pre is the return type, pre(a) the precondition +} + +namespace parsing_ambig2_test { + const bool a = true; + template struct pre {}; + using post = int; + + auto g() -> pre pre(a); + // pre is the return type, pre(a) the precondition +} + +namespace parsing_ambig3_test { + constexpr bool a = true; + constexpr bool pre(bool) { return true; } + + template + void f1() requires (pre(a)); + // just a requires clause, no postcondition + + using b = bool; + + template + void f2() requires ((b)pre(a)); + // just a requires clause, no postcondition + + template + void f3() requires (a) pre(a); + // pre(a) is a postcondition +} + +namespace parsing_ambig_test4 { + constexpr int a = 1; + constexpr int b = 2; + constexpr int c = 0; + constexpr int pre(int i) { return i; } + + template + void f() requires (a < b > pre(c)); + // just a requires clause, no postcondition + + template constexpr bool d = true; + using e = char; + + template + void g() requires d < e > pre(c); + // d is a bool variable template, pre(c) is the precondition +} + +int main() { + +} + diff --git a/clang/test/SemaCXX/ericwf.cpp b/clang/test/SemaCXX/ericwf.cpp new file mode 100644 index 00000000000000..9fcae0975ad2c8 --- /dev/null +++ b/clang/test/SemaCXX/ericwf.cpp @@ -0,0 +1,8 @@ +// RUN: %clang_cc1 -std=c++26 -fsyntax-only -verify=expected %s -fcontracts + + +void foo(int x) { + int y = x; + contract_assert(++y); + ++y; +} diff --git a/libcxx/include/contracts b/libcxx/include/contracts index 203e40e90c6ed2..58e8c3bcdbd29b 100644 --- a/libcxx/include/contracts +++ b/libcxx/include/contracts @@ -9,8 +9,8 @@ namespace std::contracts { enum class assertion_kind : unsigned char { __unknown = 0, - prec = 1, - postc = 2, + pre = 1, + post = 2, assert = 3 }; using _AssertKind = assertion_kind; @@ -35,33 +35,49 @@ public: // cannot be assigned to contract_violation& operator=(const contract_violation&) = delete; ~contract_violation() = default; - const char* comment() const noexcept { return __comment_; } + const char* comment() const noexcept { return __info_.__comment_; } - _DetectionMode detection_mode() const noexcept { return __detection_mode_; } - assertion_kind kind() const noexcept { return __kind_; } - source_location location() const noexcept { return __location_; } - evaluation_semantic semantic() const noexcept { return __semantic_;} + _DetectionMode detection_mode() const noexcept { return __info_.__detection_mode_; } + assertion_kind kind() const noexcept { return __info_.__kind_; } + const char* file_name() const noexcept { return __info_.__file_name_; } + unsigned line() const noexcept { return __info_.__line_; } + evaluation_semantic semantic() const noexcept { return __info_.__semantic_;} - contract_violation(_DetectionMode __dm = _DetectionMode::__unknown, _AssertKind __k = _AssertKind::__unknown, _EvaluationSemantic __es = _EvaluationSemantic::__unknown, const char* __c = nullptr, - source_location __loc = std::source_location::current()) noexcept - : __detection_mode_(__dm), __kind_(__k), __semantic_(__es), __comment_(__c), __location_(__loc) {} - _DetectionMode __detection_mode_; - _AssertKind __kind_; - _EvaluationSemantic __semantic_; - const char* __comment_; - source_location __location_; + struct _Info { + _AssertKind __kind_; + _EvaluationSemantic __semantic_; + _DetectionMode __detection_mode_; + + const char* __comment_; + const char* __file_name_; + const char* __function_name_; + unsigned __line_; + }; + + contract_violation(_Info __info) noexcept + : __info_(__info) {} + + + _Info __info_; }; _LIBCPP_EXPORTED_FROM_ABI void invoke_default_contract_violation_handler(const contract_violation&) noexcept; -_LIBCPP_EXPORTED_FROM_ABI void invoke_default_contract_violation_handler(); } // namespace std::contracts _LIBCPP_WEAK void handle_contract_violation(const std::contracts::contract_violation&) noexcept; + +extern "C" { +_LIBCPP_EXPORTED_FROM_ABI +void __handle_contract_violation(unsigned kind, unsigned eval_semantic, +unsigned detection_mode, const char* comment, const char* file, unsigned line); +} + + #endif // _LIBCPP_CONTRACTS diff --git a/libcxx/src/contracts.cpp b/libcxx/src/contracts.cpp index 15573e3c1a9164..99130b44963426 100644 --- a/libcxx/src/contracts.cpp +++ b/libcxx/src/contracts.cpp @@ -1,23 +1,66 @@ #include <__config> #include +#include +#include + + -_LIBCPP_WEAK void handle_contract_violation() noexcept { - __builtin_abort(); -} -_LIBCPP_WEAK void handle_contract_violation(const std::contracts::contract_violation&) noexcept { - __builtin_abort(); -} namespace std::contracts { void invoke_default_contract_violation_handler(const contract_violation& violation) noexcept { + ::handle_contract_violation(violation); } -void invoke_default_contract_violation_handler() { - invoke_default_contract_violation_handler(contract_violation()); +static void display_contract_violation(const contract_violation& violation) noexcept { + using namespace std::contracts; + std::cerr << violation.file_name() << ":" << violation.line() << ": "; + auto assert_str = [&]() -> std::pair { + switch (violation.kind()) { + case _AssertKind::pre: + return {"pre(", ")"}; + case _AssertKind::post: + return {"post(", ")"}; + + case _AssertKind::assert: + return {"contract_assert(", ")"}; + + case _AssertKind::__unknown: + return {"", ""}; + } + }(); + std::cerr << assert_str.first << violation.comment() << assert_str.second << " failed" << std::endl; } } // namespace std::contracts + +extern "C" { + +void __handle_contract_violation( + unsigned kind, unsigned eval_semantic, unsigned detection_mode, const char* comment, const char* file, unsigned line) { + + using namespace std::contracts; + + using _InfoT = std::contracts::contract_violation::_Info; + _InfoT info = {.__kind_ = static_cast<_AssertKind>(kind), + .__semantic_ = static_cast<_EvaluationSemantic>(eval_semantic), + .__detection_mode_ = static_cast<_DetectionMode>(detection_mode), + .__comment_ = comment, + .__file_name_ = file, + .__function_name_ = nullptr, + .__line_ = line}; + contract_violation violation(info); + if (::handle_contract_violation) + ::handle_contract_violation(violation); + else + std::contracts::display_contract_violation(violation); + + if (info.__semantic_ == _EvaluationSemantic::enforce) { + std::terminate(); + } +} + +} From 2c6d5fd6b596a015f797530fe302ac3d3ccd62fb Mon Sep 17 00:00:00 2001 From: Eric Fiselier Date: Sat, 6 Jul 2024 01:32:14 -0500 Subject: [PATCH 7/9] A lot of forward progress. Some work on constification, but a bunch of experiment that still needs to be undone. The addition of -fclang-contract-groups=foo=, which still needs better integrating with -fcontract-evaluation-semantic=. The [[clang::contract_group("foo.bar")]] stuff works really well. I'll be happy to try it in libc++. What still needs to be done includes: * Coming up with a proper descriptor/format for the compiler-stl interface. It seems to me we'll want the ABI to be able to evolve over time, and so we should pass a pointer to data + data descriptor of some kind so that the STL can parse version of the header from different compilers & versions. * Everything to do with parsing pre/post in inline member functions. * Contract redeclaration checking. * Constifying implicit this. * Code gen & constant evaluation for result names (and representation too). * Cleanup around experiments for source location & constification. --- clang/include/clang/AST/Expr.h | 11 ++ clang/include/clang/AST/Stmt.h | 3 + clang/include/clang/AST/StmtCXX.h | 83 +++++++--- clang/include/clang/Basic/Attr.td | 2 +- clang/include/clang/Basic/ContractOptions.h | 115 ++++++++++++++ .../clang/Basic/DiagnosticDriverKinds.td | 12 ++ .../clang/Basic/DiagnosticSemaKinds.td | 15 ++ clang/include/clang/Basic/LangOptions.h | 36 ++--- clang/include/clang/Driver/Options.td | 5 +- clang/include/clang/Sema/Sema.h | 23 ++- clang/lib/AST/StmtCXX.cpp | 37 +++-- clang/lib/Basic/CMakeLists.txt | 1 + clang/lib/Basic/ContractOptions.cpp | 143 ++++++++++++++++++ clang/lib/CodeGen/CodeGenFunction.cpp | 43 ++++-- clang/lib/Driver/ToolChains/Clang.cpp | 5 + clang/lib/Frontend/CompilerInvocation.cpp | 19 ++- clang/lib/Parse/ParseDeclCXX.cpp | 20 ++- clang/lib/Parse/ParseStmt.cpp | 19 ++- clang/lib/Sema/SemaContract.cpp | 31 +++- clang/lib/Sema/SemaExpr.cpp | 24 ++- clang/lib/Sema/SemaStmtAttr.cpp | 44 ++++++ clang/lib/Sema/TreeTransform.h | 36 ++++- clang/lib/Serialization/ASTReaderStmt.cpp | 12 +- clang/lib/Serialization/ASTWriterStmt.cpp | 2 + clang/test/SemaCXX/contract-group-attr.cpp | 18 +++ clang/test/SemaCXX/contracts-expr.cpp | 34 +++++ clang/test/SemaCXX/contracts.cpp | 17 ++- clang/test/SemaCXX/ericwf-crash.cpp | 18 +++ 28 files changed, 721 insertions(+), 107 deletions(-) create mode 100644 clang/include/clang/Basic/ContractOptions.h create mode 100644 clang/lib/Basic/ContractOptions.cpp create mode 100644 clang/test/SemaCXX/contract-group-attr.cpp create mode 100644 clang/test/SemaCXX/ericwf-crash.cpp diff --git a/clang/include/clang/AST/Expr.h b/clang/include/clang/AST/Expr.h index 423c7456b0fe57..2dc8f8e0011da7 100644 --- a/clang/include/clang/AST/Expr.h +++ b/clang/include/clang/AST/Expr.h @@ -1230,6 +1230,17 @@ class OpaqueValueExpr : public Expr { } }; +// This enum is silly, but avoids creating overload sets where many adjacent +// arguments are all convertible to/from bool or int. +enum class ContractConstification { + CC_None, + CC_ApplyConst, +}; + +constexpr ContractConstification CC_None = ContractConstification::CC_None; +constexpr ContractConstification CC_ApplyConst = + ContractConstification::CC_ApplyConst; + /// A reference to a declared variable, function, enum, etc. /// [C99 6.5.1p2] /// diff --git a/clang/include/clang/AST/Stmt.h b/clang/include/clang/AST/Stmt.h index be5a6da66a6e0e..92d7ab431a7e30 100644 --- a/clang/include/clang/AST/Stmt.h +++ b/clang/include/clang/AST/Stmt.h @@ -863,6 +863,9 @@ class alignas(void *) Stmt { LLVM_PREFERRED_TYPE(ContractKind) unsigned ContractKind : 2; + + LLVM_PREFERRED_TYPE(unsigned) + unsigned NumAttrs : 32 - NumStmtBits - 3; }; class CXXNewExprBitfields { diff --git a/clang/include/clang/AST/StmtCXX.h b/clang/include/clang/AST/StmtCXX.h index 5d675f193e2710..ffd4b9f0e0fed4 100644 --- a/clang/include/clang/AST/StmtCXX.h +++ b/clang/include/clang/AST/StmtCXX.h @@ -526,11 +526,13 @@ class CoreturnStmt : public Stmt { }; enum class ContractKind { Pre, Post, Assert }; +class SemaContractHelper; class ContractStmt final : public Stmt, - private llvm::TrailingObjects { + private llvm::TrailingObjects { friend class ASTStmtReader; friend TrailingObjects; + friend class SemaContractHelper; enum { ResultNameDeclOffset = 0, Count = 1 }; @@ -539,32 +541,74 @@ class ContractStmt final : public Stmt, return ResultNameDeclOffset + ContractAssertBits.HasResultName; } - Stmt **getSubStmts() { return getTrailingObjects(); } + unsigned attributeOffset() const { return condOffset() + 1; } - Stmt *const *getSubStmts() const { return getTrailingObjects(); } + Stmt **getSubStmts() { + return reinterpret_cast(getTrailingObjects()); + } + Stmt *const *getSubStmts() const { + return reinterpret_cast(getTrailingObjects()); + } SourceLocation KeywordLoc; + const Attr *const *getAttrArrayPtr() const { + return reinterpret_cast(getTrailingObjects() + + attributeOffset()); + } + const Attr **getAttrArrayPtr() { + return const_cast(reinterpret_cast( + getTrailingObjects() + attributeOffset())); + } + + Stmt **getStmtPtr() { + return reinterpret_cast(getTrailingObjects()); + } + + Stmt *const *getStmtPtr() const { + return reinterpret_cast(getTrailingObjects()); + } + + ArrayRef getStmts() const { + return llvm::ArrayRef(getSubStmts(), ContractAssertBits.HasResultName + 1); + } + + void copyAttrs(ArrayRef Attrs) { + assert(Attrs.size() == ContractAssertBits.NumAttrs && + "wrong number of attributes"); + std::copy(Attrs.begin(), Attrs.end(), getAttrArrayPtr()); + } + public: - ContractStmt(ContractKind CK, SourceLocation KeywordLoc, Expr *Condition) + ContractStmt(ContractKind CK, SourceLocation KeywordLoc, Expr *Condition, + DeclStmt *RN, ArrayRef Attrs = {}) : Stmt(ContractStmtClass), KeywordLoc(KeywordLoc) { ContractAssertBits.ContractKind = static_cast(CK); - ContractAssertBits.HasResultName = false; + ContractAssertBits.HasResultName = RN != nullptr; + ContractAssertBits.NumAttrs = Attrs.size(); + if (RN) + setResultNameDecl(RN); setCondition(Condition); + std::copy(Attrs.begin(), Attrs.end(), getAttrArrayPtr()); } - ContractStmt(EmptyShell Empty, ContractKind Kind, bool HasResultName = false) + ContractStmt(EmptyShell Empty, ContractKind Kind, bool HasResultName, + unsigned NumAttrs = 0) : Stmt(ContractStmtClass, Empty) { ContractAssertBits.ContractKind = static_cast(Kind); ContractAssertBits.HasResultName = HasResultName; + ContractAssertBits.NumAttrs = NumAttrs; + if (NumAttrs != 0) + std::fill_n(getAttrArrayPtr(), NumAttrs, nullptr); } static ContractStmt *Create(const ASTContext &C, ContractKind Kind, SourceLocation KeywordLoc, Expr *Condition, - DeclStmt *ResultNameDecl = nullptr); + DeclStmt *ResultNameDecl, + ArrayRef Attrs = {}); static ContractStmt *CreateEmpty(const ASTContext &C, ContractKind Kind, - bool HasResultName = false); + bool HasResultName, unsigned NumAttrs); ContractKind getContractKind() const { return static_cast(ContractAssertBits.ContractKind); @@ -573,12 +617,15 @@ class ContractStmt final : public Stmt, ContractAssertBits.ContractKind = static_cast(CK); } + ArrayRef getAttrs() const { + return llvm::ArrayRef(getAttrArrayPtr(), ContractAssertBits.NumAttrs); + } + bool hasResultNameDecl() const { return ContractAssertBits.HasResultName; } DeclStmt *getResultNameDeclStmt() const { return hasResultNameDecl() - ? static_cast( - getTrailingObjects()[ResultNameDeclOffset]) + ? static_cast(getStmtPtr()[ResultNameDeclOffset]) : nullptr; } @@ -586,21 +633,19 @@ class ContractStmt final : public Stmt, void setResultNameDecl(DeclStmt *D) { assert(hasResultNameDecl() && "no result name decl"); - getTrailingObjects()[ResultNameDeclOffset] = D; + getStmtPtr()[ResultNameDeclOffset] = D; } - void setCondition(Expr *E) { getTrailingObjects()[condOffset()] = E; } + void setCondition(Expr *E) { getStmtPtr()[condOffset()] = E; } - Expr *getCond() { - return reinterpret_cast(getTrailingObjects()[condOffset()]); - } + Expr *getCond() { return static_cast(getStmtPtr()[condOffset()]); } const Expr *getCond() const { - return reinterpret_cast(getTrailingObjects()[condOffset()]); + return static_cast(getStmtPtr()[condOffset()]); } void setCond(Expr *Cond) { - getTrailingObjects()[condOffset()] = reinterpret_cast(Cond); + getStmtPtr()[condOffset()] = reinterpret_cast(Cond); } SourceLocation getKeywordLoc() const { return KeywordLoc; } @@ -609,6 +654,10 @@ class ContractStmt final : public Stmt, return getCond()->getEndLoc(); } + // Return [[clang::contract_group]] attribute group string if specified + StringRef getContractGroup() const; + ContractEvaluationSemantic getSemantic(const LangOptions &LangOpts) const; + child_range children() { return child_range(getSubStmts(), getSubStmts() + condOffset() + 1); } diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td index d57c6add79210b..3477224968cb26 100644 --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -3230,7 +3230,7 @@ def Unavailable : InheritableAttr { def ContractGroup : StmtAttr { let Spellings = [CXX11<"clang", "contract_group">]; - let Args = [ StringArgument<"Group">]; + let Args = [ StringArgument<"Group"> ]; let Subjects = SubjectList<[ContractStmt], ErrorDiag, "contract_assert">; let Documentation = [Undocumented]; } diff --git a/clang/include/clang/Basic/ContractOptions.h b/clang/include/clang/Basic/ContractOptions.h new file mode 100644 index 00000000000000..4ec36b3476c4c4 --- /dev/null +++ b/clang/include/clang/Basic/ContractOptions.h @@ -0,0 +1,115 @@ +//===- ContractsOptions.h - C++ Contract Options ----------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +/// \file +/// Defines common enums and types used by contracts and contract attributes. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_BASIC_CONTRACT_OPTIONS_H +#define LLVM_CLANG_BASIC_CONTRACT_OPTIONS_H + +#include "clang/Basic/LLVM.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/ADT/StringRef.h" +#include +#include +#include +#include +#include +#include + +namespace clang { +using llvm::StringRef; + +enum class ContractGroupDiagnostic { + // The remaining values map to %select values in diagnostics in both + // DiagnosticSemaKinds.td and DiagnosticDriverKinds.td. + InvalidFirstChar, + InvalidChar, + InvalidLastChar, + EmptySubGroup, + Empty, + InvalidSemantic, +}; + +/// Contract evaluation mode. Determines whether to check contracts, and +// whether contract failures cause compile errors. +enum class ContractEvaluationSemantic { + Invalid = -1, + + // Contracts are parsed, syntax checked and type checked, but never evaluated. + // FIXME(EricWF): This doesn't yet map to an actual enumerator in + // std::contracts::evaluation_semantic + Ignore = 0, + + // Contracts are run, failures are reported, and when a contract fails the + // program is terminated. The compiler can assume after contracts statements + // that the contracts hold. + Enforce = 1, + + // Contracts are run, and failures are reported, but contract failures do not + // logically stop execution of the program, nor can the compiler assume + // contracts are true for optimizing. + Observe = 2, + + // Contracts are run, failures cause an immediate trap + // FIXME(EricWF): This doesn't yet map to an actual enumerator in + // std::contracts::evaluation_semantic + QuickEnforce = 3, +}; + +/// Represents the set of contract groups that have been enabled or disabled +/// on the command line using '-fclang-contract-groups='. +/// +/// A contract group is a string consisting of identifiers join by '.'. For +/// example, "a.b.c" is a contract group with three subgroups. +/// +/// When determining if a particular contract check is enabled, we check if +/// the user has enabled/disabled the group/subgroup that the check belongs to, +/// in order of specificity. For example, passing +/// '-fclang-contract-groups=-std,+std.hardening,-std.hardening.foo' +/// will enable 'std.hardening.baz', but disable 'std.hardening.foo.bar' and +/// 'std.baz'. +/// +/// TODO(EricWF): Should we match in the same manner as clang-tidy checks? +/// Specifically, allow the use of '*' and drop all notion of groups? +class ContractOptions { +public: + ContractOptions() = default; + +public: + using DiagnoseGroupFunc = + std::function; + + static bool validateContractGroup(llvm::StringRef GroupAndValue, + const DiagnoseGroupFunc &Diagnoser); + + std::vector serializeContractGroupArgs() const; + + void parseContractGroups(const std::vector &Groups, + const DiagnoseGroupFunc &Diagnoser); + + void addUnparsedContractGroup(StringRef GroupAndValue, + const DiagnoseGroupFunc &Diagnoser); + + ContractEvaluationSemantic getSemanticForGroup(llvm::StringRef Group) const; + void setGroupSemantic(llvm::StringRef Group, + ContractEvaluationSemantic Semantic); + + /// The default semantics for contracts. + ContractEvaluationSemantic DefaultSemantic = + ContractEvaluationSemantic::Enforce; + + /// The semantics for each contract group, if specified. + llvm::StringMap SemanticsByGroup; +}; + +} // namespace clang + +#endif // LLVM_CLANG_BASIC_CONTRACT_OPTIONS_H diff --git a/clang/include/clang/Basic/DiagnosticDriverKinds.td b/clang/include/clang/Basic/DiagnosticDriverKinds.td index 1ca2cb85565a1a..d6880b6bf129f5 100644 --- a/clang/include/clang/Basic/DiagnosticDriverKinds.td +++ b/clang/include/clang/Basic/DiagnosticDriverKinds.td @@ -812,4 +812,16 @@ def err_drv_triple_version_invalid : Error< def warn_missing_include_dirs : Warning< "no such include directory: '%0'">, InGroup, DefaultIgnore; + + +def err_drv_contract_group_name_invalid : Error< + "contract group name %select{" + "\"%1\" cannot start with '%2'" + "|\"%1\" cannot contain '%2'" + "|\"%1\" cannot end with '%2'" + "|\"%1\" cannot contain empty subgroup \"%2\"" + "|cannot be empty" + "|\"%2\" is an invalid value" + "}0">; + } diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 7746c50e07782a..5f6a025d4465be 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -11849,6 +11849,21 @@ def err_deduced_auto_result_name_without_body : Error< def err_result_name_not_allowed : Error< "result name %0 not allowed outside of post condition specifier">; +// Diagnose clang::contract_group attribute strings +def err_contract_group_attribute_invalid_group : Error< + "clang::contract_group group %select{" + "\"%1\" cannot start with '%2'" + "|\"%1\" cannot contain '%2'" + "|\"%1\" cannot end with '%2'" + "|\"%1\" cannot contain empty subgroup \"%2\"" + "|cannot be empty" + "|" + "}0">; + + +def err_contract_group_redeclared : Error< + "clang::contract_group attribute appears more than once">; + } // end of contracts issues let CategoryName = "Documentation Issue" in { def warn_not_a_doxygen_trailing_member_comment : Warning< diff --git a/clang/include/clang/Basic/LangOptions.h b/clang/include/clang/Basic/LangOptions.h index d2e78e58cd6dc5..2acd9161f48ceb 100644 --- a/clang/include/clang/Basic/LangOptions.h +++ b/clang/include/clang/Basic/LangOptions.h @@ -15,6 +15,7 @@ #define LLVM_CLANG_BASIC_LANGOPTIONS_H #include "clang/Basic/CommentOptions.h" +#include "clang/Basic/ContractOptions.h" #include "clang/Basic/LLVM.h" #include "clang/Basic/LangStandard.h" #include "clang/Basic/ObjCRuntime.h" @@ -441,30 +442,7 @@ class LangOptionsBase { CX_None }; - /// Contract evaluation mode. Determines whether to check contracts, and - // whether contract failures cause compile errors. - enum class ContractEvaluationSemantic { - // Contracts are parsed, syntax checked and type checked, but never evaluated. - // FIXME(EricWF): This doesn't yet map to an actual enumerator in - // std::contracts::evaluation_semantic - Ignore = 0, - - // Contracts are run, failures are reported, and when a contract fails the - // program is terminated. The compiler can assume after contracts statements - // that the contracts hold. - Enforce = 1, - - // Contracts are run, and failures are reported, but contract failures do not - // logically stop execution of the program, nor can the compiler assume - // contracts are true for optimizing. - Observe = 2, - - // Contracts are run, failures cause an immediate trap - // FIXME(EricWF): This doesn't yet map to an actual enumerator in - // std::contracts::evaluation_semantic - QuickEnforce = 3, - }; - + using ContractEvaluationSemantic = clang::ContractEvaluationSemantic; // Define simple language options (with no accessors). #define LANGOPT(Name, Bits, Default, Description) unsigned Name : Bits; @@ -555,6 +533,10 @@ class LangOptions : public LangOptionsBase { /// A prefix map for __FILE__, __BASE_FILE__ and __builtin_FILE(). std::map> MacroPrefixMap; + /// A map from a "contract group" or "contract subgroup" to the value + /// indicating whether the group is enabled or disabled from the command line. + std::unordered_map EnabledContractGroups; + /// Triples of the OpenMP targets that the host code codegen should /// take into account in order to generate accurate offloading descriptors. std::vector OMPTargetTriples; @@ -583,9 +565,9 @@ class LangOptions : public LangOptionsBase { /// C++ contracts evaluation mode ContractEvaluationSemantic ContractEvalSemantic; - /// A List of + or - prefixed contract groups to enable or disable - /// FIXME(EricWF): Implement this - std::vector ClangContractGroups; + /// A list of options pretaining to c++ contracts and clang attributes about + /// them. + ContractOptions ContractOptions; /// The seed used by the randomize structure layout feature. std::string RandstructSeed; diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td index ce5f8757a05bce..66287e2488ea7c 100644 --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -1677,11 +1677,10 @@ def fcontract_evaluation_semantic_EQ : Joined<["-"], "fcontract-evaluation-seman NormalizedValues<["Ignore", "Enforce", "Observe", "QuickEnforce"]>, MarshallingInfoEnum, "Enforce">; -def fclang_contract_groups_EQ : CommaJoined<["-"], "fcontract-groups=">, +def fclang_contract_groups_EQ : CommaJoined<["-"], "fclang-contract-groups=">, Visibility<[ClangOption, CC1Option]>, HelpText<"Clang extension. Enable or disable contracts by group. The argument is a comma-separated " - "sequence of one or more group names, each prefixed by '+' or '-'.">, - MarshallingInfoStringVector>; + "sequence of one or more group names, each prefixed by '+' or '-'.">; // C++ Coroutines diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 068d7bced2058e..8cd7fb0846b608 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -2389,8 +2389,9 @@ class Sema final : public SemaBase { ///@{ public: - StmtResult ActOnContractAssert(ContractKind CK, SourceLocation KeywordLoc, Expr *Cond, - DeclStmt *ResultNameDecl = nullptr); + StmtResult ActOnContractAssert(ContractKind CK, SourceLocation KeywordLoc, + Expr *Cond, DeclStmt *ResultNameDecl, + ParsedAttributes &Attrs); StmtResult ActOnResultNameDeclarator(Scope *S, Declarator &FuncDecl, SourceLocation IDLoc, @@ -2399,7 +2400,8 @@ class Sema final : public SemaBase { ExprResult ActOnContractAssertCondition(Expr *Cond); StmtResult BuildContractStmt(ContractKind CK, SourceLocation KeywordLoc, - Expr *Cond, DeclStmt *ResultNameDecl = nullptr); + Expr *Cond, DeclStmt *ResultNameDecl, + ArrayRef Attrs); ///@} @@ -5168,6 +5170,7 @@ class Sema final : public SemaBase { EK_Decltype, EK_TemplateArgument, EK_BoundsAttrArgument, + EK_ContractStmt, EK_Other } ExprContext; @@ -5176,7 +5179,7 @@ class Sema final : public SemaBase { bool InDiscardedStatement; bool InImmediateFunctionContext; bool InImmediateEscalatingFunctionContext; - + bool InContractStatement = false; bool IsCurrentlyCheckingDefaultArgumentOrInitializer = false; // We are in a constant context, but we also allow @@ -5256,6 +5259,11 @@ class Sema final : public SemaBase { ExpressionEvaluationContext::ImmediateFunctionContext && InDiscardedStatement); } + + bool isContractStatementContext() const { + + return InContractStatement || ExprContext == EK_ContractStmt; + } }; const ExpressionEvaluationContextRecord ¤tEvaluationContext() const { @@ -5286,6 +5294,13 @@ class Sema final : public SemaBase { EK_BoundsAttrArgument; } + bool isContractStmtContext() const { + return ExprEvalContexts.back().ExprContext == + ExpressionEvaluationContextRecord::ExpressionKind:: + EK_ContractStmt || + ExprEvalContexts.back().InContractStatement; + } + /// Increment when we find a reference; decrement when we find an ignored /// assignment. Ultimately the value is 0 if every reference is an ignored /// assignment. diff --git a/clang/lib/AST/StmtCXX.cpp b/clang/lib/AST/StmtCXX.cpp index 28795190e5cc68..c7e8656878a712 100644 --- a/clang/lib/AST/StmtCXX.cpp +++ b/clang/lib/AST/StmtCXX.cpp @@ -11,8 +11,9 @@ //===----------------------------------------------------------------------===// #include "clang/AST/StmtCXX.h" -#include "clang/AST/DeclCXX.h" #include "clang/AST/ASTContext.h" +#include "clang/AST/Attr.h" +#include "clang/AST/DeclCXX.h" using namespace clang; @@ -128,23 +129,23 @@ CoroutineBodyStmt::CoroutineBodyStmt(CoroutineBodyStmt::CtorArgs const &Args) } ContractStmt *ContractStmt::CreateEmpty(const ASTContext &C, ContractKind Kind, - bool HasResultName) { - void *Mem = C.Allocate(totalSizeToAlloc(1 + HasResultName), + bool HasResultName, unsigned NumAttrs) { + void *Mem = C.Allocate(totalSizeToAlloc(1 + HasResultName + NumAttrs), alignof(ContractStmt)); return new (Mem) ContractStmt(EmptyShell(), Kind, HasResultName); } ContractStmt *ContractStmt::Create(const ASTContext &C, ContractKind Kind, SourceLocation KeywordLoc, Expr *Condition, - DeclStmt *ResultNameDecl) { + DeclStmt *ResultNameDecl, + ArrayRef Attrs) { assert((ResultNameDecl == nullptr || Kind == ContractKind::Post) && "Only a postcondition can have a result name declaration"); - auto *S = CreateEmpty(C, Kind, ResultNameDecl != nullptr); - S->KeywordLoc = KeywordLoc; - S->setCondition(Condition); - if (ResultNameDecl) - S->setResultNameDecl(ResultNameDecl); - return S; + void *Mem = C.Allocate( + totalSizeToAlloc(1 + (ResultNameDecl != nullptr) + Attrs.size()), + alignof(ContractStmt)); + return new (Mem) + ContractStmt(Kind, KeywordLoc, Condition, ResultNameDecl, Attrs); } ResultNameDecl *ContractStmt::getResultNameDecl() const { @@ -152,3 +153,19 @@ ResultNameDecl *ContractStmt::getResultNameDecl() const { assert(D); return cast(D->getSingleDecl()); } + +StringRef ContractStmt::getContractGroup() const { + for (const Attr *A : getAttrs()) { + if (const auto *CA = dyn_cast(A)) + return CA->getGroup(); + } + return StringRef(); +} + +ContractEvaluationSemantic +ContractStmt::getSemantic(const LangOptions &LangOpts) const { + StringRef Group = getContractGroup(); + if (Group.empty()) + return LangOpts.ContractOptions.DefaultSemantic; + return LangOpts.ContractOptions.getSemanticForGroup(Group); +} diff --git a/clang/lib/Basic/CMakeLists.txt b/clang/lib/Basic/CMakeLists.txt index f30680552e0f5b..718719e7f57921 100644 --- a/clang/lib/Basic/CMakeLists.txt +++ b/clang/lib/Basic/CMakeLists.txt @@ -61,6 +61,7 @@ add_clang_library(clangBasic CLWarnings.cpp CharInfo.cpp CodeGenOptions.cpp + ContractOptions.cpp Cuda.cpp DarwinSDKInfo.cpp Diagnostic.cpp diff --git a/clang/lib/Basic/ContractOptions.cpp b/clang/lib/Basic/ContractOptions.cpp new file mode 100644 index 00000000000000..b1d7591bbfb41a --- /dev/null +++ b/clang/lib/Basic/ContractOptions.cpp @@ -0,0 +1,143 @@ +//===- Contracts.cpp - C Language Family Language Options -----------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file defines the classes from ContractOptions.h +// +//===----------------------------------------------------------------------===// + +#include "clang/Basic/ContractOptions.h" +#include "llvm/ADT/Hashing.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringSwitch.h" +#include "llvm/ADT/Twine.h" + +using namespace clang; + +void ContractOptions::setGroupSemantic(StringRef Key, + ContractEvaluationSemantic Value) { + assert(!Key.empty() && + "Set the default group semantic using DefaultSemantic"); + SemanticsByGroup[Key] = Value; +} + +ContractEvaluationSemantic +ContractOptions::getSemanticForGroup(StringRef Group) const { + while (!Group.empty()) { + if (auto Pos = SemanticsByGroup.find(Group); + Pos != SemanticsByGroup.end()) { + return Pos->second; + } + Group = Group.substr(0, Group.rfind('.')); + } + return DefaultSemantic; +} + +static const char *semanticToString(ContractEvaluationSemantic Sem) { + switch (Sem) { + case ContractEvaluationSemantic::Ignore: + return "ignore"; + case ContractEvaluationSemantic::Enforce: + return "enforce"; + case ContractEvaluationSemantic::Observe: + return "observe"; + case ContractEvaluationSemantic::QuickEnforce: + return "quick_enforce"; + case ContractEvaluationSemantic::Invalid: + return ""; + } + llvm_unreachable("unhandled ContractEvaluationSemantic"); +} + +std::vector ContractOptions::serializeContractGroupArgs() const { + std::vector ArgStrings; + + ArgStrings.reserve(SemanticsByGroup.size() + 1); + ArgStrings.push_back(semanticToString(DefaultSemantic)); + for (const auto &Group : this->SemanticsByGroup) { + ArgStrings.push_back(llvm::Twine(Group.first(), "=") + .concat(semanticToString(Group.second)) + .str()); + } + return ArgStrings; +} + +ContractEvaluationSemantic semanticFromString(StringRef Str) { + return llvm::StringSwitch(Str) + .Case("ignore", ContractEvaluationSemantic::Ignore) + .Case("enforce", ContractEvaluationSemantic::Enforce) + .Case("observe", ContractEvaluationSemantic::Observe) + .Case("quick_enforce", ContractEvaluationSemantic::QuickEnforce) + .Default(ContractEvaluationSemantic::Invalid); +} + +void ContractOptions::addUnparsedContractGroup( + StringRef Group, const ContractOptions::DiagnoseGroupFunc &Diagnoser) { + if (Group.empty()) + return Diagnoser(ContractGroupDiagnostic::Empty, "", ""); + + const bool ParseAsValueOnly = not Group.contains("="); + auto [Key, Value] = Group.split('='); + if (ParseAsValueOnly) { + assert(Value.empty() && "Value should be empty"); + std::swap(Key, Value); + } else { + if (!validateContractGroup(Key, Diagnoser)) + return; + } + ContractEvaluationSemantic Sem = semanticFromString(Value); + if (Sem == ContractEvaluationSemantic::Invalid) + return Diagnoser(ContractGroupDiagnostic::InvalidSemantic, Group, Value); + + if (ParseAsValueOnly) + DefaultSemantic = Sem; + else + SemanticsByGroup[Key] = Sem; +} + +void ContractOptions::parseContractGroups( + const std::vector &Groups, + const ContractOptions::DiagnoseGroupFunc &Diagnoser) { + + for (const auto &GroupStr : Groups) { + addUnparsedContractGroup(GroupStr, Diagnoser); + } +} + +bool ContractOptions::validateContractGroup( + llvm::StringRef GroupName, + const ContractOptions::DiagnoseGroupFunc &Diagnoser) { + using CGD = ContractGroupDiagnostic; + if (GroupName.empty()) { + Diagnoser(CGD::Empty, GroupName, ""); + return false; + } + + if (auto Pos = GroupName.find_first_not_of("abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "0123456789" + "_."); + Pos != StringRef::npos) { + Diagnoser(CGD::InvalidChar, GroupName, GroupName.substr(Pos, 1)); + return false; + } + + if (GroupName[0] == '.') { + Diagnoser(CGD::InvalidFirstChar, GroupName, "."); + return false; + } + if (GroupName.back() == '.') { + Diagnoser(CGD::InvalidLastChar, GroupName, "."); + return false; + } + // Diagnose empty subgroups. i.e. "a..b" + if (GroupName.find("..", 0) != StringRef::npos) { + Diagnoser(CGD::EmptySubGroup, GroupName, ".."); + return false; + } + return true; +} diff --git a/clang/lib/CodeGen/CodeGenFunction.cpp b/clang/lib/CodeGen/CodeGenFunction.cpp index 6b0de9c35cb179..c245d5561c2ec9 100644 --- a/clang/lib/CodeGen/CodeGenFunction.cpp +++ b/clang/lib/CodeGen/CodeGenFunction.cpp @@ -364,18 +364,17 @@ void CodeGenFunction::EmitHandleContractViolationCall(const ContractStmt &S, llvm_unreachable("unhandled ContractKind"); }(); - unsigned EvalSemantic = [&]() -> unsigned{ using EST = LangOptions::ContractEvaluationSemantic; - switch (getLangOpts().ContractEvalSemantic) { - case EST::Ignore: - assert(false && "unimplemented"); + switch (S.getSemantic(getLangOpts())) { case EST::Enforce: return 1; case EST::Observe: return 2; + case EST::Ignore: case EST::QuickEnforce: - assert(false && "unimplemented"); + case EST::Invalid: + llvm_unreachable("cannot generate a call for this semantic"); } llvm_unreachable("unhandled ContractEvaluationSemantic"); }(); @@ -415,6 +414,11 @@ void CodeGenFunction::EmitHandleContractViolationCall(const ContractStmt &S, void CodeGenFunction::EmitContractStmt(const ContractStmt &S) { // Emit the contract expression. const Expr *Expr = S.getCond(); + ContractEvaluationSemantic Semantic = S.getSemantic(getLangOpts()); + + // FIXME(EricWF): I think there's a lot more to do that simply this. + if (Semantic == LangOptions::ContractEvaluationSemantic::Ignore) + return; llvm::Value *ArgValue = EmitScalarExpr(Expr); llvm::BasicBlock *Begin = Builder.GetInsertBlock(); @@ -426,20 +430,27 @@ void CodeGenFunction::EmitContractStmt(const ContractStmt &S) { Builder.SetInsertPoint(Violation); - EmitHandleContractViolationCall(S, ContractViolationDetection::PredicateFailed); - - - llvm::CallInst *TrapCall = EmitTrapCall(llvm::Intrinsic::trap); - TrapCall->setDoesNotReturn(); - TrapCall->setDoesNotThrow(); - Builder.CreateUnreachable(); - Builder.ClearInsertionPoint(); + if (Semantic != ContractEvaluationSemantic::QuickEnforce) { + EmitHandleContractViolationCall( + S, ContractViolationDetection::PredicateFailed); + } + if (Semantic != ContractEvaluationSemantic::Observe) { + llvm::CallInst *TrapCall = EmitTrapCall(llvm::Intrinsic::trap); + TrapCall->setDoesNotReturn(); + TrapCall->setDoesNotThrow(); + Builder.CreateUnreachable(); + Builder.ClearInsertionPoint(); + } + Builder.CreateBr(End); Builder.SetInsertPoint(End); - // FIXME(EricWF): Maybe don't create the assume if the contract check is ignored. - llvm::Function *FnAssume = CGM.getIntrinsic(llvm::Intrinsic::assume); - Builder.CreateCall(FnAssume, ArgValue); + if (Semantic != ContractEvaluationSemantic::Observe) { + // FIXME(EricWF): Maybe don't create the assume if the contract check is + // ignored. + llvm::Function *FnAssume = CGM.getIntrinsic(llvm::Intrinsic::assume); + Builder.CreateCall(FnAssume, ArgValue); + } } diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp index 04148ce0739f57..61faa40eca8ee6 100644 --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -7311,6 +7311,11 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA, CmdArgs.push_back(Args.MakeArgString( Twine("-fcontract-evaluation-semantic=") + A->getValue())); } + std::vector ContractGroups = + Args.getAllArgValues(options::OPT_fclang_contract_groups_EQ); + CmdArgs.push_back(Args.MakeArgString(Twine("-fclang-contract-groups=") + + llvm::join(ContractGroups, ","))); + }(); diff --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp index 58694e5399d58c..8770954fa2d40e 100644 --- a/clang/lib/Frontend/CompilerInvocation.cpp +++ b/clang/lib/Frontend/CompilerInvocation.cpp @@ -3475,7 +3475,9 @@ void CompilerInvocationBase::GenerateLangArgs(const LangOptions &Opts, GenerateArg(Consumer, OPT_pic_is_pie); for (StringRef Sanitizer : serializeSanitizerKinds(Opts.Sanitize)) GenerateArg(Consumer, OPT_fsanitize_EQ, Sanitizer); - + for (StringRef ContractGroup : + Opts.ContractOptions.serializeContractGroupArgs()) + GenerateArg(Consumer, OPT_fclang_contract_groups_EQ, ContractGroup); return; } @@ -3747,6 +3749,10 @@ void CompilerInvocationBase::GenerateLangArgs(const LangOptions &Opts, if (!Opts.RandstructSeed.empty()) GenerateArg(Consumer, OPT_frandomize_layout_seed_EQ, Opts.RandstructSeed); + + for (StringRef ContractGroup : + Opts.ContractOptions.serializeContractGroupArgs()) + GenerateArg(Consumer, OPT_fclang_contract_groups_EQ, ContractGroup); } bool CompilerInvocation::ParseLangArgs(LangOptions &Opts, ArgList &Args, @@ -4376,6 +4382,17 @@ bool CompilerInvocation::ParseLangArgs(LangOptions &Opts, ArgList &Args, Diags.Report(diag::err_drv_hlsl_unsupported_target) << T.str(); } + auto EmitContractDiag = [&](ContractGroupDiagnostic CGD, StringRef GroupName, + StringRef InvalidChar = "") { + Diags.Report(diag::err_drv_contract_group_name_invalid) + << (int)CGD << GroupName << InvalidChar; + }; + + std::vector ContractGroupValues = + Args.getAllArgValues(options::OPT_fclang_contract_groups_EQ); + Opts.ContractOptions.parseContractGroups(ContractGroupValues, + EmitContractDiag); + return Diags.getNumErrors() == NumErrorsBefore; } diff --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp index 5e3e12e2381442..d90fac47df0d67 100644 --- a/clang/lib/Parse/ParseDeclCXX.cpp +++ b/clang/lib/Parse/ParseDeclCXX.cpp @@ -4355,9 +4355,7 @@ bool Parser::LateParseFunctionContractSpecifier(Declarator &DeclaratorInfo, Cach /*ConsumeFinalToken=*/true); ContractRange.setEnd(Toks.back().getLocation()); return true; - } -#pragma clang diagnostic ignored "-Wunused-variable" StmtResult Parser::ParseFunctionContractSpecifier(Declarator &DeclaratorInfo) { auto [CK, CKStr] = [&]() -> std::pair { @@ -4373,6 +4371,16 @@ StmtResult Parser::ParseFunctionContractSpecifier(Declarator &DeclaratorInfo) { SourceLocation KeywordLoc = Tok.getLocation(); ConsumeToken(); + ParsedAttributes CXX11Attrs(AttrFactory); + MaybeParseCXX11Attributes(CXX11Attrs); + + using ExpressionKind = + Sema::ExpressionEvaluationContextRecord::ExpressionKind; + EnterExpressionEvaluationContext EC( + Actions, Sema::ExpressionEvaluationContext::PotentiallyEvaluated, nullptr, + ExpressionKind::EK_ContractStmt); + Actions.currentEvaluationContext().InContractStatement = true; + if (Tok.isNot(tok::l_paren)) { Diag(Tok, diag::err_expected_lparen_after) << CKStr; SkipUntil({tok::equal, tok::l_brace, tok::arrow, tok::kw_try, tok::comma, @@ -4437,7 +4445,13 @@ StmtResult Parser::ParseFunctionContractSpecifier(Declarator &DeclaratorInfo) { T.consumeClose(); - return Actions.ActOnContractAssert(CK, KeywordLoc, Cond.get(), ResultNameStmt); + StmtResult ContractStmt = Actions.ActOnContractAssert( + CK, KeywordLoc, Cond.get(), ResultNameStmt, CXX11Attrs); + if (!ContractStmt.isUsable()) { + return StmtError(); + } + + return ContractStmt; } /// ParseTrailingReturnType - Parse a trailing return type on a new-style diff --git a/clang/lib/Parse/ParseStmt.cpp b/clang/lib/Parse/ParseStmt.cpp index 9f04d7dd16fd24..dd9ef1bd6d4595 100644 --- a/clang/lib/Parse/ParseStmt.cpp +++ b/clang/lib/Parse/ParseStmt.cpp @@ -344,6 +344,8 @@ StmtResult Parser::ParseStatementOrDeclarationAfterAttributes( break; case tok::kw_contract_assert: // C++20 contract-assert-statement + ProhibitAttributes(CXX11Attrs); + ProhibitAttributes(GNUAttrs); // The attributes go after the keyword Res = ParseContractAssertStatement(); SemiError = "contract_assert"; break; @@ -2439,10 +2441,15 @@ StmtResult Parser::ParseContractAssertStatement() { // Adjust the scope for the purposes of constification. EnterContractAssertScopeRAII EnterCAS(getCurScope()); + using ExpressionKind = + Sema::ExpressionEvaluationContextRecord::ExpressionKind; + EnterExpressionEvaluationContext EC( + Actions, Sema::ExpressionEvaluationContext::PotentiallyEvaluated, nullptr, + ExpressionKind::EK_ContractStmt); + Actions.currentEvaluationContext().InContractStatement = true; - // FIXME(EricWF): This seems really worg. - ParsedAttributes attrs(AttrFactory); - MaybeParseCXX11Attributes(attrs); + ParsedAttributes CXX11Attrs(AttrFactory); + MaybeParseCXX11Attributes(CXX11Attrs); if (Tok.isNot(tok::l_paren)) { Diag(Tok, diag::err_expected_lparen_after) << "contract_assert"; @@ -2473,9 +2480,9 @@ StmtResult Parser::ParseContractAssertStatement() { if (Cond.isInvalid()) return StmtError(); - return Actions.ActOnContractAssert(ContractKind::Assert, KeywordLoc, Cond.get()); - - + return Actions.ActOnContractAssert(ContractKind::Assert, KeywordLoc, + Cond.get(), + /*ResultNameDecl=*/nullptr, CXX11Attrs); } /// ParseReturnStatement diff --git a/clang/lib/Sema/SemaContract.cpp b/clang/lib/Sema/SemaContract.cpp index 674c66b749fc0d..193816273c4ccd 100644 --- a/clang/lib/Sema/SemaContract.cpp +++ b/clang/lib/Sema/SemaContract.cpp @@ -49,6 +49,17 @@ using namespace clang; using namespace sema; +class clang::SemaContractHelper { +public: + static SmallVector + buildAttributesWithDummyNode(Sema &S, ParsedAttributes &Attrs) { + ContractStmt Dummy(Stmt::EmptyShell(), ContractKind::Pre, false, 0); + SmallVector Result; + S.ProcessStmtAttributes(&Dummy, Attrs, Result); + return Result; + } +}; + ExprResult Sema::ActOnContractAssertCondition(Expr *Cond) { //assert(getCurScope()->isContractAssertScope() && "Incorrect scope for contract assert"); if (Cond->isTypeDependent()) @@ -61,16 +72,19 @@ ExprResult Sema::ActOnContractAssertCondition(Expr *Cond) { return ActOnFinishFullExpr(E.get(), /*DiscardedValue=*/false); } - StmtResult Sema::BuildContractStmt(ContractKind CK, SourceLocation KeywordLoc, - Expr *Cond, DeclStmt *ResultNameDecl) { + Expr *Cond, DeclStmt *ResultNameDecl, + ArrayRef Attrs) { assert((CK == ContractKind::Post || ResultNameDecl == nullptr) && "ResultNameDecl only allowed for postconditions"); + assert(currentEvaluationContext().InContractStatement && + "Wrong context for statement"); ExprResult E = ActOnContractAssertCondition(Cond); if (E.isInvalid()) return StmtError(); - return ContractStmt::Create(Context, CK, KeywordLoc, E.get(), ResultNameDecl); + return ContractStmt::Create(Context, CK, KeywordLoc, E.get(), ResultNameDecl, + Attrs); } static ResultNameDecl *extractResultName(DeclStmt *DS) { @@ -79,8 +93,10 @@ static ResultNameDecl *extractResultName(DeclStmt *DS) { return cast(D); } -StmtResult Sema::ActOnContractAssert(ContractKind CK, SourceLocation KeywordLoc, Expr *Cond, - DeclStmt *ResultNameDecl) { +StmtResult Sema::ActOnContractAssert(ContractKind CK, SourceLocation KeywordLoc, + Expr *Cond, DeclStmt *ResultNameDecl, + ParsedAttributes &CXX11Contracts) { + if (CK != ContractKind::Post && ResultNameDecl) { auto RND = extractResultName(ResultNameDecl); auto *II = RND->getDeclName().getAsIdentifierInfo(); @@ -90,7 +106,10 @@ StmtResult Sema::ActOnContractAssert(ContractKind CK, SourceLocation KeywordLoc, << II; return StmtError(); } - return BuildContractStmt(CK, KeywordLoc, Cond, ResultNameDecl); + + return BuildContractStmt( + CK, KeywordLoc, Cond, ResultNameDecl, + SemaContractHelper::buildAttributesWithDummyNode(*this, CXX11Contracts)); } /* FIXME(EricWF): Is this needed? diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index d926a3abadebdf..5d8a06010a1db4 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -3270,16 +3270,18 @@ static void diagnoseUncapturableValueReferenceOrBinding(Sema &S, /// [basic.contract.general] /// Within the predicate of a contract assertion, id-expressions referring to variables /// with automatic storage duration are const ([expr.prim.id.unqual]) -static bool shouldAdjustTypeForContractConstification(Sema &S, const ValueDecl *VD) { +static ContractConstification getContractConstification(Sema &S, + const ValueDecl *VD) { - if (!S.getCurScope()->isContractAssertScope()) - return false; + if (!S.getCurScope()->isContractAssertScope() && !S.isContractStmtContext()) + return CC_None; if (auto Var = dyn_cast(VD)) { - return Var->isLocalVarDeclOrParm(); + if (Var->isLocalVarDeclOrParm()) + return CC_ApplyConst; } - return false; + return CC_None; } /// Complete semantic analysis for a reference to the given declaration. @@ -3344,6 +3346,8 @@ ExprResult Sema::BuildDeclarationNameExpr( // is expanded by some outer '...' in the context of the use. type = type.getNonPackExpansionType(); + ContractConstification CC = CC_None; + switch (D->getKind()) { // Ignore all the non-ValueDecl kinds. #define ABSTRACT_DECL(kind) @@ -3432,12 +3436,17 @@ ExprResult Sema::BuildDeclarationNameExpr( // potentially-evaluated contexts? Since the variable isn't actually // captured in an unevaluated context, it seems that the answer is no. if (!isUnevaluatedContext()) { + CC = getContractConstification(*this, cast(VD)); + if (CC == CC_ApplyConst) + type = type.withConst(); + QualType CapturedType = getCapturedDeclRefType(cast(VD), Loc); if (!CapturedType.isNull()) type = CapturedType; else { - if (shouldAdjustTypeForContractConstification(*this, VD)) - type = type.withConst(); + CC = getContractConstification(*this, cast(VD)); + if (CC == CC_ApplyConst) + type = type.getNonReferenceType().withConst(); } } @@ -3538,6 +3547,7 @@ ExprResult Sema::BuildDeclarationNameExpr( auto *E = BuildDeclRefExpr(VD, type, valueKind, NameInfo, &SS, FoundD, /*FIXME: TemplateKWLoc*/ SourceLocation(), TemplateArgs); + // Clang AST consumers assume a DeclRefExpr refers to a valid decl. We // wrap a DeclRefExpr referring to an invalid decl with a dependent-type // RecoveryExpr to avoid follow-up semantic analysis (thus prevent bogus diff --git a/clang/lib/Sema/SemaStmtAttr.cpp b/clang/lib/Sema/SemaStmtAttr.cpp index 6f538ed55cb72e..4ceb73218a342a 100644 --- a/clang/lib/Sema/SemaStmtAttr.cpp +++ b/clang/lib/Sema/SemaStmtAttr.cpp @@ -12,6 +12,7 @@ #include "clang/AST/ASTContext.h" #include "clang/AST/EvaluatedExprVisitor.h" +#include "clang/Basic/ContractOptions.h" #include "clang/Basic/SourceManager.h" #include "clang/Basic/TargetInfo.h" #include "clang/Sema/DelayedDiagnostic.h" @@ -297,6 +298,46 @@ static Attr *handleNoInlineAttr(Sema &S, Stmt *St, const ParsedAttr &A, return ::new (S.Context) NoInlineAttr(S.Context, A); } +static void CheckForDuplicateContractGroupAttrs(Sema &S, + ArrayRef Attrs) { + auto FindFunc = [](const Attr *A) { return isa(A); }; + const auto FirstIter = std::find_if(Attrs.begin(), Attrs.end(), FindFunc); + if (FirstIter == Attrs.end()) + return; + + const auto *NextFound = FirstIter; + while (NextFound != Attrs.end() && + (NextFound = std::find_if(NextFound + 1, Attrs.end(), FindFunc)) != + Attrs.end()) { + S.Diag((*NextFound)->getLocation(), diag::err_contract_group_redeclared) + << *FirstIter; + S.Diag((*FirstIter)->getLocation(), diag::note_previous_attribute); + } +} + +static Attr *handleContractGroupAttr(Sema &S, Stmt *st, const ParsedAttr &A, + SourceRange Range) { + + assert(A.getNumArgs() == 1); + + StringRef GroupName; + assert(A.getArgAsExpr(0)); + if (!S.checkStringLiteralArgumentAttr(A, 0, GroupName, nullptr)) + return nullptr; + + auto EmitDiag = [&](ContractGroupDiagnostic CDK, StringRef Group, + StringRef InvalidRange = "") { + S.Diag(Range.getBegin(), diag::err_contract_group_attribute_invalid_group) + << (int)CDK << Group << InvalidRange + << A.getArgAsExpr(0)->getSourceRange(); + }; + + if (!ContractOptions::validateContractGroup(GroupName, EmitDiag)) + return nullptr; + + return ::new (S.Context) ContractGroupAttr(S.Context, A, GroupName); +} + static Attr *handleAlwaysInlineAttr(Sema &S, Stmt *St, const ParsedAttr &A, SourceRange Range) { AlwaysInlineAttr AIA(S.Context, A); @@ -636,6 +677,8 @@ static Attr *ProcessStmtAttribute(Sema &S, Stmt *St, const ParsedAttr &A, return handleCodeAlignAttr(S, St, A); case ParsedAttr::AT_MSConstexpr: return handleMSConstexprAttr(S, St, A, Range); + case ParsedAttr::AT_ContractGroup: + return handleContractGroupAttr(S, St, A, Range); default: // N.B., ClangAttrEmitter.cpp emits a diagnostic helper that ensures a // declaration attribute is not written on a statement, but this code is @@ -655,6 +698,7 @@ void Sema::ProcessStmtAttributes(Stmt *S, const ParsedAttributes &InAttrs, CheckForIncompatibleAttributes(*this, OutAttrs); CheckForDuplicateLoopAttrs(*this, OutAttrs); + CheckForDuplicateContractGroupAttrs(*this, OutAttrs); } bool Sema::CheckRebuiltStmtAttributes(ArrayRef Attrs) { diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h index 3fc95ccddd69b8..85528384c807c4 100644 --- a/clang/lib/Sema/TreeTransform.h +++ b/clang/lib/Sema/TreeTransform.h @@ -1607,8 +1607,13 @@ class TreeTransform { // // StmtResult RebuildContractStmt(ContractKind K, SourceLocation KeywordLoc, - Expr *Cond) { - return getSema().BuildContractStmt(K, KeywordLoc, Cond); + Expr *Cond, DeclStmt *ResultName, + ArrayRef Attrs) { + const bool LastVal = + getSema().currentEvaluationContext().InContractStatement; + getSema().currentEvaluationContext().InContractStatement = true; + return getSema().BuildContractStmt(K, KeywordLoc, Cond, ResultName, Attrs); + getSema().currentEvaluationContext().InContractStatement = LastVal; } /// Build a new Objective-C \@try statement. @@ -8535,17 +8540,42 @@ TreeTransform::TransformCoyieldExpr(CoyieldExpr *E) { // C++ Contract Statements +struct ValueGuard { + ValueGuard(bool *Value, bool NewValue) : Value(Value), OldVal(*Value) { + *Value = NewValue; + } + ~ValueGuard() { *Value = OldVal; } + + bool *Value; + bool OldVal; +}; + template StmtResult TreeTransform::TransformContractStmt(ContractStmt *S) { + ValueGuard InContractGuard( + &SemaRef.currentEvaluationContext().InContractStatement, true); + + StmtResult NewResultName; + if (S->hasResultNameDecl()) { + NewResultName = getDerived().TransformStmt(S->getResultNameDeclStmt()); + if (NewResultName.isInvalid()) + return StmtError(); + } + ExprResult OperandResult = getDerived().TransformExpr(S->getCond()); if (OperandResult.isInvalid()) return StmtError(); + SmallVector NewAttrs; + for (auto *A : S->getAttrs()) + NewAttrs.push_back(getDerived().TransformAttr(A)); + // Always rebuild; we don't know if this needs to be injected into a new // context or if the promise type has changed. return getDerived().RebuildContractStmt( - S->getContractKind(), S->getKeywordLoc(), OperandResult.get()); + S->getContractKind(), S->getKeywordLoc(), OperandResult.get(), + cast_or_null(NewResultName.get()), NewAttrs); } // Objective-C Statements. diff --git a/clang/lib/Serialization/ASTReaderStmt.cpp b/clang/lib/Serialization/ASTReaderStmt.cpp index b6ff3bc010c2ce..01b547d1c680d2 100644 --- a/clang/lib/Serialization/ASTReaderStmt.cpp +++ b/clang/lib/Serialization/ASTReaderStmt.cpp @@ -505,11 +505,17 @@ void ASTStmtReader::VisitDependentCoawaitExpr(DependentCoawaitExpr *E) { void ASTStmtReader::VisitContractStmt(ContractStmt *S) { VisitStmt(S); Record.skipInts(1); + unsigned NumAttrs = Record.readInt(); S->KeywordLoc = Record.readSourceLocation(); S->setCondition(Record.readExpr()); if (S->hasResultNameDecl()) S->setResultNameDecl(cast(Record.readStmt())); + AttrVec Attrs; + Record.readAttributes(Attrs); + assert(Attrs.size() == NumAttrs); + ((void)NumAttrs); + std::copy(Attrs.begin(), Attrs.end(), S->getAttrArrayPtr()); } void ASTStmtReader::VisitCapturedStmt(CapturedStmt *S) { @@ -4244,10 +4250,12 @@ Stmt *ASTReader::ReadStmtFromStream(ModuleFile &F) { case STMT_CXX_CONTRACT: { BitsUnpacker ContractBits(Record[ASTStmtReader::NumStmtFields]); - ContractKind CK = static_cast(ContractBits.getNextBits(2)); bool HasResultName = ContractBits.getNextBit(); - S = ContractStmt::CreateEmpty(Context, CK, HasResultName); + + unsigned NumAttrs = Record[ASTStmtReader::NumStmtFields + 1]; + + S = ContractStmt::CreateEmpty(Context, CK, HasResultName, NumAttrs); break; } diff --git a/clang/lib/Serialization/ASTWriterStmt.cpp b/clang/lib/Serialization/ASTWriterStmt.cpp index 423ba46f3213cb..6754730a2e643b 100644 --- a/clang/lib/Serialization/ASTWriterStmt.cpp +++ b/clang/lib/Serialization/ASTWriterStmt.cpp @@ -472,11 +472,13 @@ void ASTStmtWriter::VisitContractStmt(ContractStmt *S) { CurrentPackingBits.addBits(S->ContractAssertBits.ContractKind, /*BitsWidth=*/2); CurrentPackingBits.addBit(S->ContractAssertBits.HasResultName); + Record.push_back(S->getAttrs().size()); Record.AddSourceLocation(S->getKeywordLoc()); Record.AddStmt(S->getCond()); if (S->hasResultNameDecl()) Record.AddStmt(S->getResultNameDeclStmt()); + Record.AddAttributes(S->getAttrs()); Code = serialization::STMT_CXX_CONTRACT; } diff --git a/clang/test/SemaCXX/contract-group-attr.cpp b/clang/test/SemaCXX/contract-group-attr.cpp new file mode 100644 index 00000000000000..736a3a86a9f692 --- /dev/null +++ b/clang/test/SemaCXX/contract-group-attr.cpp @@ -0,0 +1,18 @@ +// RUN: %clang_cc1 -std=c++26 -fsyntax-only -verify=expected %s -fcontracts \ +// -fclang-contract-groups=-std.foo,std.foo.baz,std.bar + + + + +void test_attribute(int x) pre [[clang::contract_group("foo")]] (x != 0) { + contract_assert [[clang::contract_group("foo")]] (x != 0); // OK + + contract_assert [[clang::contract_group("")]] (true); // expected-error {{clang::contract_group attribute argument "" cannot be empty}} + contract_assert [[clang::contract_group("-bar")]] (true); // expected-error {{clang::contract_group attribute argument "-bar" cannot contain '-'}} + contract_assert [[clang::contract_group("foo*bar")]] (true); + contract_assert [[clang::contract_group("foo-bar")]] (true); // OK + + contract_assert [[clang::contract_group("f")]] // expected-note {{previous attribute is here}} + [[clang::contract_group("f")]] (x != 0); // expected-error {{clang::contract_group appears more than once}} +} + diff --git a/clang/test/SemaCXX/contracts-expr.cpp b/clang/test/SemaCXX/contracts-expr.cpp index bf9e936338aeb8..5d7a0a2b62a90f 100644 --- a/clang/test/SemaCXX/contracts-expr.cpp +++ b/clang/test/SemaCXX/contracts-expr.cpp @@ -12,4 +12,38 @@ constexpr int do_test() { return 42; } +int foo() { + int x = 42; + contract_assert(++x); +} + +template +constexpr inline bool is_same_v = false; + +template +constexpr inline bool is_same_v = true; + + +template +constexpr void assert_same() { + static_assert(is_same_v); +} + + +template +int foo(T v) { + int v2 = v; + const int v3 = v; + contract_assert(( + ++v, + ++v2, + ++v3 + )); + contract_assert((assert_same(), true)); + contract_assert((assert_same(), true)); + contract_assert((assert_same(), true)); + +} +template int foo(int); + constexpr int test = do_test(); diff --git a/clang/test/SemaCXX/contracts.cpp b/clang/test/SemaCXX/contracts.cpp index 7cb8865dcd069a..42b5ded0040d91 100644 --- a/clang/test/SemaCXX/contracts.cpp +++ b/clang/test/SemaCXX/contracts.cpp @@ -4,7 +4,10 @@ void test_pre_parse(int x) pre(x != 0); void test_post_parse(int x) post(x != 0); int test_dup_names(int x) // expected-note {{previous declaration is here}} - post(x : x != 0); // expected-error {{declaration of result name 'x' shadows parameter}} + post(x : // expected-error {{declaration of result name 'x' shadows parameter}} + x != 0); // expected-error {{reference to 'x' is ambiguous}} + // expected-note@-2 {{candidate found by name lookup is 'x'}} + // expected-note@-4 {{candidate found by name lookup is 'x'}} auto test_trailing_return() -> int post(r : r != 0); @@ -70,3 +73,15 @@ int test_result_name_scope() post(r : r != 0) { ((void)r); // expected-error {{use of undeclared identifier 'r'}} return 42; } + +void test_attribute(int x) pre [[clang::contract_group("foo")]] (x != 0) { + contract_assert [[clang::contract_group("foo")]] (x != 0); // OK + + contract_assert [[clang::contract_group("")]] (true); // expected-error {{clang::contract_group attribute argument "" cannot be empty}} + contract_assert [[clang::contract_group("-bar")]] (true); // expected-error {{clang::contract_group attribute argument "-bar" cannot contain '-'}} + contract_assert [[clang::contract_group("foo*bar")]] (true); + contract_assert [[clang::contract_group("foo-bar")]] (true); // OK + + contract_assert [[clang::contract_group("f")]] // expected-note {{previous attribute is here}} + [[clang::contract_group("f")]] (x != 0); // expected-error {{clang::contract_group appears more than once}} +} diff --git a/clang/test/SemaCXX/ericwf-crash.cpp b/clang/test/SemaCXX/ericwf-crash.cpp new file mode 100644 index 00000000000000..004b1cf0692ae6 --- /dev/null +++ b/clang/test/SemaCXX/ericwf-crash.cpp @@ -0,0 +1,18 @@ +// RUN: %clang_cc1 -std=c++26 %s -fcontracts -fcontract-evaluation-semantic=observe + + +int foo(int x) pre(x != 0) { + return x; +} + +template +void foo2(T v) { + contract_assert [[clang::contract_group("foo")]] (v + 1 != 0) ; +} + +int main() { + + foo2(0); +} + + From 249f571d0a94e6cc597b33a1d3d1e8e9a6e2d865 Mon Sep 17 00:00:00 2001 From: Eric Fiselier Date: Sat, 6 Jul 2024 01:44:17 -0500 Subject: [PATCH 8/9] whitespace changes --- clang/lib/CodeGen/CodeGenFunction.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/clang/lib/CodeGen/CodeGenFunction.cpp b/clang/lib/CodeGen/CodeGenFunction.cpp index c245d5561c2ec9..190ebca7536223 100644 --- a/clang/lib/CodeGen/CodeGenFunction.cpp +++ b/clang/lib/CodeGen/CodeGenFunction.cpp @@ -434,6 +434,7 @@ void CodeGenFunction::EmitContractStmt(const ContractStmt &S) { EmitHandleContractViolationCall( S, ContractViolationDetection::PredicateFailed); } + if (Semantic != ContractEvaluationSemantic::Observe) { llvm::CallInst *TrapCall = EmitTrapCall(llvm::Intrinsic::trap); TrapCall->setDoesNotReturn(); @@ -441,8 +442,8 @@ void CodeGenFunction::EmitContractStmt(const ContractStmt &S) { Builder.CreateUnreachable(); Builder.ClearInsertionPoint(); } - Builder.CreateBr(End); + Builder.CreateBr(End); Builder.SetInsertPoint(End); if (Semantic != ContractEvaluationSemantic::Observe) { From 4eee4d5abc40bcc0117985b29527a31b9441048d Mon Sep 17 00:00:00 2001 From: Eric Fiselier Date: Sat, 6 Jul 2024 13:53:23 -0500 Subject: [PATCH 9/9] change flags, but break clang --- clang/include/clang/Basic/DiagnosticDriverKinds.td | 1 - clang/include/clang/Driver/Options.td | 2 +- clang/lib/Driver/ToolChains/Clang.cpp | 4 ++-- clang/lib/Frontend/CompilerInvocation.cpp | 8 +++++--- libcxx/include/contracts | 6 +++--- libcxx/src/contracts.cpp | 1 - 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/clang/include/clang/Basic/DiagnosticDriverKinds.td b/clang/include/clang/Basic/DiagnosticDriverKinds.td index d6880b6bf129f5..2dbb42d82b3d6a 100644 --- a/clang/include/clang/Basic/DiagnosticDriverKinds.td +++ b/clang/include/clang/Basic/DiagnosticDriverKinds.td @@ -823,5 +823,4 @@ def err_drv_contract_group_name_invalid : Error< "|cannot be empty" "|\"%2\" is an invalid value" "}0">; - } diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td index 66287e2488ea7c..cb1e8e1eeea216 100644 --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -1677,7 +1677,7 @@ def fcontract_evaluation_semantic_EQ : Joined<["-"], "fcontract-evaluation-seman NormalizedValues<["Ignore", "Enforce", "Observe", "QuickEnforce"]>, MarshallingInfoEnum, "Enforce">; -def fclang_contract_groups_EQ : CommaJoined<["-"], "fclang-contract-groups=">, +def fcontract_group_evaluation_semantic_EQ : CommaJoined<["-"], "fcontract-group-evaluation-semantic=">, Visibility<[ClangOption, CC1Option]>, HelpText<"Clang extension. Enable or disable contracts by group. The argument is a comma-separated " "sequence of one or more group names, each prefixed by '+' or '-'.">; diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp index 61faa40eca8ee6..b205d174450809 100644 --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -7312,8 +7312,8 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA, Twine("-fcontract-evaluation-semantic=") + A->getValue())); } std::vector ContractGroups = - Args.getAllArgValues(options::OPT_fclang_contract_groups_EQ); - CmdArgs.push_back(Args.MakeArgString(Twine("-fclang-contract-groups=") + + Args.getAllArgValues(options::OPT_fcontract_group_evaluation_semantic_EQ); + CmdArgs.push_back(Args.MakeArgString(Twine("-fcontract-group-evaluation-semantic=") + llvm::join(ContractGroups, ","))); }(); diff --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp index 8770954fa2d40e..a062f4707680fa 100644 --- a/clang/lib/Frontend/CompilerInvocation.cpp +++ b/clang/lib/Frontend/CompilerInvocation.cpp @@ -3475,9 +3475,11 @@ void CompilerInvocationBase::GenerateLangArgs(const LangOptions &Opts, GenerateArg(Consumer, OPT_pic_is_pie); for (StringRef Sanitizer : serializeSanitizerKinds(Opts.Sanitize)) GenerateArg(Consumer, OPT_fsanitize_EQ, Sanitizer); + + for (StringRef ContractGroup : Opts.ContractOptions.serializeContractGroupArgs()) - GenerateArg(Consumer, OPT_fclang_contract_groups_EQ, ContractGroup); + GenerateArg(Consumer, OPT_fcontract_group_evaluation_semantic_EQ, ContractGroup); return; } @@ -3752,7 +3754,7 @@ void CompilerInvocationBase::GenerateLangArgs(const LangOptions &Opts, for (StringRef ContractGroup : Opts.ContractOptions.serializeContractGroupArgs()) - GenerateArg(Consumer, OPT_fclang_contract_groups_EQ, ContractGroup); + GenerateArg(Consumer, OPT_fcontract_group_evaluation_semantic_EQ, ContractGroup); } bool CompilerInvocation::ParseLangArgs(LangOptions &Opts, ArgList &Args, @@ -4389,7 +4391,7 @@ bool CompilerInvocation::ParseLangArgs(LangOptions &Opts, ArgList &Args, }; std::vector ContractGroupValues = - Args.getAllArgValues(options::OPT_fclang_contract_groups_EQ); + Args.getAllArgValues(options::OPT_fcontract_group_evaluation_semantic_EQ); Opts.ContractOptions.parseContractGroups(ContractGroupValues, EmitContractDiag); diff --git a/libcxx/include/contracts b/libcxx/include/contracts index 58e8c3bcdbd29b..5e9f1b466d99ff 100644 --- a/libcxx/include/contracts +++ b/libcxx/include/contracts @@ -56,7 +56,7 @@ public: unsigned __line_; }; - contract_violation(_Info __info) noexcept + explicit contract_violation(_Info __info) noexcept : __info_(__info) {} @@ -70,13 +70,13 @@ _LIBCPP_EXPORTED_FROM_ABI void invoke_default_contract_violation_handler(const c } // namespace std::contracts -_LIBCPP_WEAK void handle_contract_violation(const std::contracts::contract_violation&) noexcept; +_LIBCPP_WEAK void handle_contract_violation(const std::contracts::contract_violation&); extern "C" { _LIBCPP_EXPORTED_FROM_ABI void __handle_contract_violation(unsigned kind, unsigned eval_semantic, -unsigned detection_mode, const char* comment, const char* file, unsigned line); + unsigned detection_mode, const char* comment, const char* file, unsigned line); } diff --git a/libcxx/src/contracts.cpp b/libcxx/src/contracts.cpp index 99130b44963426..d3888ee304e32c 100644 --- a/libcxx/src/contracts.cpp +++ b/libcxx/src/contracts.cpp @@ -9,7 +9,6 @@ namespace std::contracts { void invoke_default_contract_violation_handler(const contract_violation& violation) noexcept { - ::handle_contract_violation(violation); }