status.h (17440B)
1 // Copyright (c) the JPEG XL Project Authors. All rights reserved. 2 // 3 // Use of this source code is governed by a BSD-style 4 // license that can be found in the LICENSE file. 5 6 #ifndef LIB_JXL_BASE_STATUS_H_ 7 #define LIB_JXL_BASE_STATUS_H_ 8 9 // Error handling: Status return type + helper macros. 10 11 #include <stdarg.h> 12 #include <stdint.h> 13 #include <stdio.h> 14 #include <stdlib.h> 15 16 #include <type_traits> 17 #include <utility> 18 19 #include "lib/jxl/base/common.h" 20 #include "lib/jxl/base/compiler_specific.h" 21 #include "lib/jxl/base/sanitizer_definitions.h" 22 23 #if JXL_ADDRESS_SANITIZER || JXL_MEMORY_SANITIZER || JXL_THREAD_SANITIZER 24 #include "sanitizer/common_interface_defs.h" // __sanitizer_print_stack_trace 25 #endif // defined(*_SANITIZER) 26 27 namespace jxl { 28 29 // Uncomment to abort when JXL_FAILURE or JXL_STATUS with a fatal error is 30 // reached: 31 // #define JXL_CRASH_ON_ERROR 32 33 #ifndef JXL_ENABLE_ASSERT 34 #define JXL_ENABLE_ASSERT 1 35 #endif 36 37 #ifndef JXL_ENABLE_CHECK 38 #define JXL_ENABLE_CHECK 1 39 #endif 40 41 // Pass -DJXL_DEBUG_ON_ERROR at compile time to print debug messages when a 42 // function returns JXL_FAILURE or calls JXL_NOTIFY_ERROR. Note that this is 43 // irrelevant if you also pass -DJXL_CRASH_ON_ERROR. 44 #if defined(JXL_DEBUG_ON_ERROR) || defined(JXL_CRASH_ON_ERROR) 45 #undef JXL_DEBUG_ON_ERROR 46 #define JXL_DEBUG_ON_ERROR 1 47 #else // JXL_DEBUG_ON_ERROR || JXL_CRASH_ON_ERROR 48 #ifdef NDEBUG 49 #define JXL_DEBUG_ON_ERROR 0 50 #else // NDEBUG 51 #define JXL_DEBUG_ON_ERROR 1 52 #endif // NDEBUG 53 #endif // JXL_DEBUG_ON_ERROR || JXL_CRASH_ON_ERROR 54 55 // Pass -DJXL_DEBUG_ON_ALL_ERROR at compile time to print debug messages on 56 // all error (fatal and non-fatal) status. This implies JXL_DEBUG_ON_ERROR. 57 #if defined(JXL_DEBUG_ON_ALL_ERROR) 58 #undef JXL_DEBUG_ON_ALL_ERROR 59 #define JXL_DEBUG_ON_ALL_ERROR 1 60 // JXL_DEBUG_ON_ALL_ERROR implies JXL_DEBUG_ON_ERROR too. 61 #undef JXL_DEBUG_ON_ERROR 62 #define JXL_DEBUG_ON_ERROR 1 63 #else // JXL_DEBUG_ON_ALL_ERROR 64 #define JXL_DEBUG_ON_ALL_ERROR 0 65 #endif // JXL_DEBUG_ON_ALL_ERROR 66 67 // The Verbose level for the library 68 #ifndef JXL_DEBUG_V_LEVEL 69 #define JXL_DEBUG_V_LEVEL 0 70 #endif // JXL_DEBUG_V_LEVEL 71 72 // Pass -DJXL_DEBUG_ON_ABORT={0,1} to force disable/enable the debug messages on 73 // JXL_ASSERT, JXL_CHECK and JXL_ABORT. 74 #ifndef JXL_DEBUG_ON_ABORT 75 #define JXL_DEBUG_ON_ABORT JXL_DEBUG_ON_ERROR 76 #endif // JXL_DEBUG_ON_ABORT 77 78 #ifdef USE_ANDROID_LOGGER 79 #include <android/log.h> 80 #define LIBJXL_ANDROID_LOG_TAG ("libjxl") 81 inline void android_vprintf(const char* format, va_list args) { 82 char* message = nullptr; 83 int res = vasprintf(&message, format, args); 84 if (res != -1) { 85 __android_log_write(ANDROID_LOG_DEBUG, LIBJXL_ANDROID_LOG_TAG, message); 86 free(message); 87 } 88 } 89 #endif 90 91 // Print a debug message on standard error or android logs. You should use the 92 // JXL_DEBUG macro instead of calling Debug directly. This function returns 93 // false, so it can be used as a return value in JXL_FAILURE. 94 JXL_FORMAT(1, 2) 95 inline JXL_NOINLINE bool Debug(const char* format, ...) { 96 va_list args; 97 va_start(args, format); 98 #ifdef USE_ANDROID_LOGGER 99 android_vprintf(format, args); 100 #else 101 vfprintf(stderr, format, args); 102 #endif 103 va_end(args); 104 return false; 105 } 106 107 // Print a debug message on standard error if "enabled" is true. "enabled" is 108 // normally a macro that evaluates to 0 or 1 at compile time, so the Debug 109 // function is never called and optimized out in release builds. Note that the 110 // arguments are compiled but not evaluated when enabled is false. The format 111 // string must be a explicit string in the call, for example: 112 // JXL_DEBUG(JXL_DEBUG_MYMODULE, "my module message: %d", some_var); 113 // Add a header at the top of your module's .cc or .h file (depending on whether 114 // you have JXL_DEBUG calls from the .h as well) like this: 115 // #ifndef JXL_DEBUG_MYMODULE 116 // #define JXL_DEBUG_MYMODULE 0 117 // #endif JXL_DEBUG_MYMODULE 118 #define JXL_DEBUG_TMP(format, ...) \ 119 ::jxl::Debug(("%s:%d: " format "\n"), __FILE__, __LINE__, ##__VA_ARGS__) 120 121 #define JXL_DEBUG(enabled, format, ...) \ 122 do { \ 123 if (enabled) { \ 124 JXL_DEBUG_TMP(format, ##__VA_ARGS__); \ 125 } \ 126 } while (0) 127 128 // JXL_DEBUG version that prints the debug message if the global verbose level 129 // defined at compile time by JXL_DEBUG_V_LEVEL is greater or equal than the 130 // passed level. 131 #if JXL_DEBUG_V_LEVEL > 0 132 #define JXL_DEBUG_V(level, format, ...) \ 133 JXL_DEBUG(level <= JXL_DEBUG_V_LEVEL, format, ##__VA_ARGS__) 134 #else 135 #define JXL_DEBUG_V(level, format, ...) 136 #endif 137 138 // Warnings (via JXL_WARNING) are enabled by default in debug builds (opt and 139 // debug). 140 #ifdef JXL_DEBUG_WARNING 141 #undef JXL_DEBUG_WARNING 142 #define JXL_DEBUG_WARNING 1 143 #else // JXL_DEBUG_WARNING 144 #ifdef NDEBUG 145 #define JXL_DEBUG_WARNING 0 146 #else // JXL_DEBUG_WARNING 147 #define JXL_DEBUG_WARNING 1 148 #endif // NDEBUG 149 #endif // JXL_DEBUG_WARNING 150 #define JXL_WARNING(format, ...) \ 151 JXL_DEBUG(JXL_DEBUG_WARNING, format, ##__VA_ARGS__) 152 153 // Exits the program after printing a stack trace when possible. 154 JXL_NORETURN inline JXL_NOINLINE bool Abort() { 155 #if JXL_ADDRESS_SANITIZER || JXL_MEMORY_SANITIZER || JXL_THREAD_SANITIZER 156 // If compiled with any sanitizer print a stack trace. This call doesn't crash 157 // the program, instead the trap below will crash it also allowing gdb to 158 // break there. 159 __sanitizer_print_stack_trace(); 160 #endif // *_SANITIZER) 161 162 #if JXL_COMPILER_MSVC 163 __debugbreak(); 164 abort(); 165 #else 166 __builtin_trap(); 167 #endif 168 } 169 170 // Exits the program after printing file/line plus a formatted string. 171 #define JXL_ABORT(format, ...) \ 172 ((JXL_DEBUG_ON_ABORT) && ::jxl::Debug(("%s:%d: JXL_ABORT: " format "\n"), \ 173 __FILE__, __LINE__, ##__VA_ARGS__), \ 174 ::jxl::Abort()) 175 176 // Use this for code paths that are unreachable unless the code would change 177 // to make it reachable, in which case it will print a warning and abort in 178 // debug builds. In release builds no code is produced for this, so only use 179 // this if this path is really unreachable. 180 #define JXL_UNREACHABLE(format, ...) \ 181 do { \ 182 if (JXL_DEBUG_WARNING) { \ 183 ::jxl::Debug(("%s:%d: JXL_UNREACHABLE: " format "\n"), __FILE__, \ 184 __LINE__, ##__VA_ARGS__); \ 185 ::jxl::Abort(); \ 186 } else { \ 187 JXL_UNREACHABLE_BUILTIN; \ 188 } \ 189 } while (0) 190 191 // Does not guarantee running the code, use only for debug mode checks. 192 #if JXL_ENABLE_ASSERT 193 #define JXL_ASSERT(condition) \ 194 do { \ 195 if (!(condition)) { \ 196 JXL_DEBUG(JXL_DEBUG_ON_ABORT, "JXL_ASSERT: %s", #condition); \ 197 ::jxl::Abort(); \ 198 } \ 199 } while (0) 200 #else 201 #define JXL_ASSERT(condition) \ 202 do { \ 203 } while (0) 204 #endif 205 206 // Define JXL_IS_DEBUG_BUILD that denotes asan, msan and other debug builds, 207 // but not opt or release. 208 #ifndef JXL_IS_DEBUG_BUILD 209 #if !defined(NDEBUG) || defined(ADDRESS_SANITIZER) || \ 210 defined(MEMORY_SANITIZER) || defined(THREAD_SANITIZER) || \ 211 defined(__clang_analyzer__) 212 #define JXL_IS_DEBUG_BUILD 1 213 #else 214 #define JXL_IS_DEBUG_BUILD 0 215 #endif 216 #endif // JXL_IS_DEBUG_BUILD 217 218 // Same as above, but only runs in debug builds (builds where NDEBUG is not 219 // defined). This is useful for slower asserts that we want to run more rarely 220 // than usual. These will run on asan, msan and other debug builds, but not in 221 // opt or release. 222 #if JXL_IS_DEBUG_BUILD 223 #define JXL_DASSERT(condition) \ 224 do { \ 225 if (!(condition)) { \ 226 JXL_DEBUG(JXL_DEBUG_ON_ABORT, "JXL_DASSERT: %s", #condition); \ 227 ::jxl::Abort(); \ 228 } \ 229 } while (0) 230 #else 231 #define JXL_DASSERT(condition) \ 232 do { \ 233 } while (0) 234 #endif 235 236 // Always runs the condition, so can be used for non-debug calls. 237 #if JXL_ENABLE_CHECK 238 #define JXL_CHECK(condition) \ 239 do { \ 240 if (!(condition)) { \ 241 JXL_DEBUG(JXL_DEBUG_ON_ABORT, "JXL_CHECK: %s", #condition); \ 242 ::jxl::Abort(); \ 243 } \ 244 } while (0) 245 #else 246 #define JXL_CHECK(condition) \ 247 do { \ 248 (void)(condition); \ 249 } while (0) 250 #endif 251 252 // A jxl::Status value from a StatusCode or Status which prints a debug message 253 // when enabled. 254 #define JXL_STATUS(status, format, ...) \ 255 ::jxl::StatusMessage(::jxl::Status(status), "%s:%d: " format "\n", __FILE__, \ 256 __LINE__, ##__VA_ARGS__) 257 258 // Notify of an error but discard the resulting Status value. This is only 259 // useful for debug builds or when building with JXL_CRASH_ON_ERROR. 260 #define JXL_NOTIFY_ERROR(format, ...) \ 261 (void)JXL_STATUS(::jxl::StatusCode::kGenericError, "JXL_ERROR: " format, \ 262 ##__VA_ARGS__) 263 264 // An error Status with a message. The JXL_STATUS() macro will return a Status 265 // object with a kGenericError code, but the comma operator helps with 266 // clang-tidy inference and potentially with optimizations. 267 #define JXL_FAILURE(format, ...) \ 268 ((void)JXL_STATUS(::jxl::StatusCode::kGenericError, "JXL_FAILURE: " format, \ 269 ##__VA_ARGS__), \ 270 ::jxl::Status(::jxl::StatusCode::kGenericError)) 271 272 // Always evaluates the status exactly once, so can be used for non-debug calls. 273 // Returns from the current context if the passed Status expression is an error 274 // (fatal or non-fatal). The return value is the passed Status. 275 #define JXL_RETURN_IF_ERROR(status) \ 276 do { \ 277 ::jxl::Status jxl_return_if_error_status = (status); \ 278 if (!jxl_return_if_error_status) { \ 279 (void)::jxl::StatusMessage( \ 280 jxl_return_if_error_status, \ 281 "%s:%d: JXL_RETURN_IF_ERROR code=%d: %s\n", __FILE__, __LINE__, \ 282 static_cast<int>(jxl_return_if_error_status.code()), #status); \ 283 return jxl_return_if_error_status; \ 284 } \ 285 } while (0) 286 287 // As above, but without calling StatusMessage. Intended for bundles (see 288 // fields.h), which have numerous call sites (-> relevant for code size) and do 289 // not want to generate excessive messages when decoding partial headers. 290 #define JXL_QUIET_RETURN_IF_ERROR(status) \ 291 do { \ 292 ::jxl::Status jxl_return_if_error_status = (status); \ 293 if (!jxl_return_if_error_status) { \ 294 return jxl_return_if_error_status; \ 295 } \ 296 } while (0) 297 298 enum class StatusCode : int32_t { 299 // Non-fatal errors (negative values). 300 kNotEnoughBytes = -1, 301 302 // The only non-error status code. 303 kOk = 0, 304 305 // Fatal-errors (positive values) 306 kGenericError = 1, 307 }; 308 309 // Drop-in replacement for bool that raises compiler warnings if not used 310 // after being returned from a function. Example: 311 // Status LoadFile(...) { return true; } is more compact than 312 // bool JXL_MUST_USE_RESULT LoadFile(...) { return true; } 313 // In case of error, the status can carry an extra error code in its value which 314 // is split between fatal and non-fatal error codes. 315 class JXL_MUST_USE_RESULT Status { 316 public: 317 // We want implicit constructor from bool to allow returning "true" or "false" 318 // on a function when using Status. "true" means kOk while "false" means a 319 // generic fatal error. 320 // NOLINTNEXTLINE(google-explicit-constructor) 321 constexpr Status(bool ok) 322 : code_(ok ? StatusCode::kOk : StatusCode::kGenericError) {} 323 324 // NOLINTNEXTLINE(google-explicit-constructor) 325 constexpr Status(StatusCode code) : code_(code) {} 326 327 // We also want implicit cast to bool to check for return values of functions. 328 // NOLINTNEXTLINE(google-explicit-constructor) 329 constexpr operator bool() const { return code_ == StatusCode::kOk; } 330 331 constexpr StatusCode code() const { return code_; } 332 333 // Returns whether the status code is a fatal error. 334 constexpr bool IsFatalError() const { 335 return static_cast<int32_t>(code_) > 0; 336 } 337 338 private: 339 StatusCode code_; 340 }; 341 342 static constexpr Status OkStatus() { return Status(StatusCode::kOk); } 343 344 // Helper function to create a Status and print the debug message or abort when 345 // needed. 346 inline JXL_FORMAT(2, 3) Status 347 StatusMessage(const Status status, const char* format, ...) { 348 // This block will be optimized out when JXL_DEBUG_ON_ERROR and 349 // JXL_DEBUG_ON_ALL_ERROR are both disabled. 350 if ((JXL_DEBUG_ON_ERROR && status.IsFatalError()) || 351 (JXL_DEBUG_ON_ALL_ERROR && !status)) { 352 va_list args; 353 va_start(args, format); 354 #ifdef USE_ANDROID_LOGGER 355 android_vprintf(format, args); 356 #else 357 vfprintf(stderr, format, args); 358 #endif 359 va_end(args); 360 } 361 #ifdef JXL_CRASH_ON_ERROR 362 // JXL_CRASH_ON_ERROR means to Abort() only on non-fatal errors. 363 if (status.IsFatalError()) { 364 Abort(); 365 } 366 #endif // JXL_CRASH_ON_ERROR 367 return status; 368 } 369 370 template <typename T> 371 class JXL_MUST_USE_RESULT StatusOr { 372 static_assert(!std::is_convertible<StatusCode, T>::value && 373 !std::is_convertible<T, StatusCode>::value, 374 "You cannot make a StatusOr with a type convertible from or to " 375 "StatusCode"); 376 static_assert(std::is_move_constructible<T>::value && 377 std::is_move_assignable<T>::value, 378 "T must be move constructible and move assignable"); 379 380 public: 381 // NOLINTNEXTLINE(google-explicit-constructor) 382 StatusOr(StatusCode code) : code_(code) { 383 JXL_ASSERT(code_ != StatusCode::kOk); 384 } 385 386 // NOLINTNEXTLINE(google-explicit-constructor) 387 StatusOr(Status status) : StatusOr(status.code()) {} 388 389 // NOLINTNEXTLINE(google-explicit-constructor) 390 StatusOr(T&& value) : code_(StatusCode::kOk) { 391 new (&storage_.data_) T(std::move(value)); 392 } 393 394 StatusOr(StatusOr&& other) noexcept { 395 if (other.ok()) { 396 new (&storage_.data_) T(std::move(other.storage_.data_)); 397 } 398 code_ = other.code_; 399 } 400 401 StatusOr& operator=(StatusOr&& other) noexcept { 402 if (this == &other) return *this; 403 if (ok() && other.ok()) { 404 storage_.data_ = std::move(other.storage_.data_); 405 } else if (other.ok()) { 406 new (&storage_.data_) T(std::move(other.storage_.data_)); 407 } else if (ok()) { 408 storage_.data_.~T(); 409 } 410 code_ = other.code_; 411 return *this; 412 } 413 414 StatusOr(const StatusOr&) = delete; 415 StatusOr operator=(const StatusOr&) = delete; 416 417 bool ok() const { return code_ == StatusCode::kOk; } 418 Status status() const { return code_; } 419 420 // Only call this if you are absolutely sure that `ok()` is true. 421 // Ideally, never call this manually and rely on JXL_ASSIGN_OR_RETURN. 422 T value() && { 423 JXL_ASSERT(ok()); 424 return std::move(storage_.data_); 425 } 426 427 ~StatusOr() { 428 if (code_ == StatusCode::kOk) { 429 storage_.data_.~T(); 430 } 431 } 432 433 private: 434 union Storage { 435 char placeholder_; 436 T data_; 437 Storage() {} 438 ~Storage() {} 439 } storage_; 440 441 StatusCode code_; 442 }; 443 444 #define JXL_ASSIGN_OR_RETURN(lhs, statusor) \ 445 PRIVATE_JXL_ASSIGN_OR_RETURN_IMPL( \ 446 JXL_JOIN(assign_or_return_temporary_variable, __LINE__), lhs, statusor) 447 448 // NOLINTBEGIN(bugprone-macro-parentheses) 449 #define PRIVATE_JXL_ASSIGN_OR_RETURN_IMPL(name, lhs, statusor) \ 450 auto name = statusor; \ 451 JXL_RETURN_IF_ERROR(name.status()); \ 452 lhs = std::move(name).value(); 453 // NOLINTEND(bugprone-macro-parentheses) 454 455 // NB: do not use outside of tests / tools!!! 456 #define JXL_ASSIGN_OR_DIE(lhs, statusor) \ 457 PRIVATE_JXL_ASSIGN_OR_DIE_IMPL( \ 458 JXL_JOIN(assign_or_die_temporary_variable, __LINE__), lhs, statusor) 459 460 // NOLINTBEGIN(bugprone-macro-parentheses) 461 #define PRIVATE_JXL_ASSIGN_OR_DIE_IMPL(name, lhs, statusor) \ 462 auto name = statusor; \ 463 if (!name.ok()) jxl::Abort(); \ 464 lhs = std::move(name).value(); 465 // NOLINTEND(bugprone-macro-parentheses) 466 467 } // namespace jxl 468 469 #endif // LIB_JXL_BASE_STATUS_H_