Skip to content

Commit 33eaf39

Browse files
committed
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=<semantic>, 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.
1 parent 670df13 commit 33eaf39

28 files changed

+721
-107
lines changed

clang/include/clang/AST/Expr.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1235,6 +1235,17 @@ class OpaqueValueExpr : public Expr {
12351235
}
12361236
};
12371237

1238+
// This enum is silly, but avoids creating overload sets where many adjacent
1239+
// arguments are all convertible to/from bool or int.
1240+
enum class ContractConstification {
1241+
CC_None,
1242+
CC_ApplyConst,
1243+
};
1244+
1245+
constexpr ContractConstification CC_None = ContractConstification::CC_None;
1246+
constexpr ContractConstification CC_ApplyConst =
1247+
ContractConstification::CC_ApplyConst;
1248+
12381249
/// A reference to a declared variable, function, enum, etc.
12391250
/// [C99 6.5.1p2]
12401251
///

clang/include/clang/AST/Stmt.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -863,6 +863,9 @@ class alignas(void *) Stmt {
863863

864864
LLVM_PREFERRED_TYPE(ContractKind)
865865
unsigned ContractKind : 2;
866+
867+
LLVM_PREFERRED_TYPE(unsigned)
868+
unsigned NumAttrs : 32 - NumStmtBits - 3;
866869
};
867870

868871
class CXXNewExprBitfields {

clang/include/clang/AST/StmtCXX.h

Lines changed: 66 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -526,11 +526,13 @@ class CoreturnStmt : public Stmt {
526526
};
527527

528528
enum class ContractKind { Pre, Post, Assert };
529+
class SemaContractHelper;
529530

530531
class ContractStmt final : public Stmt,
531-
private llvm::TrailingObjects<ContractStmt, Stmt *> {
532+
private llvm::TrailingObjects<ContractStmt, void *> {
532533
friend class ASTStmtReader;
533534
friend TrailingObjects;
535+
friend class SemaContractHelper;
534536

535537
enum { ResultNameDeclOffset = 0, Count = 1 };
536538

@@ -539,32 +541,74 @@ class ContractStmt final : public Stmt,
539541
return ResultNameDeclOffset + ContractAssertBits.HasResultName;
540542
}
541543

542-
Stmt **getSubStmts() { return getTrailingObjects<Stmt *>(); }
544+
unsigned attributeOffset() const { return condOffset() + 1; }
543545

544-
Stmt *const *getSubStmts() const { return getTrailingObjects<Stmt *>(); }
546+
Stmt **getSubStmts() {
547+
return reinterpret_cast<Stmt **>(getTrailingObjects<void *>());
548+
}
549+
Stmt *const *getSubStmts() const {
550+
return reinterpret_cast<Stmt *const *>(getTrailingObjects<void *>());
551+
}
545552

546553
SourceLocation KeywordLoc;
547554

555+
const Attr *const *getAttrArrayPtr() const {
556+
return reinterpret_cast<const Attr *const *>(getTrailingObjects<void *>() +
557+
attributeOffset());
558+
}
559+
const Attr **getAttrArrayPtr() {
560+
return const_cast<Attr const **>(reinterpret_cast<Attr **>(
561+
getTrailingObjects<void *>() + attributeOffset()));
562+
}
563+
564+
Stmt **getStmtPtr() {
565+
return reinterpret_cast<Stmt **>(getTrailingObjects<void *>());
566+
}
567+
568+
Stmt *const *getStmtPtr() const {
569+
return reinterpret_cast<Stmt *const *>(getTrailingObjects<void *>());
570+
}
571+
572+
ArrayRef<const Stmt *> getStmts() const {
573+
return llvm::ArrayRef(getSubStmts(), ContractAssertBits.HasResultName + 1);
574+
}
575+
576+
void copyAttrs(ArrayRef<const Attr *> Attrs) {
577+
assert(Attrs.size() == ContractAssertBits.NumAttrs &&
578+
"wrong number of attributes");
579+
std::copy(Attrs.begin(), Attrs.end(), getAttrArrayPtr());
580+
}
581+
548582
public:
549-
ContractStmt(ContractKind CK, SourceLocation KeywordLoc, Expr *Condition)
583+
ContractStmt(ContractKind CK, SourceLocation KeywordLoc, Expr *Condition,
584+
DeclStmt *RN, ArrayRef<const Attr *> Attrs = {})
550585
: Stmt(ContractStmtClass), KeywordLoc(KeywordLoc) {
551586
ContractAssertBits.ContractKind = static_cast<unsigned>(CK);
552-
ContractAssertBits.HasResultName = false;
587+
ContractAssertBits.HasResultName = RN != nullptr;
588+
ContractAssertBits.NumAttrs = Attrs.size();
589+
if (RN)
590+
setResultNameDecl(RN);
553591
setCondition(Condition);
592+
std::copy(Attrs.begin(), Attrs.end(), getAttrArrayPtr());
554593
}
555594

556-
ContractStmt(EmptyShell Empty, ContractKind Kind, bool HasResultName = false)
595+
ContractStmt(EmptyShell Empty, ContractKind Kind, bool HasResultName,
596+
unsigned NumAttrs = 0)
557597
: Stmt(ContractStmtClass, Empty) {
558598
ContractAssertBits.ContractKind = static_cast<unsigned>(Kind);
559599
ContractAssertBits.HasResultName = HasResultName;
600+
ContractAssertBits.NumAttrs = NumAttrs;
601+
if (NumAttrs != 0)
602+
std::fill_n(getAttrArrayPtr(), NumAttrs, nullptr);
560603
}
561604

562605
static ContractStmt *Create(const ASTContext &C, ContractKind Kind,
563606
SourceLocation KeywordLoc, Expr *Condition,
564-
DeclStmt *ResultNameDecl = nullptr);
607+
DeclStmt *ResultNameDecl,
608+
ArrayRef<const Attr *> Attrs = {});
565609

566610
static ContractStmt *CreateEmpty(const ASTContext &C, ContractKind Kind,
567-
bool HasResultName = false);
611+
bool HasResultName, unsigned NumAttrs);
568612

