-
Notifications
You must be signed in to change notification settings - Fork 674
api: OIIO_CONTRACT_ASSERT and other hardening improvements #5006
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,9 +9,161 @@ | |
| #include <cstdio> | ||
| #include <cstdlib> | ||
|
|
||
| #include <OpenImageIO/oiioversion.h> | ||
| #include <OpenImageIO/platform.h> | ||
|
|
||
|
|
||
|
|
||
| // General resources about security and hardening for C++: | ||
| // | ||
| // https://best.openssf.org/Compiler-Hardening-Guides/Compiler-Options-Hardening-Guide-for-C-and-C++.html | ||
| // https://www.gnu.org/software/libc/manual/html_node/Source-Fortification.html | ||
| // https://gcc.gnu.org/onlinedocs/libstdc++/manual/using_macros.html | ||
| // https://libcxx.llvm.org/Hardening.html | ||
| // https://cheatsheetseries.owasp.org/cheatsheets/C-Based_Toolchain_Hardening_Cheat_Sheet.html | ||
| // https://stackoverflow.com/questions/13544512/what-is-the-most-hardened-set-of-options-for-gcc-compiling-c-c | ||
| // https://medium.com/@simontoth/daily-bit-e-of-c-hardened-mode-of-standard-library-implementations-18be2422c372 | ||
| // https://en.cppreference.com/w/cpp/contract | ||
| // https://en.cppreference.com/w/cpp/language/contracts | ||
|
|
||
|
|
||
|
|
||
| // Define hardening levels for OIIO: which checks should we do? | ||
| // NONE - YOLO mode, no extra checks (not recommended) | ||
| // FAST - Minimal checks that have low performance impact | ||
| // EXTENSIVE - More thorough checks, may impact performance | ||
| // DEBUG - Maximum checks, for debugging purposes | ||
| #define OIIO_HARDENING_NONE 0 | ||
| #define OIIO_HARDENING_FAST 1 | ||
| #define OIIO_HARDENING_EXTENSIVE 2 | ||
| #define OIIO_HARDENING_DEBUG 3 | ||
|
|
||
| // OIIO_HARDENING_DEFAULT defines the default hardening level we actually use. | ||
| // By default, we use FAST for release builds and DEBUG for debug builds. But | ||
| // it can be overridden: | ||
| // - For OIIO internals, at OIIO build time with the `OIIO_HARDENING` CMake | ||
| // variable. | ||
| // - For other projects using OIIO's headers, any translation unit may | ||
| // override this by defining OIIO_HARDENING_DEFAULT before including any | ||
| // OIIO headers. But note that this only affects calls to inline functions | ||
| // or templates defined in the headers. Non-inline functions compiled into | ||
| // the OIIO library itself will have been compiled with whatever hardening | ||
| // level was selected when the library was built. | ||
| #ifndef OIIO_HARDENING_DEFAULT | ||
| # ifdef NDEBUG | ||
| # define OIIO_HARDENING_DEFAULT OIIO_HARDENING_FAST | ||
| # else | ||
| # define OIIO_HARDENING_DEFAULT OIIO_HARDENING_DEBUG | ||
| # endif | ||
| #endif | ||
|
|
||
|
|
||
| // Choices for what to do when a contract assertion fails. | ||
| // This mimics the C++26 standard's std::contract behavior. | ||
| #define OIIO_ASSERTION_RESPONSE_IGNORE 0 | ||
| #define OIIO_ASSERTION_RESPONSE_OBSERVE 1 | ||
| #define OIIO_ASSERTION_RESPONSE_ENFORCE 2 | ||
| #define OIIO_ASSERTION_RESPONSE_QUICK_ENFORCE 3 | ||
|
|
||
| // OIIO_ASSERTION_RESPONSE_DEFAULT defines the default response to failed | ||
| // contract assertions. By default, we enforce them, UNLESS we are a release | ||
| // mode build that has set the hardening mode to NONE. But any translation | ||
| // unit (including clients of OIIO) may override this by defining | ||
| // OIIO_ASSERTION_RESPONSE_DEFAULT before including any OIIO headers. But note | ||
| // that this only affects calls to inline functions or templates defined in | ||
| // the headers. Non-inline functions compiled into the OIIO library itself | ||
| // will have been compiled with whatever response was selected when the | ||
| // library was built. | ||
| #ifndef OIIO_ASSERTION_RESPONSE_DEFAULT | ||
| # if OIIO_HARDENING_DEFAULT == OIIO_HARDENING_NONE && defined(NDEBUG) | ||
| # define OIIO_ASSERTION_RESPONSE_DEFAULT OIIO_ASSERTION_RESPONSE_IGNORE | ||
| # else | ||
| # define OIIO_ASSERTION_RESPONSE_DEFAULT OIIO_ASSERTION_RESPONSE_ENFORCE | ||
| # endif | ||
| #endif | ||
|
|
||
|
|
||
|
|
||
| // `OIIO_CONTRACT_ASSERT(condition)` checks if the condition is met, and if | ||
| // not, calls the contract violation handler with the appropriate response. | ||
| // `OIIO_CONTRACT_ASSERT_MSG(condition, msg)` is the same, but allows a | ||
| // different message to be passed to the handler. | ||
| #if OIIO_ASSERTION_RESPONSE_DEFAULT == OIIO_ASSERTION_RESPONSE_IGNORE | ||
| # define OIIO_CONTRACT_ASSERT_MSG(condition, message) (void)0 | ||
| #elif OIIO_ASSERTION_RESPONSE_DEFAULT == OIIO_ASSERTION_RESPONSE_QUICK_ENFORCE | ||
| # define OIIO_CONTRACT_ASSERT_MSG(condition, message) \ | ||
| (OIIO_LIKELY(condition) ? ((void)0) : (std::abort(), (void)0)) | ||
| #elif OIIO_ASSERTION_RESPONSE_DEFAULT == OIIO_ASSERTION_RESPONSE_OBSERVE | ||
| # define OIIO_CONTRACT_ASSERT_MSG(condition, message) \ | ||
| (OIIO_LIKELY(condition) ? ((void)0) \ | ||
| : (OIIO::contract_violation_handler( \ | ||
| __FILE__ ":" OIIO_STRINGIZE(__LINE__), \ | ||
| OIIO_PRETTY_FUNCTION, message), \ | ||
| (void)0)) | ||
| #elif OIIO_ASSERTION_RESPONSE_DEFAULT == OIIO_ASSERTION_RESPONSE_ENFORCE | ||
| # define OIIO_CONTRACT_ASSERT_MSG(condition, message) \ | ||
| (OIIO_LIKELY(condition) ? ((void)0) \ | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we want to one day move to
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, we can cross that bridge when we come to it. The good thing about using the ternary operator is that it lets use use these ASSERT style macros as if they were statements. It might take some thought about how to structure it using 'if' that won't syntactically break someplace where we've used it or would like to use it. |
||
| : (OIIO::contract_violation_handler( \ | ||
| __FILE__ ":" OIIO_STRINGIZE(__LINE__), \ | ||
| OIIO_PRETTY_FUNCTION, message), \ | ||
| std::abort(), (void)0)) | ||
| #else | ||
| # error "Unknown OIIO_ASSERTION_RESPONSE_DEFAULT" | ||
| #endif | ||
|
|
||
| #define OIIO_CONTRACT_ASSERT(condition) \ | ||
| OIIO_CONTRACT_ASSERT_MSG(condition, #condition) | ||
|
|
||
| // Macros to use to select whether or not to do a contract check, based on the | ||
| // hardening level: | ||
| // - OIIO_HARDENING_ASSERT_FAST : only checks contract for >= FAST hardening. | ||
| // - OIIO_HARDENING_ASSERT_EXTENSIVE : only checks contract for >= EXTENSIVE. | ||
| // - OIIO_HARDENING_ASSERT_DEBUG : only checks contract for DEBUG hardening. | ||
| #if OIIO_HARDENING_DEFAULT >= OIIO_HARDENING_FAST | ||
| # define OIIO_HARDENING_ASSERT_FAST_MSG(condition, message) \ | ||
| OIIO_CONTRACT_ASSERT_MSG(condition, message) | ||
| #else | ||
| # define OIIO_HARDENING_ASSERT_FAST_MSG(...) (void)0 | ||
| #endif | ||
|
|
||
| #if OIIO_HARDENING_DEFAULT >= OIIO_HARDENING_EXTENSIVE | ||
| # define OIIO_HARDENING_ASSERT_EXTENSIVE_MSG(condition, message) \ | ||
| OIIO_CONTRACT_ASSERT_MSG(condition, message) | ||
| #else | ||
| # define OIIO_HARDENING_ASSERT_EXTENSIVE_MSG(...) (void)0 | ||
| #endif | ||
|
|
||
| #if OIIO_HARDENING_DEFAULT >= OIIO_HARDENING_DEBUG | ||
| # define OIIO_HARDENING_ASSERT_DEBUG_MSG(condition, message) \ | ||
| OIIO_CONTRACT_ASSERT_MSG(condition, message) | ||
| #else | ||
| # define OIIO_HARDENING_ASSERT_DEBUG_MSG(...) (void)0 | ||
| #endif | ||
|
|
||
| #define OIIO_HARDENING_ASSERT_NONE(condition) \ | ||
| OIIO_HARDENING_ASSERT_NONE_MSG(condition, #condition) | ||
| #define OIIO_HARDENING_ASSERT_FAST(condition) \ | ||
| OIIO_HARDENING_ASSERT_FAST_MSG(condition, #condition) | ||
| #define OIIO_HARDENING_ASSERT_EXTENSIVE(condition) \ | ||
| OIIO_HARDENING_ASSERT_EXTENSIVE_MSG(condition, #condition) | ||
| #define OIIO_HARDENING_ASSERT_DEBUG(condition) \ | ||
| OIIO_HARDENING_ASSERT_DEBUG_MSG(condition, #condition) | ||
|
|
||
|
|
||
| OIIO_NAMESPACE_3_1_BEGIN | ||
| // Internal contract assertion handler | ||
| OIIO_UTIL_API void | ||
| contract_violation_handler(const char* location, const char* function, | ||
| const char* msg = ""); | ||
| OIIO_NAMESPACE_3_1_END | ||
|
|
||
| OIIO_NAMESPACE_BEGIN | ||
| #ifndef OIIO_DOXYGEN | ||
| using v3_1::contract_violation_handler; | ||
| #endif | ||
| OIIO_NAMESPACE_END | ||
|
|
||
|
|
||
| /// OIIO_ABORT_IF_DEBUG is a call to abort() for debug builds, but does | ||
| /// nothing for release builds. | ||
| #ifndef NDEBUG | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd probably remove this part of the comment since attempting to set the define like this will most likely lead to a lot of surprises. It'll give a false sense of accomplishment and wouldn't be a complete workaround. Probably best to instead update the build documentation with the correct way to set this for folks building on their own.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, what I'm trying to account for here is that these are public headers that might be used by other projects or apps. There's "the behavior of OIIO internals, determined at build time", and there's also "how MY software behaves when I'm using OIIO's utility classes."