Skip to content

Commit 9897658

Browse files
lgritzssh4net
authored andcommitted
api: OIIO_CONTRACT_ASSERT and other hardening improvements (AcademySoftwareFoundation#5006)
Review: we have long had two assertion macros: OIIO_ASSERT which aborts upon failure in Debug builds and prints but continues in Release builds, and OIIO_DASSERT which aborts in Debug builds and is completely inactive for Relase builds. Inspired by C++26 contracts, and increasingly available "hardening modes" in major compilers (especially with the LLVM/clang project's libc++), I'm introducing some new verification helpers. New macro `OIIO_CONTRACT_ASSERT` more closely mimics C++26 contract_assert in many ways, and perhaps will simply wrap C++ contract_assert when C++26 is on our menu. Important ways that OIIO_CONTRACT_ASSERT differs from OIIO_ASSERT and OIIO_DASSERT: * Keeping in line with C++ contracts, there are 4 possible responses to a failed contract assertion: Ignore, Observe (print only), Enforce (print and abort) and Quick-Enforce (just abort). * Also define hardening levels: None, Fast, Extensive, and Debug, mimicking the levels of libc++. The idea is that maybe there will be some CONTRACT_ASSERT checks you only want to do for certain hardening levels. * By default, the contract failure response is Enforce, unless it's both a release build and the hardening level is set to None (in which case the response will be Ignore). But it's also overrideable optionally on a per-translation-unit basis by setting OIIO_ASSERTION_RESPONSE_DEFAULT before any OIIO headers are included (though obviously that only applies to inline functions or templates, not to any already-compiled code in the library). * Macros for explicit hardening levels: OIIO_HARDENING_ASSERT_FAST(), EXTENSIVE(), and DEBUG(), which call CONTRACT_ASSERT only when the hardening level is what's required or stricter. I also changed the bounds checking in operator[] of string_view, span, and image_span to use the contract assertions. Note that this adds a tiny bit of overhead, since the default is "enforce" for release builds (previously, using OIIO_DASSERT, it did no checks for release builds). But the benchmarks seem to idicate that the perf difference is barely measurable. I added some benchmarking that proves that the bounds check adds a minute overhead to an element access for a trivial `span<float>`, maybe even indescernable. Here are benchmarks comparing raw pointer access, std::array access, span access with the new checks, span access carefully bypassing the tests. Linux workstation, gcc-11, on my work computer: pointer operator[]: 647.8 ns (+/- 0.1ns) std::array operator[]: 647.8 ns (+/- 0.1ns) span operator[] : 657.6 ns (+/- 0.5ns) span unsafe indexing: 648.2 ns (+/- 0.2ns) span range : 648.1 ns (+/- 0.1ns) These are the most stable tests I have, with the least trial-to-trial variation, and show about a 1.5% speed hit on the bounds-checked span access itself, which I think will be truly un-measurable in the context of being interleaved with any other operations that you do with the data you pull from the span. Mac Intel, Apple Clang 17, on my (old) personal laptop: (much more variable timing, probably from MacOS scheduler quirks) pointer operator[]: 929.2 ns (+/- 6.7ns) std::array operator[]: 913.1 ns (+/- 20.6ns) span operator[] : 905.8 ns (+/- 13.3ns) span unsafe indexing: 913.9 ns (+/- 16.6ns) span range : 916.4 ns (+/- 20.3ns) You can see that here there is no obvious penalty, in fact it appears a little faster, but all within the timing uncertainty of the multiple trials, so statistically it's hard to discern any penalty. And a couple more for good measure from our CI, but note that because these are uncontrolled machines somewhere on the GitHub cloud, the timings might not be as reliable: Windows, MSVS 2022: pointer operator[]: 3716.3 ns (+/- 6.3ns) std::array operator[]: 3715.5 ns (+/- 3.4ns) span operator[] : 3715.6 ns (+/- 2.6ns) span unsafe indexing: 3712.1 ns (+/- 0.7ns) span range : 3714.2 ns (+/- 2.9ns) Linux, gcc-14, C++20: pointer operator[]: 1130.9 ns (+/- 0.2ns), 884.2 k/s std::array operator[]: 1132.0 ns (+/- 0.4ns), 883.4 k/s span operator[] : 1133.7 ns (+/- 0.4ns), 882.1 k/s span unsafe indexing: 1134.2 ns (+/- 1.6ns), 881.7 k/s span range : 1133.9 ns (+/- 0.7ns), 881.9 k/s MacOS ARM: pointer operator[]: 3456.6 ns (+/- 7.5ns) std::array operator[]: 3466.8 ns (+/- 12.2ns) span operator[] : 3610.9 ns (+/- 11.0ns) span unsafe indexing: 3607.4 ns (+/- 4.9ns) span range : 3612.4 ns (+/- 12.2ns) Windows with MSVS and Linux with newer g++ don't appear to show any penalty, and the bracketing of trial times indicates that maybe it's consistent enough to be meaningful? I can't think of anything I'm doing wrong here that would throw off the timing or disable the range checking on these tests. For MacOS ARM, the span looks like it has about a 4% penalty versus raw pointers? But OTOH, span bounds-checked vs non-checked vs range-for are all the same, so maybe the speed vs raw pointer is something else entirely? Also please note that a preferred way to avoid these extra bounds checks entirely is to change an index-oriented loop like span s; for (size_t i = 0; i < s.size(); ++i) foo(s[i]); // maybe bounds check on each iteration? to a range based loop: span s; for (auto& v : s) foo(v); which should be inherently safe and require no in-loop checks at all. --------- Signed-off-by: Larry Gritz <lg@larrygritz.com> Signed-off-by: Vlad (Kuzmin) Erium <libalias@gmail.com> Signed-off-by: Vlad <shaamaan@gmail.com>
1 parent aa2bd86 commit 9897658

10 files changed

Lines changed: 308 additions & 30 deletions

File tree

src/build-scripts/ci-benchmark.bash

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,14 @@ ls build
1313
ls $BUILD_BIN_DIR
1414

1515
mkdir -p build/benchmarks
16-
for t in image_span_test ; do
16+
for t in image_span_test span_test ; do
1717
echo
1818
echo
1919
echo "$t"
2020
echo "========================================================"
21-
${BUILD_BIN_DIR}/$t > build/benchmarks/$t.out
22-
cat build/benchmarks/$t.out
21+
OpenImageIO_CI=0 ${BUILD_BIN_DIR}/$t | tee build/benchmarks/$t.out
22+
# Note: set OpenImageIO_CI=0 to avoid CI-specific automatic reduction of
23+
# the number of trials and iterations.
2324
echo "========================================================"
2425
echo "========================================================"
2526
echo

src/cmake/compiler.cmake

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -499,12 +499,12 @@ endif ()
499499
# https://cheatsheetseries.owasp.org/cheatsheets/C-Based_Toolchain_Hardening_Cheat_Sheet.html
500500

501501
if (${CMAKE_BUILD_TYPE} STREQUAL "Debug")
502-
set (${PROJ_NAME}_HARDENING_DEFAULT 2)
502+
set (${PROJ_NAME}_HARDENING_DEFAULT 2) # Extensive
503503
else ()
504-
set (${PROJ_NAME}_HARDENING_DEFAULT 1)
504+
set (${PROJ_NAME}_HARDENING_DEFAULT 1) # Fast
505505
endif ()
506506
set_cache (${PROJ_NAME}_HARDENING ${${PROJ_NAME}_HARDENING_DEFAULT}
507-
"Turn on security hardening features 0, 1, 2, 3")
507+
"Turn on security hardening features 0=none, 1=fast, 2=extensive, 3=debug")
508508
# Implementation:
509509
add_compile_definitions (${PROJ_NAME}_HARDENING_DEFAULT=${${PROJ_NAME}_HARDENING})
510510
if (${PROJ_NAME}_HARDENING GREATER_EQUAL 1)

src/include/OpenImageIO/dassert.h

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,161 @@
99
#include <cstdio>
1010
#include <cstdlib>
1111

12+
#include <OpenImageIO/oiioversion.h>
1213
#include <OpenImageIO/platform.h>
1314

1415

16+
17+
// General resources about security and hardening for C++:
18+
//
19+
// https://best.openssf.org/Compiler-Hardening-Guides/Compiler-Options-Hardening-Guide-for-C-and-C++.html
20+
// https://www.gnu.org/software/libc/manual/html_node/Source-Fortification.html
21+
// https://gcc.gnu.org/onlinedocs/libstdc++/manual/using_macros.html
22+
// https://libcxx.llvm.org/Hardening.html
23+
// https://cheatsheetseries.owasp.org/cheatsheets/C-Based_Toolchain_Hardening_Cheat_Sheet.html
24+
// https://stackoverflow.com/questions/13544512/what-is-the-most-hardened-set-of-options-for-gcc-compiling-c-c
25+
// https://medium.com/@simontoth/daily-bit-e-of-c-hardened-mode-of-standard-library-implementations-18be2422c372
26+
// https://en.cppreference.com/w/cpp/contract
27+
// https://en.cppreference.com/w/cpp/language/contracts
28+
29+
30+
31+
// Define hardening levels for OIIO: which checks should we do?
32+
// NONE - YOLO mode, no extra checks (not recommended)
33+
// FAST - Minimal checks that have low performance impact
34+
// EXTENSIVE - More thorough checks, may impact performance
35+
// DEBUG - Maximum checks, for debugging purposes
36+
#define OIIO_HARDENING_NONE 0
37+
#define OIIO_HARDENING_FAST 1
38+
#define OIIO_HARDENING_EXTENSIVE 2
39+
#define OIIO_HARDENING_DEBUG 3
40+
41+
// OIIO_HARDENING_DEFAULT defines the default hardening level we actually use.
42+
// By default, we use FAST for release builds and DEBUG for debug builds. But
43+
// it can be overridden:
44+
// - For OIIO internals, at OIIO build time with the `OIIO_HARDENING` CMake
45+
// variable.
46+
// - For other projects using OIIO's headers, any translation unit may
47+
// override this by defining OIIO_HARDENING_DEFAULT before including any
48+
// OIIO headers. But note that this only affects calls to inline functions
49+
// or templates defined in the headers. Non-inline functions compiled into
50+
// the OIIO library itself will have been compiled with whatever hardening
51+
// level was selected when the library was built.
52+
#ifndef OIIO_HARDENING_DEFAULT
53+
# ifdef NDEBUG
54+
# define OIIO_HARDENING_DEFAULT OIIO_HARDENING_FAST
55+
# else
56+
# define OIIO_HARDENING_DEFAULT OIIO_HARDENING_DEBUG
57+
# endif
58+
#endif
59+
60+
61+
// Choices for what to do when a contract assertion fails.
62+
// This mimics the C++26 standard's std::contract behavior.
63+
#define OIIO_ASSERTION_RESPONSE_IGNORE 0
64+
#define OIIO_ASSERTION_RESPONSE_OBSERVE 1
65+
#define OIIO_ASSERTION_RESPONSE_ENFORCE 2
66+
#define OIIO_ASSERTION_RESPONSE_QUICK_ENFORCE 3
67+
68+
// OIIO_ASSERTION_RESPONSE_DEFAULT defines the default response to failed
69+
// contract assertions. By default, we enforce them, UNLESS we are a release
70+
// mode build that has set the hardening mode to NONE. But any translation
71+
// unit (including clients of OIIO) may override this by defining
72+
// OIIO_ASSERTION_RESPONSE_DEFAULT before including any OIIO headers. But note
73+
// that this only affects calls to inline functions or templates defined in
74+
// the headers. Non-inline functions compiled into the OIIO library itself
75+
// will have been compiled with whatever response was selected when the
76+
// library was built.
77+
#ifndef OIIO_ASSERTION_RESPONSE_DEFAULT
78+
# if OIIO_HARDENING_DEFAULT == OIIO_HARDENING_NONE && defined(NDEBUG)
79+
# define OIIO_ASSERTION_RESPONSE_DEFAULT OIIO_ASSERTION_RESPONSE_IGNORE
80+
# else
81+
# define OIIO_ASSERTION_RESPONSE_DEFAULT OIIO_ASSERTION_RESPONSE_ENFORCE
82+
# endif
83+
#endif
84+
85+
86+
87+
// `OIIO_CONTRACT_ASSERT(condition)` checks if the condition is met, and if
88+
// not, calls the contract violation handler with the appropriate response.
89+
// `OIIO_CONTRACT_ASSERT_MSG(condition, msg)` is the same, but allows a
90+
// different message to be passed to the handler.
91+
#if OIIO_ASSERTION_RESPONSE_DEFAULT == OIIO_ASSERTION_RESPONSE_IGNORE
92+
# define OIIO_CONTRACT_ASSERT_MSG(condition, message) (void)0
93+
#elif OIIO_ASSERTION_RESPONSE_DEFAULT == OIIO_ASSERTION_RESPONSE_QUICK_ENFORCE
94+
# define OIIO_CONTRACT_ASSERT_MSG(condition, message) \
95+
(OIIO_LIKELY(condition) ? ((void)0) : (std::abort(), (void)0))
96+
#elif OIIO_ASSERTION_RESPONSE_DEFAULT == OIIO_ASSERTION_RESPONSE_OBSERVE
97+
# define OIIO_CONTRACT_ASSERT_MSG(condition, message) \
98+
(OIIO_LIKELY(condition) ? ((void)0) \
99+
: (OIIO::contract_violation_handler( \
100+
__FILE__ ":" OIIO_STRINGIZE(__LINE__), \
101+
OIIO_PRETTY_FUNCTION, message), \
102+
(void)0))
103+
#elif OIIO_ASSERTION_RESPONSE_DEFAULT == OIIO_ASSERTION_RESPONSE_ENFORCE
104+
# define OIIO_CONTRACT_ASSERT_MSG(condition, message) \
105+
(OIIO_LIKELY(condition) ? ((void)0) \
106+
: (OIIO::contract_violation_handler( \
107+
__FILE__ ":" OIIO_STRINGIZE(__LINE__), \
108+
OIIO_PRETTY_FUNCTION, message), \
109+
std::abort(), (void)0))
110+
#else
111+
# error "Unknown OIIO_ASSERTION_RESPONSE_DEFAULT"
112+
#endif
113+
114+
#define OIIO_CONTRACT_ASSERT(condition) \
115+
OIIO_CONTRACT_ASSERT_MSG(condition, #condition)
116+
117+
// Macros to use to select whether or not to do a contract check, based on the
118+
// hardening level:
119+
// - OIIO_HARDENING_ASSERT_FAST : only checks contract for >= FAST hardening.
120+
// - OIIO_HARDENING_ASSERT_EXTENSIVE : only checks contract for >= EXTENSIVE.
121+
// - OIIO_HARDENING_ASSERT_DEBUG : only checks contract for DEBUG hardening.
122+
#if OIIO_HARDENING_DEFAULT >= OIIO_HARDENING_FAST
123+
# define OIIO_HARDENING_ASSERT_FAST_MSG(condition, message) \
124+
OIIO_CONTRACT_ASSERT_MSG(condition, message)
125+
#else
126+
# define OIIO_HARDENING_ASSERT_FAST_MSG(...) (void)0
127+
#endif
128+
129+
#if OIIO_HARDENING_DEFAULT >= OIIO_HARDENING_EXTENSIVE
130+
# define OIIO_HARDENING_ASSERT_EXTENSIVE_MSG(condition, message) \
131+
OIIO_CONTRACT_ASSERT_MSG(condition, message)
132+
#else
133+
# define OIIO_HARDENING_ASSERT_EXTENSIVE_MSG(...) (void)0
134+
#endif
135+
136+
#if OIIO_HARDENING_DEFAULT >= OIIO_HARDENING_DEBUG
137+
# define OIIO_HARDENING_ASSERT_DEBUG_MSG(condition, message) \
138+
OIIO_CONTRACT_ASSERT_MSG(condition, message)
139+
#else
140+
# define OIIO_HARDENING_ASSERT_DEBUG_MSG(...) (void)0
141+
#endif
142+
143+
#define OIIO_HARDENING_ASSERT_NONE(condition) \
144+
OIIO_HARDENING_ASSERT_NONE_MSG(condition, #condition)
145+
#define OIIO_HARDENING_ASSERT_FAST(condition) \
146+
OIIO_HARDENING_ASSERT_FAST_MSG(condition, #condition)
147+
#define OIIO_HARDENING_ASSERT_EXTENSIVE(condition) \
148+
OIIO_HARDENING_ASSERT_EXTENSIVE_MSG(condition, #condition)
149+
#define OIIO_HARDENING_ASSERT_DEBUG(condition) \
150+
OIIO_HARDENING_ASSERT_DEBUG_MSG(condition, #condition)
151+
152+
153+
OIIO_NAMESPACE_3_1_BEGIN
154+
// Internal contract assertion handler
155+
OIIO_UTIL_API void
156+
contract_violation_handler(const char* location, const char* function,
157+
const char* msg = "");
158+
OIIO_NAMESPACE_3_1_END
159+
160+
OIIO_NAMESPACE_BEGIN
161+
#ifndef OIIO_DOXYGEN
162+
using v3_1::contract_violation_handler;
163+
#endif
164+
OIIO_NAMESPACE_END
165+
166+
15167
/// OIIO_ABORT_IF_DEBUG is a call to abort() for debug builds, but does
16168
/// nothing for release builds.
17169
#ifndef NDEBUG

src/include/OpenImageIO/hash.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -247,8 +247,11 @@ strhash (string_view s)
247247
size_t len = s.length();
248248
if (! len) return 0;
249249
unsigned int h = 0;
250-
for (size_t i = 0; i < len; ++i) {
251-
h += (unsigned char)(s[i]);
250+
for (auto c : s) {
251+
// Note: by using range for here, instead of looping over indices and
252+
// calling operator[] to get each char, we avoid the bounds checking
253+
// that operator[] does.
254+
h += (unsigned char)(c);
252255
h += h << 10;
253256
h ^= h >> 6;
254257
}

src/include/OpenImageIO/image_span.h

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -271,18 +271,30 @@ template<typename T, size_t Rank = 4> class image_span {
271271
/// Return a pointer to the value at channel c, pixel (x,y,z).
272272
inline T* getptr(int c, int x, int y, int z = 0) const
273273
{
274-
// Bounds check in debug mode
275-
OIIO_DASSERT(unsigned(c) < unsigned(nchannels())
276-
&& unsigned(x) < unsigned(width())
277-
&& unsigned(y) < unsigned(height())
278-
&& unsigned(z) < unsigned(depth()));
279274
if constexpr (Rank == 2) {
280275
OIIO_DASSERT(y == 0 && z == 0);
276+
OIIO_CONTRACT_ASSERT(unsigned(c) < unsigned(nchannels())
277+
&& unsigned(x) < unsigned(width()));
278+
return (T*)((char*)data() + c * chanstride());
281279
} else if constexpr (Rank == 3) {
282280
OIIO_DASSERT(z == 0);
281+
OIIO_CONTRACT_ASSERT(unsigned(c) < unsigned(nchannels())
282+
&& unsigned(x) < unsigned(width())
283+
&& unsigned(y) < unsigned(height()));
284+
return (T*)((char*)data() + c * chanstride() + x * xstride()
285+
+ y * ystride());
286+
} else {
287+
// Rank == 4
288+
OIIO_CONTRACT_ASSERT(unsigned(c) < unsigned(nchannels())
289+
&& unsigned(x) < unsigned(width())
290+
&& unsigned(y) < unsigned(height())
291+
&& unsigned(z) < unsigned(depth()));
292+
return (T*)((char*)data() + c * chanstride() + x * xstride()
293+
+ y * ystride() + z * zstride());
283294
}
284-
return (T*)((char*)data() + c * chanstride() + x * xstride()
285-
+ y * ystride() + z * zstride());
295+
#ifdef __INTEL_COMPILER
296+
return nullptr; // should never get here, but icc is confused
297+
#endif
286298
}
287299

288300
/// Return a pointer to the value at channel 0, pixel (x,y,z).

src/include/OpenImageIO/span.h

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -207,28 +207,28 @@ class span {
207207
/// optimized builds, there is no bounds check. Note: this is different
208208
/// from C++ std::span, which never bounds checks `operator[]`.
209209
constexpr reference operator[] (size_type idx) const {
210-
OIIO_DASSERT(idx < m_size && "OIIO::span::operator[] range check");
210+
OIIO_CONTRACT_ASSERT(idx < m_size);
211211
return m_data[idx];
212212
}
213213
constexpr reference operator() (size_type idx) const {
214-
OIIO_DASSERT(idx < m_size && "OIIO::span::operator() range check");
214+
OIIO_CONTRACT_ASSERT(idx < m_size);
215215
return m_data[idx];
216216
}
217217
/// Bounds-checked access, throws an assertion if out of range.
218218
reference at (size_type idx) const {
219219
if (idx >= size())
220-
throw (std::out_of_range ("OpenImageIO::span::at"));
220+
throw (std::out_of_range ("OIIO::span::at"));
221221
return m_data[idx];
222222
}
223223

224224
/// The first element of the span.
225225
constexpr reference front() const noexcept {
226-
OIIO_DASSERT(m_size >= 1);
226+
OIIO_CONTRACT_ASSERT(m_size >= 1);
227227
return m_data[0];
228228
}
229229
/// The last element of the span.
230230
constexpr reference back() const noexcept {
231-
OIIO_DASSERT(m_size >= 1);
231+
OIIO_CONTRACT_ASSERT(m_size >= 1);
232232
return m_data[size() - 1];
233233
}
234234

@@ -374,14 +374,16 @@ class span_strided {
374374
constexpr stride_type stride() const noexcept { return m_stride; }
375375

376376
constexpr reference operator[] (size_type idx) const {
377+
OIIO_CONTRACT_ASSERT(idx < m_size);
377378
return m_data[m_stride*idx];
378379
}
379380
constexpr reference operator() (size_type idx) const {
381+
OIIO_CONTRACT_ASSERT(idx < m_size);
380382
return m_data[m_stride*idx];
381383
}
382384
reference at (size_type idx) const {
383385
if (idx >= size())
384-
throw (std::out_of_range ("OpenImageIO::span_strided::at"));
386+
throw (std::out_of_range ("OIIO::span_strided::at"));
385387
return m_data[m_stride*idx];
386388
}
387389
constexpr reference front() const noexcept { return m_data[0]; }

src/include/OpenImageIO/string_view.h

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include <stdexcept>
1515
#include <string>
1616

17+
#include <OpenImageIO/dassert.h>
1718
#include <OpenImageIO/export.h>
1819
#include <OpenImageIO/oiioversion.h>
1920
#include <OpenImageIO/platform.h>
@@ -205,11 +206,12 @@ class basic_string_view {
205206
/// Is the view empty, containing no characters?
206207
constexpr bool empty() const noexcept { return m_len == 0; }
207208

208-
/// Element access of an individual character. For debug build, does
209-
/// bounds check with assertion. For optimized builds, there is no bounds
210-
/// check. Note: this is different from C++ std::span, which never bounds
211-
/// checks `operator[]`.
212-
constexpr const_reference operator[](size_type pos) const { return m_chars[pos]; }
209+
/// Element access of an individual character. For debug or hardened
210+
/// builds, does bounds check with assertion.
211+
constexpr const_reference operator[](size_type pos) const {
212+
OIIO_CONTRACT_ASSERT(pos < m_len);
213+
return m_chars[pos];
214+
}
213215
/// Element access with bounds checking and exception if out of bounds.
214216
constexpr const_reference at(size_t pos) const
215217
{
@@ -218,9 +220,15 @@ class basic_string_view {
218220
return m_chars[pos];
219221
}
220222
/// The first character of the view.
221-
constexpr const_reference front() const { return m_chars[0]; }
223+
constexpr const_reference front() const {
224+
OIIO_CONTRACT_ASSERT(m_len >= 1);
225+
return m_chars[0];
226+
}
222227
/// The last character of the view.
223-
constexpr const_reference back() const { return m_chars[m_len - 1]; }
228+
constexpr const_reference back() const {
229+
OIIO_CONTRACT_ASSERT(m_len >= 1);
230+
return m_chars[m_len - 1];
231+
}
224232
/// Return the underlying data pointer to the first character.
225233
constexpr const_pointer data() const noexcept { return m_chars; }
226234

src/libutil/errorhandler.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,4 +58,15 @@ ErrorHandler::operator()(int errcode, const std::string& msg)
5858
fflush(stderr);
5959
}
6060

61+
62+
63+
void
64+
contract_violation_handler(const char* location, const char* function,
65+
const char* msg)
66+
{
67+
Strutil::print(stderr, "{} ({}): Contract assertion failed: {}\n", location,
68+
function, msg ? msg : "");
69+
fflush(stderr);
70+
}
71+
6172
OIIO_NAMESPACE_3_1_END

0 commit comments

Comments
 (0)