1 |
|
/****************************************************************************** |
2 |
|
* Top contributors (to current version): |
3 |
|
* Tim King, Mathias Preiner, Andres Noetzli |
4 |
|
* |
5 |
|
* This file is part of the cvc5 project. |
6 |
|
* |
7 |
|
* Copyright (c) 2009-2021 by the authors listed in the file AUTHORS |
8 |
|
* in the top-level source directory and their institutional affiliations. |
9 |
|
* All rights reserved. See the file COPYING in the top-level source |
10 |
|
* directory for licensing information. |
11 |
|
* **************************************************************************** |
12 |
|
* |
13 |
|
* Assertion utility classes, functions and macros. |
14 |
|
* |
15 |
|
* Assertion macros assert a condition and aborts() the process if the |
16 |
|
* condition is not satisfied. These macro leave a hanging ostream for the user |
17 |
|
* to specify additional information about the failure. |
18 |
|
* |
19 |
|
* Example usage: |
20 |
|
* AlwaysAssert(x >= 0) << "x must be positive."; |
21 |
|
* |
22 |
|
* Assert is an AlwaysAssert that is only enabled in debug builds. |
23 |
|
* Assert(pointer != nullptr); |
24 |
|
* |
25 |
|
* CVC5_FATAL() can be used to indicate unreachable code. |
26 |
|
* |
27 |
|
* Note: The AlwaysAssert and Assert macros are not safe for use in |
28 |
|
* signal-handling code. |
29 |
|
*/ |
30 |
|
|
31 |
|
#include "cvc5_private_library.h" |
32 |
|
|
33 |
|
#ifndef CVC5__CHECK_H |
34 |
|
#define CVC5__CHECK_H |
35 |
|
|
36 |
|
#include <cstdarg> |
37 |
|
#include <ostream> |
38 |
|
|
39 |
|
#include "base/exception.h" |
40 |
|
#include "cvc5_export.h" |
41 |
|
|
42 |
|
// Define CVC5_PREDICT_FALSE(x) that helps the compiler predict that x will be |
43 |
|
// false (if there is compiler support). |
44 |
|
#ifdef __has_builtin |
45 |
|
#if __has_builtin(__builtin_expect) |
46 |
|
#define CVC5_PREDICT_FALSE(x) (__builtin_expect(x, false)) |
47 |
|
#define CVC5_PREDICT_TRUE(x) (__builtin_expect(x, true)) |
48 |
|
#else |
49 |
|
#define CVC5_PREDICT_FALSE(x) x |
50 |
|
#define CVC5_PREDICT_TRUE(x) x |
51 |
|
#endif |
52 |
|
#else |
53 |
|
#define CVC5_PREDICT_FALSE(x) x |
54 |
|
#define CVC5_PREDICT_TRUE(x) x |
55 |
|
#endif |
56 |
|
|
57 |
|
#ifdef __has_cpp_attribute |
58 |
|
#if __has_cpp_attribute(fallthrough) |
59 |
|
#define CVC5_FALLTHROUGH [[fallthrough]] |
60 |
|
#endif // __has_cpp_attribute(fallthrough) |
61 |
|
#endif // __has_cpp_attribute |
62 |
|
#ifndef CVC5_FALLTHROUGH |
63 |
|
#define CVC5_FALLTHROUGH |
64 |
|
#endif |
65 |
|
|
66 |
|
namespace cvc5 { |
67 |
|
|
68 |
|
// Implementation notes: |
69 |
|
// To understand FatalStream and OStreamVoider, it is useful to understand |
70 |
|
// how a AlwaysAssert is structured. AlwaysAssert(cond) is roughly the following |
71 |
|
// pattern: |
72 |
|
// cond ? (void)0 : OstreamVoider() & FatalStream().stream() |
73 |
|
// This is a carefully crafted message to achieve a hanging ostream using |
74 |
|
// operator precedence. The line `AlwaysAssert(cond) << foo << bar;` will bind |
75 |
|
// as follows: |
76 |
|
// `cond ? ((void)0) : (OSV() & ((FS().stream() << foo) << bar));` |
77 |
|
// Once the expression is evaluated, the destructor ~FatalStream() of the |
78 |
|
// temporary object is then run, which abort()'s the process. The role of the |
79 |
|
// OStreamVoider() is to match the void type of the true branch. |
80 |
|
|
81 |
|
// Class that provides an ostream and whose destructor aborts! Direct usage of |
82 |
|
// this class is discouraged. |
83 |
|
class CVC5_EXPORT FatalStream |
84 |
|
{ |
85 |
|
public: |
86 |
|
FatalStream(const char* function, const char* file, int line); |
87 |
|
[[noreturn]] ~FatalStream(); |
88 |
|
|
89 |
|
std::ostream& stream(); |
90 |
|
|
91 |
|
private: |
92 |
|
void Flush(); |
93 |
|
}; |
94 |
|
|
95 |
|
// Helper class that changes the type of an std::ostream& into a void. See |
96 |
|
// "Implementation notes" for more information. |
97 |
|
class OstreamVoider |
98 |
|
{ |
99 |
|
public: |
100 |
798 |
OstreamVoider() {} |
101 |
|
// The operator precedence between operator& and operator<< is critical here. |
102 |
798 |
void operator&(std::ostream&) {} |
103 |
|
}; |
104 |
|
|
105 |
|
// CVC5_FATAL() always aborts a function and provides a convenient way of |
106 |
|
// formatting error messages. This can be used instead of a return type. |
107 |
|
// |
108 |
|
// Example function that returns a type Foo: |
109 |
|
// Foo bar(T t) { |
110 |
|
// switch(t.type()) { |
111 |
|
// ... |
112 |
|
// default: |
113 |
|
// CVC5_FATAL() << "Unknown T type " << t.enum(); |
114 |
|
// } |
115 |
|
// } |
116 |
|
#define CVC5_FATAL() \ |
117 |
|
FatalStream(__PRETTY_FUNCTION__, __FILE__, __LINE__).stream() |
118 |
|
|
119 |
|
/* GCC <= 9.2 ignores CVC5_NO_RETURN of ~FatalStream() if |
120 |
|
* used in template classes (e.g., CDHashMap::save()). As a workaround we |
121 |
|
* explicitly call abort() to let the compiler know that the |
122 |
|
* corresponding function call will not return. */ |
123 |
|
#define SuppressWrongNoReturnWarning abort() |
124 |
|
|
125 |
|
// If `cond` is true, log an error message and abort the process. |
126 |
|
// Otherwise, does nothing. This leaves a hanging std::ostream& that can be |
127 |
|
// inserted into. |
128 |
|
#define CVC5_FATAL_IF(cond, function, file, line) \ |
129 |
|
CVC5_PREDICT_FALSE(!(cond)) \ |
130 |
|
? (void)0 : OstreamVoider() & FatalStream(function, file, line).stream() |
131 |
|
|
132 |
|
// If `cond` is false, log an error message and abort()'s the process. |
133 |
|
// Otherwise, does nothing. This leaves a hanging std::ostream& that can be |
134 |
|
// inserted into using operator<<. Example usages: |
135 |
|
// AlwaysAssert(x >= 0); |
136 |
|
// AlwaysAssert(x >= 0) << "x must be positive"; |
137 |
|
// AlwaysAssert(x >= 0) << "expected a positive value. Got " << x << " |
138 |
|
// instead"; |
139 |
|
#define AlwaysAssert(cond) \ |
140 |
|
CVC5_FATAL_IF(!(cond), __PRETTY_FUNCTION__, __FILE__, __LINE__) \ |
141 |
|
<< "Check failure\n\n " << #cond << "\n" |
142 |
|
|
143 |
|
// Assert is a variant of AlwaysAssert() that is only checked when |
144 |
|
// CVC5_ASSERTIONS is defined. We rely on the optimizer to remove the deadcode. |
145 |
|
#ifdef CVC5_ASSERTIONS |
146 |
|
#define Assert(cond) AlwaysAssert(cond) |
147 |
|
#else |
148 |
|
#define Assert(cond) \ |
149 |
|
CVC5_FATAL_IF(false, __PRETTY_FUNCTION__, __FILE__, __LINE__) |
150 |
|
#endif |
151 |
|
|
152 |
14 |
class AssertArgumentException : public Exception |
153 |
|
{ |
154 |
|
protected: |
155 |
|
AssertArgumentException() : Exception() {} |
156 |
|
|
157 |
|
void construct(const char* header, |
158 |
|
const char* extra, |
159 |
|
const char* function, |
160 |
|
const char* file, |
161 |
|
unsigned line, |
162 |
|
const char* fmt, |
163 |
|
va_list args); |
164 |
|
|
165 |
|
void construct(const char* header, |
166 |
|
const char* extra, |
167 |
|
const char* function, |
168 |
|
const char* file, |
169 |
|
unsigned line); |
170 |
|
|
171 |
|
public: |
172 |
|
AssertArgumentException(const char* condStr, |
173 |
|
const char* argDesc, |
174 |
|
const char* function, |
175 |
|
const char* file, |
176 |
|
unsigned line, |
177 |
|
const char* fmt, |
178 |
|
...); |
179 |
|
|
180 |
|
AssertArgumentException(const char* condStr, |
181 |
|
const char* argDesc, |
182 |
|
const char* function, |
183 |
|
const char* file, |
184 |
|
unsigned line); |
185 |
|
|
186 |
|
}; /* class AssertArgumentException */ |
187 |
|
|
188 |
|
#define Unreachable() CVC5_FATAL() << "Unreachable code reached" |
189 |
|
|
190 |
|
#define Unhandled() CVC5_FATAL() << "Unhandled case encountered" |
191 |
|
|
192 |
|
#define Unimplemented() CVC5_FATAL() << "Unimplemented code encountered" |
193 |
|
|
194 |
|
#define InternalError() CVC5_FATAL() << "Internal error detected" |
195 |
|
|
196 |
|
#define IllegalArgument(arg, msg...) \ |
197 |
|
throw ::cvc5::IllegalArgumentException( \ |
198 |
|
"", \ |
199 |
|
#arg, \ |
200 |
|
__PRETTY_FUNCTION__, \ |
201 |
|
::cvc5::IllegalArgumentException::formatVariadic(msg).c_str()); |
202 |
|
// This cannot use check argument directly as this forces |
203 |
|
// CheckArgument to use a va_list. This is unsupported in Swig. |
204 |
|
#define PrettyCheckArgument(cond, arg, msg...) \ |
205 |
|
do \ |
206 |
|
{ \ |
207 |
|
if (__builtin_expect((!(cond)), false)) \ |
208 |
|
{ \ |
209 |
|
throw ::cvc5::IllegalArgumentException( \ |
210 |
|
#cond, \ |
211 |
|
#arg, \ |
212 |
|
__PRETTY_FUNCTION__, \ |
213 |
|
::cvc5::IllegalArgumentException::formatVariadic(msg).c_str()); \ |
214 |
|
} \ |
215 |
|
} while (0) |
216 |
|
#define AlwaysAssertArgument(cond, arg, msg...) \ |
217 |
|
do \ |
218 |
|
{ \ |
219 |
|
if (__builtin_expect((!(cond)), false)) \ |
220 |
|
{ \ |
221 |
|
throw ::cvc5::AssertArgumentException( \ |
222 |
|
#cond, #arg, __PRETTY_FUNCTION__, __FILE__, __LINE__, ##msg); \ |
223 |
|
} \ |
224 |
|
} while (0) |
225 |
|
|
226 |
|
#ifdef CVC5_ASSERTIONS |
227 |
|
#define AssertArgument(cond, arg, msg...) AlwaysAssertArgument(cond, arg, ##msg) |
228 |
|
#define DebugCheckArgument(cond, arg, msg...) CheckArgument(cond, arg, ##msg) |
229 |
|
#else /* ! CVC5_ASSERTIONS */ |
230 |
|
#define AssertArgument(cond, arg, msg...) /*__builtin_expect( ( cond ), true \ |
231 |
|
)*/ |
232 |
|
#define DebugCheckArgument( \ |
233 |
|
cond, arg, msg...) /*__builtin_expect( ( cond ), true )*/ |
234 |
|
#endif /* CVC5_ASSERTIONS */ |
235 |
|
|
236 |
|
} // namespace cvc5 |
237 |
|
|
238 |
|
#endif /* CVC5__CHECK_H */ |