@@ -123,113 +123,95 @@ void issue_warning(const char *warning);
123123
124124template <typename T>
125125struct ReportBase {
126- std::ostringstream msg;
127-
128- ReportBase (const char *file, const char *function, int line, const char *condition_string, const char *prefix) {
129- if (debug_is_active_impl (1 , file, function, line)) {
130- msg << prefix << " at " << file << " :" << line << ' ' ;
131- if (condition_string) {
132- msg << " Condition failed: " << condition_string << ' ' ;
133- }
134- }
135- }
136-
137- // Just a trick used to convert RValue into LValue
138- HALIDE_ALWAYS_INLINE T &ref () {
139- return *static_cast <T *>(this );
140- }
141-
142126 template <typename S>
143127 HALIDE_ALWAYS_INLINE T &operator <<(const S &x) {
144128 msg << x;
145129 return *static_cast <T *>(this );
146130 }
147131
132+ HALIDE_ALWAYS_INLINE operator bool () const {
133+ return !finalized;
134+ }
135+
148136protected:
137+ std::ostringstream msg{};
138+ bool finalized{false };
139+
140+ // This function is called as part of issue() below. We can't use a
141+ // virtual function because issue() needs to be marked [[noreturn]]
142+ // for errors and be left alone for warnings (i.e., they have
143+ // different signatures).
149144 std::string finalize_message () {
150145 if (!msg.str ().empty () && msg.str ().back () != ' \n ' ) {
151146 msg << " \n " ;
152147 }
148+ finalized = true ;
153149 return msg.str ();
154150 }
151+
152+ T &init (const char *file, const char *function, const int line, const char *condition_string, const char *prefix) {
153+ if (debug_is_active_impl (1 , file, function, line)) {
154+ msg << prefix << " at " << file << " :" << line << ' ' ;
155+ if (condition_string) {
156+ msg << " Condition failed: " << condition_string << ' ' ;
157+ }
158+ }
159+ return *static_cast <T *>(this );
160+ }
155161};
156162
157163template <typename Exception>
158- struct ErrorReport : ReportBase<ErrorReport<Exception>> {
159- using Base = ReportBase<ErrorReport>;
160-
161- ErrorReport (const char *file, const char *function, int line, const char *condition_string)
162- : Base(file, function, line, condition_string, Exception::error_name) {
163- this ->msg << " Error: " ;
164+ struct ErrorReport final : ReportBase<ErrorReport<Exception>> {
165+ ErrorReport &init (const char *file, const char *function, const int line, const char *condition_string) {
166+ return ReportBase<ErrorReport>::init (file, function, line, condition_string, Exception::error_name) << " Error: " ;
164167 }
165168
166- #ifdef _MSC_VER
167- #pragma warning(push)
168- #pragma warning(disable : 4722)
169- #endif
170- /* * When you're done using << on the object, and let it fall out of
171- * scope, this throws an exception (or aborts if exceptions are disabled).
172- * This is a little dangerous because the destructor will also be
173- * called if there's an exception in flight due to an error in one
174- * of the arguments passed to operator<<. We handle this by rethrowing
175- * the current exception, if it exists.
176- */
177- [[noreturn]] ~ErrorReport () noexcept (false ) {
169+ [[noreturn]] void issue () noexcept (false ) {
178170 throw_error (Exception (this ->finalize_message ()));
179171 }
180- #ifdef _MSC_VER
181- #pragma warning(pop)
182- #endif
183172};
184173
185- struct WarningReport : ReportBase<WarningReport> {
186- WarningReport (const char *file, const char *function, int line, const char *condition_string)
187- : ReportBase(file, function, line, condition_string, " Warning" ) {
188- this ->msg << " Warning: " ;
174+ struct WarningReport final : ReportBase<WarningReport> {
175+ WarningReport &init (const char *file, const char *function, const int line, const char *condition_string) {
176+ return ReportBase::init (file, function, line, condition_string, " Warning" ) << " Warning: " ;
189177 }
190178
191- /* * When you're done using << on the object, and let it fall out of
192- * scope, this prints the computed warning message.
193- */
194- ~WarningReport () {
179+ void issue () {
195180 issue_warning (this ->finalize_message ().c_str ());
196181 }
197182};
198183
199- // This uses operator precedence as a trick to avoid argument evaluation if
200- // an assertion is true: it is intended to be used as part of the
201- // _halide_internal_assertion macro, to coerce the result of the stream
202- // expression to void (to match the condition-is-false case).
203- struct Voidifier {
204- // This has to be an operator with a precedence lower than << but
205- // higher than ?:
206- template <typename T>
207- HALIDE_ALWAYS_INLINE void operator &(T &) {
208- }
209- };
210-
211184/* *
212- * _halide_internal_assertion is used to implement our assertion macros
185+ * _halide_internal_diagnostic is used to implement our assertion macros
213186 * in such a way that the messages output for the assertion are only
214187 * evaluated if the assertion's value is false.
215188 *
216- * Note that this macro intentionally has no parens internally; in actual
217- * use, the implicit grouping will end up being
218- *
219- * condition ? (void) : (Voidifier() & (ErrorReport << arg1 << arg2 ... << argN))
220- *
221189 * This (regrettably) requires a macro to work, but has the highly desirable
222190 * effect that all assertion parameters are totally skipped (not ever evaluated)
223191 * when the assertion is true.
192+ *
193+ * The macro works by deferring the call to issue() until after the stream
194+ * has been evaluated. This previously used a trick where ErrorReport would
195+ * throw in the destructor, but throwing in a destructor is UB in a lot of
196+ * scenarios, and it was easy to break things by mistake.
224197 */
225- #define _halide_internal_assertion (condition, type ) \
198+ // clang-format off
199+ #define _halide_internal_diagnostic (condition, type, condition_string ) \
226200 /* NOLINTNEXTLINE(bugprone-macro-parentheses) */ \
227- (condition) ? (void )0 : ::Halide::Internal::Voidifier() & ::Halide::Internal::ErrorReport<type>(__FILE__, __FUNCTION__, __LINE__, #condition).ref()
201+ if (!(condition)) for (type _err; _err; _err.issue()) _err.init(__FILE__, __FUNCTION__, __LINE__, condition_string)
202+ // clang-format on
203+
204+ #define _halide_internal_error (type ) \
205+ _halide_internal_diagnostic (0 , Halide::Internal::ErrorReport<type>, nullptr )
206+
207+ #define _halide_internal_assertion (condition, type ) \
208+ _halide_internal_diagnostic (condition, Halide::Internal::ErrorReport<type>, #condition)
209+
210+ #define user_error _halide_internal_error (Halide::CompileError)
211+ #define internal_error _halide_internal_error (Halide::InternalError)
212+ #define halide_runtime_error _halide_internal_error (Halide::RuntimeError)
228213
229- #define internal_error Halide::Internal::ErrorReport<Halide::InternalError>(__FILE__, __FUNCTION__, __LINE__, nullptr )
230- #define user_error Halide::Internal::ErrorReport<Halide::CompileError>(__FILE__, __FUNCTION__, __LINE__, nullptr )
231- #define user_warning Halide::Internal::WarningReport (__FILE__, __FUNCTION__, __LINE__, nullptr )
232- #define halide_runtime_error Halide::Internal::ErrorReport<Halide::RuntimeError>(__FILE__, __FUNCTION__, __LINE__, nullptr )
214+ #define user_warning _halide_internal_diagnostic (0 , Halide::Internal::WarningReport, nullptr )
233215
234216#define internal_assert (c ) _halide_internal_assertion(c, Halide::InternalError)
235217#define user_assert (c ) _halide_internal_assertion(c, Halide::CompileError)
0 commit comments