Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
b58968c
verify: Declare the object a class invariant is instantiated for.
SwayamInSync Oct 24, 2026
23288e0
verify: Call a member operator on its first operand, return a member'…
SwayamInSync Oct 24, 2026
c0c0688
verify: Let a callee lent a local object have local objects of its own.
SwayamInSync Oct 24, 2026
8fc95d0
test: Check member operators and the invariants of objects passed by …
SwayamInSync Oct 24, 2026
3b5d6a1
test: Check a null pointer argument and an extent through a reference…
SwayamInSync Oct 24, 2026
dcb9c9f
test: Expect a record's invariant to hold through a reference.
SwayamInSync Oct 24, 2026
2e04499
verify: Declare the check that rejects using the reference a member r…
SwayamInSync Oct 24, 2026
19d1182
verify: Reject a copy or assignment from the reference a member retur…
SwayamInSync Oct 24, 2026
1ff05af
test: Check copying and assigning the reference a member operator ret…
SwayamInSync Oct 24, 2026
1b7dd64
docs: Describe member operators and the class invariants of objects p…
SwayamInSync Oct 24, 2026
010fc0c
docs: Note member operators and class invariants at reference and poi…
SwayamInSync Oct 24, 2026
4380589
docs: Design member operators, own-object returns, and type invariant…
SwayamInSync Oct 24, 2026
1aefeef
docs: Describe how member operators and the invariants of parameters …
SwayamInSync Oct 24, 2026
0632d6c
verify: Give cppverify::result, cppverify::old, and quantifiers the d…
SwayamInSync Oct 24, 2026
6e3a800
verify: Declare contract instantiation for template specializations a…
SwayamInSync Oct 24, 2026
6af1d1e
verify: Check a decreases component and a quantifier's bounds and bod…
SwayamInSync Oct 24, 2026
c1382bc
verify: Keep a function template's contract and check its clauses thr…
SwayamInSync Oct 24, 2026
7c37848
verify: Transform contract clauses with the parser's checks, loop con…
SwayamInSync Oct 24, 2026
eb77fe2
verify: Substitute template arguments into a function contract.
SwayamInSync Oct 24, 2026
36c3cf8
verify: Instantiate a specialization's contract with its definition, …
SwayamInSync Oct 24, 2026
69eb19c
verify: Instantiate a specialization's contract where it is used.
SwayamInSync Oct 24, 2026
96dbdeb
verify: Record where each template specialization is instantiated.
SwayamInSync Oct 24, 2026
c0aee24
verify: Verify each instantiated specialization of a function templat…
SwayamInSync Oct 24, 2026
246d2b4
verify: Note where a specialization is instantiated after its errors …
SwayamInSync Oct 24, 2026
cca2982
test: Check function templates verified in each specialization.
SwayamInSync Oct 24, 2026
694cb56
test: Check that a function template's contract is checked where it i…
SwayamInSync Oct 24, 2026
0a34abb
test: Expect function template contracts kept, and result and old of …
SwayamInSync Oct 24, 2026
9f4a732
test: Expect a warning for a contracted template that nothing instant…
SwayamInSync Oct 24, 2026
fa8f944
test: Expect the warning for an uninstantiated contracted template.
SwayamInSync Oct 24, 2026
83bbad9
docs: Describe function templates verified in each specialization.
SwayamInSync Oct 24, 2026
2fd37fe
docs: List function templates as supported and class template members…
SwayamInSync Oct 24, 2026
af8645d
docs: Note function templates in the release notes.
SwayamInSync Oct 24, 2026
e39b949
docs: Design function templates verified per instantiation.
SwayamInSync Oct 24, 2026
4551563
docs: Describe contract instantiation and the converter's specializat…
SwayamInSync Oct 24, 2026
ac5f112
test: Check a constexpr template lifted as a spec in each specializat…
SwayamInSync Oct 24, 2026
4c52295
docs: Note that a constexpr template in a contract is a spec in each …
SwayamInSync Oct 24, 2026
22c3329
verify: Give each instantiated quantifier and choice a scope of its o…
SwayamInSync Oct 24, 2026
d890301
verify: Keep a check-by block in the scope of its instantiated quanti…
SwayamInSync Oct 24, 2026
d27059d
verify: Drop the this scope and ghost mark a specialization's context…
SwayamInSync Oct 24, 2026
24a3d15
verify: Declare the naming of parameters, a pack's elements apart.
SwayamInSync Oct 24, 2026
fce5a9f
verify: Name the parameters a pack expands to as C++26 indexes them.
SwayamInSync Oct 24, 2026
4138ee0
test: Check a contract on a definition after a declaration, and varia…
SwayamInSync Oct 24, 2026
3693656
test: Check that instantiating a contract leaves a caller its tempora…
SwayamInSync Oct 24, 2026
5ee709d
docs: Describe binder scopes and pack element names in function templ…
SwayamInSync Oct 24, 2026
c91908e
docs: Describe variadic templates.
SwayamInSync Oct 24, 2026
1722556
verify: Map a contract's parameters through the pattern's, which stan…
SwayamInSync Oct 24, 2026
f653fb6
docs: Describe how a contract's parameters are mapped in a specializa…
SwayamInSync Oct 24, 2026
47516c5
test: Pass the temporary by value, which is one before the call is re…
SwayamInSync Oct 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 20 additions & 5 deletions clang/include/clang/AST/ExprContract.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,19 @@ namespace clang {
// Forward declaration for serialization friend access.
class ASTStmtReader;

/// The dependence of a quantifier or a choice: its operands', with its own
/// type fixed. In a template, `forall(k, 0, n, p[k] > 0)` over a `T *p` is
/// value-dependent, though it is a bool.
inline ExprDependence binderExprDependence(const Expr *Lo, const Expr *Hi,
const Expr *Body) {
ExprDependence D = Body ? Body->getDependence() : ExprDependence::None;
if (Lo)
D |= Lo->getDependence();
if (Hi)
D |= Hi->getDependence();
return turnTypeToValueDependence(D);
}

/// ForallExpr - Represents a universal quantifier:
/// forall(binder, lo, hi, body)
/// means: for all binder in [lo, hi), body holds; forall(binder, body)
Expand All @@ -46,7 +59,7 @@ class ForallExpr : public Expr {
SubExprs[LO] = Lo;
SubExprs[HI] = Hi;
SubExprs[BODY] = Body;
setDependence(ExprDependence::None);
setDependence(binderExprDependence(Lo, Hi, Body));
}

explicit ForallExpr(EmptyShell Empty) : Expr(ForallExprClass, Empty) {}
Expand Down Expand Up @@ -101,7 +114,7 @@ class ExistsExpr : public Expr {
SubExprs[LO] = Lo;
SubExprs[HI] = Hi;
SubExprs[BODY] = Body;
setDependence(ExprDependence::None);
setDependence(binderExprDependence(Lo, Hi, Body));
}

explicit ExistsExpr(EmptyShell Empty) : Expr(ExistsExprClass, Empty) {}
Expand Down Expand Up @@ -156,7 +169,7 @@ class ContractChooseExpr : public Expr {
SubExprs[LO] = Lo;
SubExprs[HI] = Hi;
SubExprs[BODY] = Body;
setDependence(ExprDependence::None);
setDependence(binderExprDependence(Lo, Hi, Body));
}

explicit ContractChooseExpr(EmptyShell Empty) : Expr(ContractChooseExprClass, Empty) {}
Expand Down Expand Up @@ -202,7 +215,7 @@ class OldExpr : public Expr {
: Expr(OldExprClass, Inner->getType(), VK_PRValue, OK_Ordinary),
OldLoc(OldLoc), LParenLoc(LParenLoc), RParenLoc(RParenLoc),
Inner(Inner) {
setDependence(ExprDependence::None);
setDependence(Inner->getDependence());
}

explicit OldExpr(EmptyShell Empty) : Expr(OldExprClass, Empty) {}
Expand Down Expand Up @@ -237,7 +250,9 @@ class ResultExpr : public Expr {
QualType ReturnType)
: Expr(ResultExprClass, ReturnType, VK_PRValue, OK_Ordinary),
ResultLoc(ResultLoc), EndLoc(EndLoc) {
setDependence(ExprDependence::None);
// In a template, the return type may depend on its parameters.
setDependence(
toExprDependenceForImpliedType(ReturnType->getDependence()));
}

explicit ResultExpr(EmptyShell Empty) : Expr(ResultExprClass, Empty) {}
Expand Down
25 changes: 25 additions & 0 deletions clang/include/clang/Sema/Sema.h
Original file line number Diff line number Diff line change
Expand Up @@ -10394,6 +10394,17 @@ class Sema final : public SemaBase {
ExprResult ActOnContractRange(Expr *Base, Expr *Lower, SourceLocation ColonLoc,
Expr *Length, SourceLocation RBLoc);

/// A component of a decreases clause, which must be an integer.
ExprResult ActOnContractMeasure(ExprResult E);

/// forall, exists, or choose (\p Kind) over \p Binder: its bounds must be
/// integers and its body a condition.
ExprResult BuildContractBinderExpr(Stmt::StmtClass Kind,
SourceLocation KeywordLoc,
SourceLocation LParenLoc,
SourceLocation RParenLoc, VarDecl *Binder,
Expr *Lo, Expr *Hi, Expr *Body);

/// Rewrite unqualified field names to this->field for type_invariant(expr).
ExprResult ActOnTypeInvariantExpr(ExprResult E, CXXRecordDecl *Record);

Expand Down Expand Up @@ -14336,6 +14347,20 @@ class Sema final : public SemaBase {
void InstantiateExceptionSpec(SourceLocation PointOfInstantiation,
FunctionDecl *Function);

/// Instantiate the contract of a function template specialization, or of
/// a member of a class template specialization, from its pattern's, when
/// it is used or defined and has none yet. (Contracts are instantiated
/// lazily, as exception specifications are: an unused member's contract is
/// never instantiated.)
void InstantiateFunctionContract(SourceLocation PointOfInstantiation,
FunctionDecl *Function);

/// The contract \p From of a template, with \p TemplateArgs substituted,
/// in \p To. Returns true on error.
bool SubstFunctionContract(const FunctionContractInfo &From,
FunctionContractInfo &To,
const MultiLevelTemplateArgumentList &TemplateArgs);

/// Instantiate (or find existing instantiation of) a function template with a
/// given set of template arguments.
///
Expand Down
76 changes: 24 additions & 52 deletions clang/lib/Parse/ParseCppVerify.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -534,12 +534,11 @@ void Parser::ParseFunctionContractClauseList(FunctionContractClauses &Clauses) {
// expressions. Parse each with ParseAssignmentExpression so the comma is
// a tuple separator, not the C comma operator.
do {
ExprResult E = finishCppVerifyExpression(ParseAssignmentExpression());
ExprResult E = ParseAssignmentExpression();
if (E.isInvalid())
break;
if (!E.get()->isTypeDependent() && !E.get()->getType()->isIntegerType())
Diag(E.get()->getExprLoc(), diag::err_contract_decreases_not_int);
else
E = finishCppVerifyExpression(Actions.ActOnContractMeasure(E));
if (E.isUsable())
I.Decreases.push_back(E.get());
if (Tok.is(tok::comma))
ConsumeToken();
Expand Down Expand Up @@ -673,17 +672,14 @@ void Parser::attachFunctionContract(Decl *Result,
I.MayDiverge.isInvalid() && I.Inductive.isInvalid() &&
I.BehaviorChecks.empty() && !I.IsSpec && !I.IsProof))
return;
// Templates are not verified yet: their clauses are ignored, as a member
// function's are.
if (FD->isTemplated() && !I.IsSpec && !I.IsProof) {
// A member of a class template is named by its class.
const auto *Method = dyn_cast<CXXMethodDecl>(FD);
if (Method && Method->getParent()->isTemplated())
Diag(FD->getLocation(), diag::warn_contract_template_unsupported)
<< 1 << Method->getParent();
else
Diag(FD->getLocation(), diag::warn_contract_template_unsupported)
<< 0 << FD;
// A function template's clauses are instantiated with each of its
// specializations. Members of class templates are not verified yet: their
// clauses are ignored.
if (const auto *Method = dyn_cast<CXXMethodDecl>(FD);
Method && Method->getParent()->isTemplated() && !I.IsSpec &&
!I.IsProof) {
Diag(FD->getLocation(), diag::warn_contract_template_unsupported)
<< 1 << Method->getParent();
return;
}
FunctionContractInfo &FCI = Ctx.getOrCreateFunctionContract(FD);
Expand Down Expand Up @@ -835,17 +831,13 @@ void Parser::ParseLoopContractClauses(LoopContractInfo &Contract) {
// Parse each component with ParseAssignmentExpression so the comma is a
// tuple separator, not the C comma operator.
while (true) {
ExprResult D = finishCppVerifyExpression(ParseAssignmentExpression());
ExprResult D = ParseAssignmentExpression();
if (D.isUsable())
D = finishCppVerifyExpression(Actions.ActOnContractMeasure(D));
if (D.isInvalid()) {
SkipUntil(tok::r_paren, StopAtSemi);
return;
}
if (!D.get()->isTypeDependent() &&
!D.get()->getType()->isIntegerType()) {
Diag(D.get()->getExprLoc(), diag::err_contract_decreases_not_int);
SkipUntil(tok::r_paren, StopAtSemi);
return;
}
Contract.Decreases.push_back(D.get());
if (Tok.isNot(tok::comma))
break;
Expand All @@ -867,10 +859,11 @@ void Parser::attachLoopContract(StmtResult &Loop, LoopContractInfo &Contract) {
(Contract.Invariants.empty() && Contract.Decreases.empty() &&
Contract.MayDiverge.isInvalid() && Contract.Modifies.empty()))
return;
if (const FunctionDecl *FD = Actions.getCurFunctionDecl();
FD && FD->isTemplated()) {
if (const auto *Method =
dyn_cast_or_null<CXXMethodDecl>(Actions.getCurFunctionDecl());
Method && Method->getParent()->isTemplated()) {
Diag(Loop.get()->getBeginLoc(), diag::warn_contract_template_unsupported)
<< 0 << FD;
<< 1 << Method->getParent();
return;
}
Actions.getASTContext().getOrCreateLoopContract(Loop.get()) =
Expand Down Expand Up @@ -1379,40 +1372,19 @@ ExprResult Parser::ParseQuantifierExpr() {
return ExprError();
}

// Validate bound types: lo and hi must be integer.
for (const ExprResult &Bound : {Lo, Hi})
if (Bound.isUsable() && !Bound.get()->isTypeDependent() &&
!Bound.get()->getType()->isIntegerType()) {
Diag(Bound.get()->getExprLoc(),
diag::err_contract_quantifier_bound_not_int);
SkipUntil(tok::r_paren, StopAtSemi);
return ExprError();
}

// Validate body: must be contextually convertible to bool.
ExprResult BodyBool = Actions.ActOnContractCondition(Body);
if (BodyBool.isInvalid()) {
SkipUntil(tok::r_paren, StopAtSemi);
return ExprError();
}

if (Tok.isNot(tok::r_paren)) {
Diag(Tok, diag::err_contract_expected_rparen) << Keyword;
SkipUntil(tok::r_paren, StopAtSemi);
return ExprError();
}
SourceLocation RParenLoc = ConsumeParen();

Expr *LoE = Lo.isUsable() ? Lo.get() : nullptr;
Expr *HiE = Hi.isUsable() ? Hi.get() : nullptr;
if (IsChoose)
return new (Ctx) ContractChooseExpr(KwLoc, LParenLoc, RParenLoc, BoundVar,
LoE, HiE, BodyBool.get(), Ctx.IntTy);
if (IsForall)
return new (Ctx) ForallExpr(KwLoc, LParenLoc, RParenLoc, BoundVar, LoE, HiE,
BodyBool.get(), Ctx.BoolTy);
return new (Ctx) ExistsExpr(KwLoc, LParenLoc, RParenLoc, BoundVar, LoE, HiE,
BodyBool.get(), Ctx.BoolTy);
return Actions.BuildContractBinderExpr(
IsChoose ? Stmt::ContractChooseExprClass
: IsForall ? Stmt::ForallExprClass
: Stmt::ExistsExprClass,
KwLoc, LParenLoc, RParenLoc, BoundVar, Lo.isUsable() ? Lo.get() : nullptr,
Hi.isUsable() ? Hi.get() : nullptr, Body.get());
}

/// Parse cppverify::old(expr)
Expand Down
38 changes: 38 additions & 0 deletions clang/lib/Sema/SemaContract.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,44 @@ ExprResult Sema::ActOnContractRange(Expr *Base, Expr *Lower,
OK_Ordinary, ColonLoc, RBLoc);
}

ExprResult Sema::ActOnContractMeasure(ExprResult E) {
if (E.isInvalid() || E.get()->isTypeDependent() ||
E.get()->getType()->isIntegerType())
return E;
Diag(E.get()->getExprLoc(), diag::err_contract_decreases_not_int);
return ExprError();
}

ExprResult Sema::BuildContractBinderExpr(Stmt::StmtClass Kind,
SourceLocation KeywordLoc,
SourceLocation LParenLoc,
SourceLocation RParenLoc,
VarDecl *Binder, Expr *Lo, Expr *Hi,
Expr *Body) {
for (Expr *Bound : {Lo, Hi})
if (Bound && !Bound->isTypeDependent() &&
!Bound->getType()->isIntegerType()) {
Diag(Bound->getExprLoc(), diag::err_contract_quantifier_bound_not_int);
return ExprError();
}
ExprResult Condition = ActOnContractCondition(Body);
if (Condition.isInvalid())
return ExprError();
switch (Kind) {
case Stmt::ContractChooseExprClass:
return new (Context) ContractChooseExpr(KeywordLoc, LParenLoc, RParenLoc,
Binder, Lo, Hi, Condition.get(),
Binder->getType());
case Stmt::ForallExprClass:
return new (Context) ForallExpr(KeywordLoc, LParenLoc, RParenLoc, Binder,
Lo, Hi, Condition.get(), Context.BoolTy);
default:
assert(Kind == Stmt::ExistsExprClass && "not a quantifier or a choice");
return new (Context) ExistsExpr(KeywordLoc, LParenLoc, RParenLoc, Binder,
Lo, Hi, Condition.get(), Context.BoolTy);
}
}

ExprResult Sema::ActOnTypeInvariantExpr(ExprResult E, CXXRecordDecl *Record) {
if (E.isInvalid() || !Record)
return E;
Expand Down
6 changes: 6 additions & 0 deletions clang/lib/Sema/SemaExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19173,6 +19173,12 @@ void Sema::MarkFunctionReferenced(SourceLocation Loc, FunctionDecl *Func,
if (FPT && isUnresolvedExceptionSpec(FPT->getExceptionSpecType()))
ResolveExceptionSpec(Loc, FPT);

// A contract of a template specialization is needed where the
// specialization is used, as its callers rely on it, whether or not its
// definition is instantiated here.
if (OdrUse != OdrUseContext::None)
InstantiateFunctionContract(Loc, Func);

// A callee could be called by a host function then by a device function.
// If we only try recording once, we will miss recording the use on device
// side. Therefore keep trying until it is recorded.
Expand Down
23 changes: 23 additions & 0 deletions clang/lib/Sema/SemaTemplateInstantiate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1793,6 +1793,21 @@ namespace {
return AC;
}

// A quantifier's or a choice's binder is its own local, as a lambda's
// are: each element of a pack expansion binds a variable of its own.
ExprResult TransformForallExpr(ForallExpr *E) {
LocalInstantiationScope Scope(SemaRef, /*CombineWithOuterScope=*/true);
return inherited::TransformForallExpr(E);
}
ExprResult TransformExistsExpr(ExistsExpr *E) {
LocalInstantiationScope Scope(SemaRef, /*CombineWithOuterScope=*/true);
return inherited::TransformExistsExpr(E);
}
ExprResult TransformContractChooseExpr(ContractChooseExpr *E) {
LocalInstantiationScope Scope(SemaRef, /*CombineWithOuterScope=*/true);
return inherited::TransformContractChooseExpr(E);
}

ExprResult TransformLambdaExpr(LambdaExpr *E) {
// Do not rebuild lambdas to avoid creating a new type.
// Lambdas have already been processed inside their eval contexts.
Expand Down Expand Up @@ -4493,6 +4508,14 @@ Sema::SubstExpr(Expr *E, const MultiLevelTemplateArgumentList &TemplateArgs) {
return Instantiator.TransformExpr(E);
}

bool Sema::SubstFunctionContract(
const FunctionContractInfo &From, FunctionContractInfo &To,
const MultiLevelTemplateArgumentList &TemplateArgs) {
TemplateInstantiator Instantiator(*this, TemplateArgs, SourceLocation(),
DeclarationName());
return Instantiator.TransformFunctionContract(From, To);
}

ExprResult
Sema::SubstCXXIdExpr(Expr *E,
const MultiLevelTemplateArgumentList &TemplateArgs) {
Expand Down
47 changes: 47 additions & 0 deletions clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5436,6 +5436,50 @@ void Sema::InstantiateExceptionSpec(SourceLocation PointOfInstantiation,
TemplateArgs);
}

void Sema::InstantiateFunctionContract(SourceLocation PointOfInstantiation,
FunctionDecl *Function) {
if (!getLangOpts().VerifyContracts || Function->isInvalidDecl() ||
!Function->isTemplateInstantiation())
return;
const FunctionDecl *Pattern =
Function->getTemplateInstantiationPattern(/*ForDefinition=*/false);
const FunctionContractInfo *PatternContract =
Pattern ? Context.getFunctionContract(Pattern) : nullptr;
if (!PatternContract || Context.getFunctionContract(Function))
return;

// Recorded first, so that a use of the specialization in its own contract
// finds it and stops.
FunctionContractInfo &Contract = Context.getOrCreateFunctionContract(Function);
Contract.ContractDecl = Function;
Contract.IsSpec = PatternContract->IsSpec;
Contract.IsProof = PatternContract->IsProof;

NonSFINAEContext _(*this);
InstantiatingTemplate Inst(*this, PointOfInstantiation, Function);
if (Inst.isInvalid())
return;
Sema::ContextRAII SavedContext(*this, Function);
// The contract is a full expression of its own, whatever the use that
// asks for it, and the specs it applies are used.
EnterExpressionEvaluationContext Evaluated(
*this, ExpressionEvaluationContext::PotentiallyEvaluated);
LocalInstantiationScope Scope(*this);
MultiLevelTemplateArgumentList TemplateArgs = getTemplateInstantiationArgs(
Function, Function->getLexicalDeclContext(), /*Final=*/false,
/*Innermost=*/std::nullopt, /*RelativeToPrimary=*/true);
// A member's `this` is the specialization's, the current context. The
// scope identifies the parameters of every redeclaration of the pattern,
// so the clauses may name those of the declaration they are written on.
if (addInstantiatedParametersToScope(Function, Pattern, Scope, TemplateArgs))
return;
FunctionContractInfo Instantiated;
if (SubstFunctionContract(*PatternContract, Instantiated, TemplateArgs))
return;
Instantiated.ContractDecl = Function;
Contract = std::move(Instantiated);
}

/// Initializes the common fields of an instantiation function
/// declaration (New) from the corresponding fields of its template (Tmpl).
///
Expand Down Expand Up @@ -5766,6 +5810,9 @@ void Sema::InstantiateFunctionDefinition(SourceLocation PointOfInstantiation,
}
}

// The definition is verified against its own contract.
InstantiateFunctionContract(PointOfInstantiation, Function);

NonSFINAEContext _(*this);
InstantiatingTemplate Inst(*this, PointOfInstantiation, Function);
if (Inst.isInvalid())
Expand Down
Loading
Loading