569613
ContractKind getContractKind() const {
570614
return static_cast<ContractKind>(ContractAssertBits.ContractKind);
@@ -573,34 +617,35 @@ class ContractStmt final : public Stmt,
573617
ContractAssertBits.ContractKind = static_cast<unsigned>(CK);
574618
}
575619

620+
ArrayRef<const Attr *> getAttrs() const {
621+
return llvm::ArrayRef(getAttrArrayPtr(), ContractAssertBits.NumAttrs);
622+
}
623+
576624
bool hasResultNameDecl() const { return ContractAssertBits.HasResultName; }
577625

578626
DeclStmt *getResultNameDeclStmt() const {
579627
return hasResultNameDecl()
580-
? static_cast<DeclStmt *>(
581-
getTrailingObjects<Stmt *>()[ResultNameDeclOffset])
628+
? static_cast<DeclStmt *>(getStmtPtr()[ResultNameDeclOffset])
582629
: nullptr;
583630
}
584631

585632
ResultNameDecl *getResultNameDecl() const;
586633

587634
void setResultNameDecl(DeclStmt *D) {
588635
assert(hasResultNameDecl() && "no result name decl");
589-
getTrailingObjects<Stmt *>()[ResultNameDeclOffset] = D;
636+
getStmtPtr()[ResultNameDeclOffset] = D;
590637
}
591638

592-
void setCondition(Expr *E) { getTrailingObjects<Stmt *>()[condOffset()] = E; }
639+
void setCondition(Expr *E) { getStmtPtr()[condOffset()] = E; }
593640

594-
Expr *getCond() {
595-
return reinterpret_cast<Expr *>(getTrailingObjects<Stmt *>()[condOffset()]);
596-
}
641+
Expr *getCond() { return static_cast<Expr *>(getStmtPtr()[condOffset()]); }
597642

