status.h (14296B)
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 <cstdarg> 12 #include <cstdint> 13 #include <cstdio> 14 #include <cstdlib> 15 #include <type_traits> 16 #include <utility> 17 18 #include "lib/jxl/base/common.h" 19 #include "lib/jxl/base/compiler_specific.h" 20 21 namespace jxl { 22 23 // The Verbose level for the library 24 #ifndef JXL_DEBUG_V_LEVEL 25 #define JXL_DEBUG_V_LEVEL 0 26 #endif // JXL_DEBUG_V_LEVEL 27 28 #ifdef USE_ANDROID_LOGGER 29 #include <android/log.h> 30 #define LIBJXL_ANDROID_LOG_TAG ("libjxl") 31 inline void android_vprintf(const char* format, va_list args) { 32 char* message = nullptr; 33 int res = vasprintf(&message, format, args); 34 if (res != -1) { 35 __android_log_write(ANDROID_LOG_DEBUG, LIBJXL_ANDROID_LOG_TAG, message); 36 free(message); 37 } 38 } 39 #endif 40 41 // Print a debug message on standard error or android logs. You should use the 42 // JXL_DEBUG macro instead of calling Debug directly. This function returns 43 // false, so it can be used as a return value in JXL_FAILURE. 44 JXL_FORMAT(1, 2) 45 inline JXL_NOINLINE bool Debug(const char* format, ...) { 46 va_list args; 47 va_start(args, format); 48 #ifdef USE_ANDROID_LOGGER 49 android_vprintf(format, args); 50 #else 51 vfprintf(stderr, format, args); 52 #endif 53 va_end(args); 54 return false; 55 } 56 57 // Print a debug message on standard error if "enabled" is true. "enabled" is 58 // normally a macro that evaluates to 0 or 1 at compile time, so the Debug 59 // function is never called and optimized out in release builds. Note that the 60 // arguments are compiled but not evaluated when enabled is false. The format 61 // string must be a explicit string in the call, for example: 62 // JXL_DEBUG(JXL_DEBUG_MYMODULE, "my module message: %d", some_var); 63 // Add a header at the top of your module's .cc or .h file (depending on whether 64 // you have JXL_DEBUG calls from the .h as well) like this: 65 // #ifndef JXL_DEBUG_MYMODULE 66 // #define JXL_DEBUG_MYMODULE 0 67 // #endif JXL_DEBUG_MYMODULE 68 #define JXL_DEBUG_TMP(format, ...) \ 69 ::jxl::Debug(("%s:%d: " format "\n"), __FILE__, __LINE__, ##__VA_ARGS__) 70 71 #define JXL_DEBUG(enabled, format, ...) \ 72 do { \ 73 if (enabled) { \ 74 JXL_DEBUG_TMP(format, ##__VA_ARGS__); \ 75 } \ 76 } while (0) 77 78 // JXL_DEBUG version that prints the debug message if the global verbose level 79 // defined at compile time by JXL_DEBUG_V_LEVEL is greater or equal than the 80 // passed level. 81 #if JXL_DEBUG_V_LEVEL > 0 82 #define JXL_DEBUG_V(level, format, ...) \ 83 JXL_DEBUG(level <= JXL_DEBUG_V_LEVEL, format, ##__VA_ARGS__) 84 #else 85 #define JXL_DEBUG_V(level, format, ...) 86 #endif 87 88 #define JXL_WARNING(format, ...) \ 89 JXL_DEBUG(JXL_IS_DEBUG_BUILD, format, ##__VA_ARGS__) 90 91 #if JXL_IS_DEBUG_BUILD 92 // Exits the program after printing a stack trace when possible. 93 JXL_NORETURN inline JXL_NOINLINE bool Abort() { 94 JXL_PRINT_STACK_TRACE(); 95 JXL_CRASH(); 96 } 97 #endif 98 99 #if JXL_IS_DEBUG_BUILD 100 #define JXL_DEBUG_ABORT(format, ...) \ 101 do { \ 102 if (JXL_DEBUG_ON_ABORT) { \ 103 ::jxl::Debug(("%s:%d: JXL_DEBUG_ABORT: " format "\n"), __FILE__, \ 104 __LINE__, ##__VA_ARGS__); \ 105 } \ 106 ::jxl::Abort(); \ 107 } while (0); 108 #else 109 #define JXL_DEBUG_ABORT(format, ...) 110 #endif 111 112 // Use this for code paths that are unreachable unless the code would change 113 // to make it reachable, in which case it will print a warning and abort in 114 // debug builds. In release builds no code is produced for this, so only use 115 // this if this path is really unreachable. 116 #if JXL_IS_DEBUG_BUILD 117 #define JXL_UNREACHABLE(format, ...) \ 118 (::jxl::Debug(("%s:%d: JXL_UNREACHABLE: " format "\n"), __FILE__, __LINE__, \ 119 ##__VA_ARGS__), \ 120 ::jxl::Abort(), JXL_FAILURE(format, ##__VA_ARGS__)) 121 #else // JXL_IS_DEBUG_BUILD 122 #define JXL_UNREACHABLE(format, ...) \ 123 JXL_FAILURE("internal: " format, ##__VA_ARGS__) 124 #endif 125 126 // Only runs in debug builds (builds where NDEBUG is not 127 // defined). This is useful for slower asserts that we want to run more rarely 128 // than usual. These will run on asan, msan and other debug builds, but not in 129 // opt or release. 130 #if JXL_IS_DEBUG_BUILD 131 #define JXL_DASSERT(condition) \ 132 do { \ 133 if (!(condition)) { \ 134 JXL_DEBUG(JXL_DEBUG_ON_ABORT, "JXL_DASSERT: %s", #condition); \ 135 ::jxl::Abort(); \ 136 } \ 137 } while (0) 138 #else 139 #define JXL_DASSERT(condition) 140 #endif 141 142 // A jxl::Status value from a StatusCode or Status which prints a debug message 143 // when enabled. 144 #define JXL_STATUS(status, format, ...) \ 145 ::jxl::StatusMessage(::jxl::Status(status), "%s:%d: " format "\n", __FILE__, \ 146 __LINE__, ##__VA_ARGS__) 147 148 // Notify of an error but discard the resulting Status value. This is only 149 // useful for debug builds or when building with JXL_CRASH_ON_ERROR. 150 #define JXL_NOTIFY_ERROR(format, ...) \ 151 (void)JXL_STATUS(::jxl::StatusCode::kGenericError, "JXL_ERROR: " format, \ 152 ##__VA_ARGS__) 153 154 // An error Status with a message. The JXL_STATUS() macro will return a Status 155 // object with a kGenericError code, but the comma operator helps with 156 // clang-tidy inference and potentially with optimizations. 157 #define JXL_FAILURE(format, ...) \ 158 ((void)JXL_STATUS(::jxl::StatusCode::kGenericError, "JXL_FAILURE: " format, \ 159 ##__VA_ARGS__), \ 160 ::jxl::Status(::jxl::StatusCode::kGenericError)) 161 162 // Always evaluates the status exactly once, so can be used for non-debug calls. 163 // Returns from the current context if the passed Status expression is an error 164 // (fatal or non-fatal). The return value is the passed Status. 165 #define JXL_RETURN_IF_ERROR(status) \ 166 do { \ 167 ::jxl::Status jxl_return_if_error_status = (status); \ 168 if (!jxl_return_if_error_status) { \ 169 (void)::jxl::StatusMessage( \ 170 jxl_return_if_error_status, \ 171 "%s:%d: JXL_RETURN_IF_ERROR code=%d: %s\n", __FILE__, __LINE__, \ 172 static_cast<int>(jxl_return_if_error_status.code()), #status); \ 173 return jxl_return_if_error_status; \ 174 } \ 175 } while (0) 176 177 // As above, but without calling StatusMessage. Intended for bundles (see 178 // fields.h), which have numerous call sites (-> relevant for code size) and do 179 // not want to generate excessive messages when decoding partial headers. 180 #define JXL_QUIET_RETURN_IF_ERROR(status) \ 181 do { \ 182 ::jxl::Status jxl_return_if_error_status = (status); \ 183 if (!jxl_return_if_error_status) { \ 184 return jxl_return_if_error_status; \ 185 } \ 186 } while (0) 187 188 #if JXL_IS_DEBUG_BUILD 189 // Debug: fatal check. 190 #define JXL_ENSURE(condition) \ 191 do { \ 192 if (!(condition)) { \ 193 ::jxl::Debug("JXL_ENSURE: %s", #condition); \ 194 ::jxl::Abort(); \ 195 } \ 196 } while (0) 197 #else 198 // Release: non-fatal check of condition. If false, just return an error. 199 #define JXL_ENSURE(condition) \ 200 do { \ 201 if (!(condition)) { \ 202 return JXL_FAILURE("JXL_ENSURE: %s", #condition); \ 203 } \ 204 } while (0) 205 #endif 206 207 enum class StatusCode : int32_t { 208 // Non-fatal errors (negative values). 209 kNotEnoughBytes = -1, 210 211 // The only non-error status code. 212 kOk = 0, 213 214 // Fatal-errors (positive values) 215 kGenericError = 1, 216 }; 217 218 // Drop-in replacement for bool that raises compiler warnings if not used 219 // after being returned from a function. Example: 220 // Status LoadFile(...) { return true; } is more compact than 221 // bool JXL_MUST_USE_RESULT LoadFile(...) { return true; } 222 // In case of error, the status can carry an extra error code in its value which 223 // is split between fatal and non-fatal error codes. 224 class JXL_MUST_USE_RESULT Status { 225 public: 226 // We want implicit constructor from bool to allow returning "true" or "false" 227 // on a function when using Status. "true" means kOk while "false" means a 228 // generic fatal error. 229 // NOLINTNEXTLINE(google-explicit-constructor) 230 constexpr Status(bool ok) 231 : code_(ok ? StatusCode::kOk : StatusCode::kGenericError) {} 232 233 // NOLINTNEXTLINE(google-explicit-constructor) 234 constexpr Status(StatusCode code) : code_(code) {} 235 236 // We also want implicit cast to bool to check for return values of functions. 237 // NOLINTNEXTLINE(google-explicit-constructor) 238 constexpr operator bool() const { return code_ == StatusCode::kOk; } 239 240 constexpr StatusCode code() const { return code_; } 241 242 // Returns whether the status code is a fatal error. 243 constexpr bool IsFatalError() const { 244 return static_cast<int32_t>(code_) > 0; 245 } 246 247 private: 248 StatusCode code_; 249 }; 250 251 static constexpr Status OkStatus() { return Status(StatusCode::kOk); } 252 253 // Helper function to create a Status and print the debug message or abort when 254 // needed. 255 inline JXL_FORMAT(2, 3) Status 256 StatusMessage(const Status status, const char* format, ...) { 257 // This block will be optimized out when JXL_IS_DEBUG_BUILD is disabled. 258 if ((JXL_IS_DEBUG_BUILD && status.IsFatalError()) || 259 (JXL_DEBUG_ON_ALL_ERROR && !status)) { 260 va_list args; 261 va_start(args, format); 262 #ifdef USE_ANDROID_LOGGER 263 android_vprintf(format, args); 264 #else 265 vfprintf(stderr, format, args); 266 #endif 267 va_end(args); 268 } 269 #if JXL_CRASH_ON_ERROR 270 // JXL_CRASH_ON_ERROR means to Abort() only on non-fatal errors. 271 if (status.IsFatalError()) { 272 ::jxl::Abort(); 273 } 274 #endif // JXL_CRASH_ON_ERROR 275 return status; 276 } 277 278 template <typename T> 279 class JXL_MUST_USE_RESULT StatusOr { 280 static_assert(!std::is_convertible<StatusCode, T>::value && 281 !std::is_convertible<T, StatusCode>::value, 282 "You cannot make a StatusOr with a type convertible from or to " 283 "StatusCode"); 284 static_assert(std::is_move_constructible<T>::value && 285 std::is_move_assignable<T>::value, 286 "T must be move constructible and move assignable"); 287 288 public: 289 // NOLINTNEXTLINE(google-explicit-constructor) 290 StatusOr(StatusCode code) : code_(code) { 291 JXL_DASSERT(code_ != StatusCode::kOk); 292 } 293 294 // NOLINTNEXTLINE(google-explicit-constructor) 295 StatusOr(Status status) : StatusOr(status.code()) {} 296 297 // NOLINTNEXTLINE(google-explicit-constructor) 298 StatusOr(T&& value) : code_(StatusCode::kOk) { 299 new (&storage_.data_) T(std::move(value)); 300 } 301 302 StatusOr(StatusOr&& other) noexcept { 303 if (other.ok()) { 304 new (&storage_.data_) T(std::move(other.storage_.data_)); 305 } 306 code_ = other.code_; 307 } 308 309 StatusOr& operator=(StatusOr&& other) noexcept { 310 if (this == &other) return *this; 311 if (ok() && other.ok()) { 312 storage_.data_ = std::move(other.storage_.data_); 313 } else if (other.ok()) { 314 new (&storage_.data_) T(std::move(other.storage_.data_)); 315 } else if (ok()) { 316 storage_.data_.~T(); 317 } 318 code_ = other.code_; 319 return *this; 320 } 321 322 StatusOr(const StatusOr&) = delete; 323 StatusOr operator=(const StatusOr&) = delete; 324 325 bool ok() const { return code_ == StatusCode::kOk; } 326 Status status() const { return code_; } 327 328 // Only call this if you are absolutely sure that `ok()` is true. 329 // Never call this manually: rely on JXL_ASSIGN_OR. 330 T value_() && { 331 JXL_DASSERT(ok()); 332 return std::move(storage_.data_); 333 } 334 335 ~StatusOr() { 336 if (code_ == StatusCode::kOk) { 337 storage_.data_.~T(); 338 } 339 } 340 341 private: 342 union Storage { 343 char placeholder_; 344 T data_; 345 Storage() {} 346 ~Storage() {} 347 } storage_; 348 349 StatusCode code_; 350 }; 351 352 #define JXL_ASSIGN_OR_RETURN(lhs, statusor) \ 353 PRIVATE_JXL_ASSIGN_OR_RETURN_IMPL( \ 354 JXL_JOIN(assign_or_return_temporary_variable, __LINE__), lhs, statusor) 355 356 // NOLINTBEGIN(bugprone-macro-parentheses) 357 #define PRIVATE_JXL_ASSIGN_OR_RETURN_IMPL(name, lhs, statusor) \ 358 auto name = statusor; \ 359 JXL_RETURN_IF_ERROR(name.status()); \ 360 lhs = std::move(name).value_(); 361 // NOLINTEND(bugprone-macro-parentheses) 362 363 #define JXL_ASSIGN_OR_QUIT(lhs, statusor, message) \ 364 PRIVATE_JXL_ASSIGN_OR_QUIT_IMPL( \ 365 JXL_JOIN(assign_or_temporary_variable, __LINE__), lhs, statusor, \ 366 message) 367 368 // NOLINTBEGIN(bugprone-macro-parentheses) 369 #define PRIVATE_JXL_ASSIGN_OR_QUIT_IMPL(name, lhs, statusor, message) \ 370 auto name = statusor; \ 371 if (!name.ok()) { \ 372 QUIT(message); \ 373 } \ 374 lhs = std::move(name).value_(); 375 // NOLINTEND(bugprone-macro-parentheses) 376 377 } // namespace jxl 378 379 #endif // LIB_JXL_BASE_STATUS_H_