Skip to content

Commit 1983bc6

Browse files
committed
Fix codegen for throwing contracts
1 parent 1cd86ae commit 1983bc6

File tree

5 files changed

+274
-232
lines changed

5 files changed

+274
-232
lines changed

clang/lib/CodeGen/CGContracts.cpp

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,27 @@ constexpr ContractViolationDetection ExceptionRaised =
4949
ContractViolationDetection::ExceptionRaised;
5050

5151
namespace clang::CodeGen {
52+
5253
enum ContractCheckpoint {
5354
EmittingContract,
5455
EmittingTryBody,
5556
EmittingCatchBody,
5657
};
58+
5759
enum ContractEmissionStyle {
60+
/// Emit the contract violation as an inline basic block immediatly following
61+
/// the predicate. The basic block is not shared by other contracts.
5862
Inline,
63+
64+
/// Emit a single shared contract violation handler per-function.
65+
/// This only works when exceptions are disabled, otherwise the violation
66+
/// handler
67+
/// may throw from the violation handler.
5968
SharedEnforce,
69+
70+
/// Emit a single shared contract violation handler per-function.
71+
///
72+
/// We branch directly there skipping all cleanups.
6073
SharedTrap,
6174
};
6275

@@ -216,19 +229,20 @@ void CodeGenFunction::EmitHandleContractViolationCall(
216229
llvm::Value *ViolationInfoGV, bool IsNoReturn) {
217230
auto &Ctx = getContext();
218231

219-
CanQualType ArgTypes[3] = {Ctx.IntTy, Ctx.IntTy, Ctx.VoidPtrTy};
232+
CanQualType ArgTypes[3] = {Ctx.UnsignedIntTy, Ctx.UnsignedIntTy,
233+
Ctx.VoidPtrTy};
220234

221235
const CGFunctionInfo &VFuncInfo =
222236
CGM.getTypes().arrangeBuiltinFunctionDeclaration(getContext().VoidTy,
223237
ArgTypes);
238+
224239
StringRef TargetFuncName = "__handle_contract_violation_v3";
225240
llvm::FunctionType *VFTy = CGM.getTypes().GetFunctionType(VFuncInfo);
226241
llvm::FunctionCallee VFunc = CGM.CreateRuntimeFunction(VFTy, TargetFuncName);
227242

228243
if (IsNoReturn) {
229244
llvm::Value *Args[3] = {EvalSemantic, DetectionMode, ViolationInfoGV};
230245
EmitNoreturnRuntimeCallOrInvoke(VFunc, Args);
231-
232246
Builder.ClearInsertionPoint();
233247
} else {
234248
CallArgList Args;
@@ -266,6 +280,9 @@ static bool StmtCanThrow(const Stmt *S) {
266280
// Fall through to visit the children.
267281
}
268282

283+
if (isa<CXXThrowExpr>(S))
284+
return true;
285+
269286
if (const auto *TE = dyn_cast<CXXBindTemporaryExpr>(S)) {
270287
// Special handling of CXXBindTemporaryExpr here as calling of Dtor of the
271288
// temporary is not part of `children()` as covered in the fall through.

clang/test/Contracts/Runnable/breathing-test.cpp

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,11 @@
33

44
#include "contracts.h"
55
#include "contracts-runtime.h"
6-
#include "assert.h"
6+
#include "my_assert.h"
77

88
using namespace std::contracts;
99

10-
constinit int count = 0;
11-
void handle_contract_violation(contract_violation const& vio) {
12-
count += 1;
13-
}
10+
1411

1512
void test(int x) pre(x) post(x) { contract_assert(x); }
1613

clang/test/Contracts/Runnable/library-compiler-interface.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,23 +46,32 @@ using namespace std::contracts;
4646

4747

4848
evaluation_semantic expected_semantic;
49+
detection_mode expected_mode = detection_mode::predicate_false;
4950
std::source_location expected_loc;
5051
int expected_line_offset = 0;
5152
unsigned handler_called = 0;
5253

54+
55+
5356
extern "C" void __handle_contract_violation_v3(unsigned __sem, unsigned __mode, void *data) {
5457
using namespace std::contracts;
5558
++handler_called;
5659
assert(__sem == static_cast<unsigned>(expected_semantic));
5760
evaluation_semantic sem = static_cast<evaluation_semantic>(__sem);
5861
assert(__mode == static_cast<unsigned>(detection_mode::predicate_false) ||
5962
__mode == static_cast<unsigned>(detection_mode::evaluation_exception));
63+
assert(__mode == static_cast<unsigned>(expected_mode));
6064
detection_mode mode = static_cast<detection_mode>(__mode);
6165
BuiltinContractStruct *cs = (BuiltinContractStruct*)data;
6266
void* SourceLoc = reinterpret_cast<char*>(cs) + __builtin_offsetof(BuiltinContractStruct, file);
6367
std::source_location loc = std::source_location::__create_from_pointer(SourceLoc);
6468
assert(expected_loc.file_name());
6569
assert(location_equals(loc, expected_loc, expected_line_offset));
70+
_ContractViolationImpl impl{.kind = cs->contract_kind,
71+
72+
.semantic = sem,
73+
.mode = mode,
74+
.comment = cs->comment};
6675
if (sem == evaluation_semantic::enforce && expected_semantic == evaluation_semantic::enforce) {
6776
exit(0);
6877
}
Lines changed: 17 additions & 225 deletions
Original file line numberDiff line numberDiff line change
@@ -1,231 +1,8 @@
11
// ADDITIONAL_COMPILE_FLAGS: -std=c++26 -Xclang -fcontracts -fcontract-evaluation-semantic=enforce
2+
// -fcontract-group-evaluation-semantic=observe=observe,enforce=enforce
23

34
#include "nttp_string.h"
4-
#include <map>
5-
#include <string>
6-
#include <cassert>
7-
#include <tuple>
8-
#include <vector>
9-
#include <exception>
10-
#include <format>
11-
#include <functional>
12-
#include <iostream>
13-
14-
#include "test_macros.h"
15-
#pragma GCC diagnostic ignored "-Wunused-variable"
16-
#pragma GCC diagnostic ignored "-Wunused"
17-
#pragma GCC diagnostic ignored "-Wunused-template"
18-
#pragma GCC diagnostic ignored "-Wunused-parameter"
19-
20-
#define STR2(x) #x
21-
#define STR(x) STR2(x)
22-
#define CONCAT1(x, y) x##y
23-
#define CONCAT(x, y) CONCAT1(x, y)
24-
25-
#define REGISTER_TEST(test) static ::TestRegistrar ANON_VAR(test) = []()
26-
27-
#define ANON_VAR(id) CONCAT(anon_var_, CONCAT(id, __LINE__))
28-
29-
#define COUNTERS_EQ(list, ...) assert(eq(list, {__VA_ARGS__}))
30-
31-
std::map<std::string, int>& GetCounterStore() {
32-
static std::map<std::string, int> CounterStore;
33-
return CounterStore;
34-
}
35-
36-
template <TStr Key>
37-
auto& KV = GetCounterStore()[Key.str()];
38-
39-
inline bool count(bool value) {
40-
KV<"AssertCounter"> += 1;
41-
return value;
42-
}
43-
44-
template <class T, size_t... N>
45-
auto array_to_tuple_impl(const T* arr, std::index_sequence<N...>) {
46-
return std::tuple{arr[N]...};
47-
}
48-
49-
template <class T, size_t N>
50-
auto array_to_tuple(T (&arr)[N]) {
51-
return array_to_tuple_impl(arr, std::make_index_sequence<N>{});
52-
}
53-
54-
template <class T, size_t N>
55-
auto array_to_tuple(const std::array<T, N>& arr) {
56-
return array_to_tuple_impl(arr.data(), std::make_index_sequence<N>{});
57-
}
58-
template <class... Args>
59-
struct first_type;
60-
61-
template <class T, class... Args>
62-
struct first_type<T, Args...> {
63-
using type = T;
64-
static_assert((std::is_same_v<T, Args> && ...));
65-
};
66-
template <class T>
67-
struct first_type<T> {
68-
using type = T;
69-
};
70-
template <class... Args>
71-
using first_type_t = typename first_type<Args...>::type;
72-
73-
template <class... Args>
74-
std::vector<first_type_t<Args...>> tuple_to_vector(std::tuple<Args...> const& tuple) {
75-
return std::apply(
76-
[](auto... values) {
77-
std::vector<first_type_t<Args...>> result;
78-
(result.push_back(values), ...);
79-
return result;
80-
},
81-
tuple);
82-
}
83-
84-
template <size_t N, class T>
85-
auto initlist_to_tuple(std::initializer_list<T> il) {
86-
assert(il.size() == N);
87-
std::array<T, N> arr = {};
88-
std::copy(il.begin(), il.end(), arr.begin());
89-
return array_to_tuple(arr);
90-
}
91-
92-
template <class T>
93-
std::string vector_to_str(std::vector<T> const& V) {
94-
std::string tmp = "[";
95-
bool first = true;
96-
for (auto vv : V) {
97-
if (!first) {
98-
tmp += ", ";
99-
}
100-
first = false;
101-
tmp += std::to_string(vv);
102-
}
103-
return tmp + "]";
104-
}
105-
106-
template <class... Args>
107-
inline bool eq(std::tuple<Args...>& list, std::initializer_list<int> il) {
108-
auto tup2 = initlist_to_tuple<sizeof...(Args)>(il);
109-
decltype(tup2) tup3 = list;
110-
bool result = (list == tup2);
111-
if (!result) {
112-
std::string expect_str = vector_to_str(tuple_to_vector(tup2));
113-
std::string actual_str = vector_to_str(tuple_to_vector(tup3));
114-
115-
std::cout << "Expected: " << expect_str << "\n";
116-
std::cout << "Actual: " << actual_str << "\n";
117-
std::cout << std::endl;
118-
}
119-
return result;
120-
}
121-
122-
template <class... Args>
123-
inline void reset(std::tuple<Args&...> const& list) {
124-
int values[sizeof...(Args)] = {};
125-
list = array_to_tuple(values);
126-
}
127-
128-
struct RegisteredTest {
129-
template <class Func>
130-
RegisteredTest(Func xtest) {
131-
Test = xtest;
132-
}
133-
134-
void operator()() {
135-
assert(!executed);
136-
Test();
137-
executed = true;
138-
}
139-
140-
RegisteredTest(RegisteredTest const&) = delete;
141-
RegisteredTest(RegisteredTest&&) = delete;
142-
~RegisteredTest() { assert(executed); }
143-
144-
std::function<void()> Test;
145-
bool executed = false;
146-
};
147-
148-
struct TestRegistrar {
149-
static std::vector<RegisteredTest*>& GetTests() {
150-
static std::vector<RegisteredTest*> TestRegistrars;
151-
return TestRegistrars;
152-
}
153-
154-
static void RunTests() {
155-
std::cout << "Running " << GetTests().size() << " tests\n";
156-
assert(GetTests().size() > 0);
157-
for (auto* Reg : GetTests()) {
158-
(*Reg)();
159-
}
160-
}
161-
162-
template <class Func>
163-
TestRegistrar(Func&& f) {
164-
test = new RegisteredTest(std::forward<Func>(f));
165-
GetTests().push_back(test);
166-
}
167-
168-
TestRegistrar(TestRegistrar const&) = delete;
169-
170-
~TestRegistrar() { assert(test->executed); }
171-
172-
RegisteredTest* test = nullptr;
173-
};
174-
175-
struct CounterStoreT {
176-
decltype(auto) get() { return GetCounterStore(); }
177-
178-
decltype(auto) operator[](std::string_view key) { return get()[std::string(key)]; }
179-
180-
decltype(auto) at(std::string_view key) { return get().at(std::string(key)); }
181-
182-
std::map<std::string, int>* operator->() { return &get(); }
183-
};
184-
constinit CounterStoreT CounterStore;
185-
186-
template <auto, class T>
187-
using AsType = T;
188-
189-
template <TStr Key>
190-
auto Counter = std::ref(CounterStore[Key.str()]);
191-
192-
template <TStr... Key>
193-
auto CounterGroup = std::tuple<AsType<Key, int&>...>{GetCounterStore()[Key.str()]...};
194-
195-
struct AliveCounter {
196-
explicit AliveCounter(const char* key) : Counter(&CounterStore[key]) {
197-
assert(Counter && *Counter >= 0);
198-
*Counter += 1;
199-
}
200-
201-
AliveCounter(nullptr_t) = delete;
202-
AliveCounter(void*) = delete;
203-
204-
constexpr AliveCounter(int* dest) : Counter(dest) {
205-
assert(Counter && *Counter >= 0);
206-
*Counter += 1;
207-
}
208-
209-
constexpr AliveCounter(AliveCounter const& RHS) : Counter(RHS.Counter) {
210-
assert(Counter && *Counter >= 0);
211-
*Counter += 1;
212-
}
213-
214-
~AliveCounter() {
215-
assert(*Counter >= 1);
216-
*Counter -= 1;
217-
}
218-
219-
int* Counter;
220-
};
221-
222-
template <TStr Key>
223-
struct CAliveCounter : private AliveCounter {
224-
CAliveCounter() : AliveCounter(&KV<Key>) {}
225-
CAliveCounter(CAliveCounter const& RHS) : AliveCounter(RHS) {}
226-
227-
~CAliveCounter() = default;
228-
};
5+
#include "contracts_test.h"
2296

2307
namespace fn_template_test {
2318
template <class T>
@@ -421,6 +198,21 @@ REGISTER_TEST(order_test) {
421198

422199
} // namespace order_test
423200

201+
namespace exception_handling {
202+
203+
void foo(int x) {
204+
try {
205+
contract_assert [[clang::contract_group("observe")]] ((true) ? throw 42 : true);
206+
207+
} catch (...) {
208+
assert(false);
209+
}
210+
}
211+
212+
REGISTER_TEST(test_ex_handling) { foo(0); };
213+
214+
} // namespace exception_handling
215+
424216
#define ASSERT_CEQ(key, value) assert(GetCounterStore().at(STR(KEY)) == value)
425217
#define massert(...) assert((__VA_ARGS__))
426218
int main() { TestRegistrar::RunTests(); }

0 commit comments

Comments
 (0)