598643
const Expr *getCond() const {
599-
return reinterpret_cast<Expr *>(getTrailingObjects<Stmt *>()[condOffset()]);
644+
return static_cast<Expr *>(getStmtPtr()[condOffset()]);
600645
}
601646

602647
void setCond(Expr *Cond) {
603-
getTrailingObjects<Stmt *>()[condOffset()] = reinterpret_cast<Stmt *>(Cond);
648+
getStmtPtr()[condOffset()] = reinterpret_cast<Stmt *>(Cond);
604649
}
605650

606651
SourceLocation getKeywordLoc() const { return KeywordLoc; }
@@ -609,6 +654,10 @@ class ContractStmt final : public Stmt,
609654
return getCond()->getEndLoc();
610655
}
611656

657+
// Return [[clang::contract_group]] attribute group string if specified
658+
StringRef getContractGroup() const;
659+
ContractEvaluationSemantic getSemantic(const LangOptions &LangOpts) const;
660+
612661
child_range children() {
613662
return child_range(getSubStmts(), getSubStmts() + condOffset() + 1);
614663
}

clang/include/clang/Basic/Attr.td

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3262,7 +3262,7 @@ def Unavailable : InheritableAttr {
32623262

32633263
def ContractGroup : StmtAttr {
32643264
let Spellings = [CXX11<"clang", "contract_group">];
3265-
let Args = [ StringArgument<"Group">];
3265+
let Args = [ StringArgument<"Group"> ];
32663266
let Subjects = SubjectList<[ContractStmt], ErrorDiag, "contract_assert">;
32673267
let Documentation = [Undocumented];
32683268
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
//===- ContractsOptions.h - C++ Contract Options ----------------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
//
9+
/// \file
10+
/// Defines common enums and types used by contracts and contract attributes.
11+
//
12+
//===----------------------------------------------------------------------===//
13+
14+
#ifndef LLVM_CLANG_BASIC_CONTRACT_OPTIONS_H
15+
#define LLVM_CLANG_BASIC_CONTRACT_OPTIONS_H
16+
17+
#include "clang/Basic/LLVM.h"
18+
#include "llvm/ADT/StringMap.h"
19+
#include "llvm/ADT/StringRef.h"
20+
#include <cassert>
21+
#include <cstdint>
22+
#include <functional>
23+
#include <optional>
24+
#include <string>
25+
#include <vector>
26+
27+
namespace clang {
28+
using llvm::StringRef;
29+
30+
enum class ContractGroupDiagnostic {
31+
// The remaining values map to %select values in diagnostics in both
32+
// DiagnosticSemaKinds.td and DiagnosticDriverKinds.td.
33+
InvalidFirstChar,
34+
InvalidChar,
35+
InvalidLastChar,
36+
EmptySubGroup,
37+
Empty,
38+
InvalidSemantic,
39+
};
40+
41+
/// Contract evaluation mode. Determines whether to check contracts, and
42+
// whether contract failures cause compile errors.
43+
enum class ContractEvaluationSemantic {
44+
Invalid = -1,
45+
46+
// Contracts are parsed, syntax checked and type checked, but never evaluated.
47+
// FIXME(EricWF): This doesn't yet map to an actual enumerator in
48+
// std::contracts::evaluation_semantic
49+
Ignore = 0,
50+
51+
// Contracts are run, failures are reported, and when a contract fails the
52+
// program is terminated. The compiler can assume after contracts statements
53+
// that the contracts hold.
54+
Enforce = 1,
55+
56+
// Contracts are run, and failures are reported, but contract failures do not
57+
// logically stop execution of the program, nor can the compiler assume
58+
// contracts are true for optimizing.
59+
Observe = 2,
60+
61+
// Contracts are run, failures cause an immediate trap
62+
// FIXME(EricWF): This doesn't yet map to an actual enumerator in
63+
// std::contracts::evaluation_semantic
64+
QuickEnforce = 3,
65+
};
66+
67+
/// Represents the set of contract groups that have been enabled or disabled
68+
/// on the command line using '-fclang-contract-groups='.
69+
///
70+
/// A contract group is a string consisting of identifiers join by '.'. For
71+
/// example, "a.b.c" is a contract group with three subgroups.
72+
///
73+
/// When determining if a particular contract check is enabled, we check if
74+
/// the user has enabled/disabled the group/subgroup that the check belongs to,
75+
/// in order of specificity. For example, passing
76+
/// '-fclang-contract-groups=-std,+std.hardening,-std.hardening.foo'
77+
/// will enable 'std.hardening.baz', but disable 'std.hardening.foo.bar' and
78+
/// 'std.baz'.
79+
///
80+
/// TODO(EricWF): Should we match in the same manner as clang-tidy checks?
81+
/// Specifically, allow the use of '*' and drop all notion of groups?
82+
class ContractOptions {
83+
public:
84+
ContractOptions() = default;
85+
86+
public:
87+
using DiagnoseGroupFunc =
88+
std::function<void(ContractGroupDiagnostic, StringRef, StringRef)>;
89+
90+
static bool validateContractGroup(llvm::StringRef GroupAndValue,
91+
const DiagnoseGroupFunc &Diagnoser);
92+
93+
std::vector<std::string> serializeContractGroupArgs() const;
94+
95+
void parseContractGroups(const std::vector<std::string> &Groups,
96+
const DiagnoseGroupFunc &Diagnoser);
97+
98+
void addUnparsedContractGroup(StringRef GroupAndValue,
99+
const DiagnoseGroupFunc &Diagnoser);
100+
101+
ContractEvaluationSemantic getSemanticForGroup(llvm::StringRef Group) const;
102+
void setGroupSemantic(llvm::StringRef Group,
103+
ContractEvaluationSemantic Semantic);
104+
105+
/// The default semantics for contracts.
106+
ContractEvaluationSemantic DefaultSemantic =
107+
ContractEvaluationSemantic::Enforce;
108+
109+
/// The semantics for each contract group, if specified.
110+
llvm::StringMap<ContractEvaluationSemantic> SemanticsByGroup;
111+
};
112+
113+
} // namespace clang
114+
115+
#endif // LLVM_CLANG_BASIC_CONTRACT_OPTIONS_H

clang/include/clang/Basic/DiagnosticDriverKinds.td

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -816,4 +816,16 @@ def err_drv_triple_version_invalid : Error<
816816

817817
def warn_missing_include_dirs : Warning<
818818
"no such include directory: '%0'">, InGroup<MissingIncludeDirs>, DefaultIgnore;
819+
820+
821+
def err_drv_contract_group_name_invalid : Error<
822+
"contract group name %select{"
823+
"\"%1\" cannot start with '%2'"
824+
"|\"%1\" cannot contain '%2'"
825+
"|\"%1\" cannot end with '%2'"
826+
"|\"%1\" cannot contain empty subgroup \"%2\""
827+
"|cannot be empty"
828+
"|\"%2\" is an invalid value"
829+
"}0">;
830+
819831
}

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11905,6 +11905,21 @@ def err_deduced_auto_result_name_without_body : Error<
1190511905
def err_result_name_not_allowed : Error<
1190611906
"result name %0 not allowed outside of post condition specifier">;
1190711907

11908+
// Diagnose clang::contract_group attribute strings
11909+
def err_contract_group_attribute_invalid_group : Error<
11910+
"clang::contract_group group %select{"
11911+
"\"%1\" cannot start with '%2'"
11912+
"|\"%1\" cannot contain '%2'"
11913+
"|\"%1\" cannot end with '%2'"
11914+
"|\"%1\" cannot contain empty subgroup \"%2\""
11915+
"|cannot be empty"
11916+
"|<cannot-happen-here>"
11917+
"}0">;
11918+
11919+
11920+
def err_contract_group_redeclared : Error<
11921+
"clang::contract_group attribute appears more than once">;
11922+
1190811923
} // end of contracts issues
1190911924
let CategoryName = "Documentation Issue" in {
1191011925
def warn_not_a_doxygen_trailing_member_comment : Warning<

0 commit comments

Comments
 (0)