1 /* Copyright (C) 2011 Wildfire Games. 2 * 3 * Permission is hereby granted, free of charge, to any person obtaining 4 * a copy of this software and associated documentation files (the 5 * "Software"), to deal in the Software without restriction, including 6 * without limitation the rights to use, copy, modify, merge, publish, 7 * distribute, sublicense, and/or sell copies of the Software, and to 8 * permit persons to whom the Software is furnished to do so, subject to 9 * the following conditions: 10 * 11 * The above copyright notice and this permission notice shall be included 12 * in all copies or substantial portions of the Software. 13 * 14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 18 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 19 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 20 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 */ 22 23 /* 24 * error handling system: defines status codes, translates them to/from 25 * other schemes (e.g. errno), associates them with descriptive text, 26 * simplifies propagating errors / checking if functions failed. 27 */ 28 29 /** 30 31 Error handling system 32 33 34 Why Error Codes? 35 ---------------- 36 37 To convey information about what failed, the alternatives are unique 38 integral codes and direct pointers to descriptive text. Both occupy the 39 same amount of space, but codes are easier to internationalize. 40 41 42 Method of Propagating Errors 43 ---------------------------- 44 45 When a low-level function has failed, this must be conveyed to the 46 higher-level application logic across several functions on the call stack. 47 There are two alternatives: 48 1) check at each call site whether a function failed; 49 if so, return to the caller. 50 2) throw an exception. 51 52 We will discuss the advantages and disadvantages of exceptions, 53 which are the opposites of call site checking. 54 - performance: they shouldn't be used in time-critical code. 55 - predictability: exceptions can come up almost anywhere, 56 so it is hard to say what execution path will be taken. 57 - interoperability: not compatible with other languages. 58 + readability: cleans up code by separating application logic and 59 error handling. however, this is also a disadvantage because it 60 may be difficult to see at a glance if a piece of code does 61 error checking at all. 62 + visibility: errors are more likely to be seen than relying on 63 callers to check return codes; less reliant on discipline. 64 65 Both have their place. Our recommendation is to throw error code 66 exceptions when checking call sites and propagating errors becomes tedious. 67 However, inter-module boundaries should always return error codes for 68 interoperability with other languages. 69 70 71 Simplifying Call-Site Checking 72 ------------------------------ 73 74 As mentioned above, this approach requires discipline. We provide 75 "enforcer" macros to simplify this task by propagating errors to 76 the calling function. 77 78 Consider the following example: 79 Status status = doWork(); 80 if(status != INFO::OK) 81 return status; 82 This can be replaced by: 83 RETURN_STATUS_IF_ERR(doWork()); 84 85 This provides a visible sign that the code handles errors but 86 reduces clutter. 87 88 89 When to warn the user? 90 ---------------------- 91 92 When a function fails, there are 2 places we can raise a warning: 93 as soon as the error condition is known, or higher on the call stack. 94 95 We prefer the former because it is easier to ensure that all 96 possible return paths have been covered: search for all "return ERR::*" 97 or "return StatusFrom*" that are not followed by a "// NOWARN" comment. 98 The latter approach also risks multiple warnings along the 99 call stack for the same error. 100 101 Note the special case of "validator" functions that e.g. verify the 102 state of an object: we now discuss pros/cons of just returning errors 103 without warning, and having their callers take care of that. 104 + they typically have many return paths (-> increased code size) 105 - this is balanced by validators that have many call sites. 106 - we want all return statements wrapped for consistency and 107 easily checking if any were forgotten 108 - adding // NOWARN to each validator return statement would be tedious. 109 - there is no advantage to checking at the call site; the call stack 110 indicates which caller of the validator failed anyway. 111 Validator functions should therefore also use WARN_RETURN. 112 113 114 Numbering Scheme 115 ---------------- 116 117 Each module header defines its own error codes to avoid a full rebuild 118 whenever a new code is added. 119 120 Error codes start at -100000 (warnings are positive, but the 121 corresponding negative value should not be used to avoid confusion). 122 This scheme avoids collisions with all other known error codes. 123 124 Each header gets 100 possible values; the tens value may be 125 used to denote groups within that header. 126 127 The subsystem is denoted by the ten-thousands digit: 128 0 general 129 1 file 130 2 res (resource management) 131 3 sysdep (system-dependent) 132 4 win (Windows-specific) 133 134 To summarize: +/-1SHHCC (S=subsystem, HH=header, CC=code number) 135 136 10 general 137 00CC misc 138 03CC path 139 04CC debug 140 05CC debug_stl 141 06CC secure_crt 142 07CC wchar 143 144 11 file 145 01CC vfs 146 03CC file 147 04CC archive 148 149 12 res 150 00CC h_mgr 151 01CC tex 152 153 13 sysdep 154 00CC cpu 155 01CC os_cpu 156 157 14 win 158 00CC whrt 159 **/ 160 161 #ifndef INCLUDED_STATUS 162 #define INCLUDED_STATUS 163 164 #include "lib/lib_api.h" 165 166 // an integral type allows defining error codes in separate headers, 167 // but is not as type-safe as an enum. use Lint's 'strong type' checking 168 // to catch errors such as Status Func() { return 1; }. 169 // this must be i64 because some functions may multiplex Status with 170 // file offsets/sizes in their return value. 171 typedef i64 Status; 172 173 // associates a status code with a description [and errno_equivalent]. 174 struct StatusDefinition // POD 175 { 176 Status status; 177 178 // typically a string literal; must remain valid until end of program. 179 const wchar_t* description; 180 181 // omit initializer (or initialize to 0) if there is no errno equivalent. 182 int errno_equivalent; 183 }; 184 185 // retrieving description and errno_equivalent requires going through all 186 // StatusDefinition instances. we avoid dynamic memory allocation (which 187 // is problematic because status codes may be needed before _cinit) by 188 // organizing them into a linked list, with nodes residing in static storage. 189 // since modules may introduce many status codes, they are stored in an 190 // array, aka "bucket", which includes a link to the next bucket. 191 // initialized via STATUS_ADD_DEFINITIONS; opaque. 192 struct StatusDefinitionBucket // POD 193 { 194 const StatusDefinition* definitions; 195 size_t numDefinitions; 196 StatusDefinitionBucket* next; 197 }; 198 199 /** 200 * (called via STATUS_ADD_DEFINITIONS) 201 * 202 * @param bucket is being added; its definitions and numDefinitions must 203 * already be initialized. 204 * @return previous bucket in list, suitable for initializing bucket->next. 205 * 206 * (this function must be callable as a static initializer; initializing 207 * next avoids the need for a separate dummy variable) 208 **/ 209 LIB_API StatusDefinitionBucket* StatusAddDefinitions(StatusDefinitionBucket* bucket); 210 211 /** 212 * add a module's array of StatusDefinition to the list. 213 * typically invoked at file scope. 214 * @param definitions name (identifier) of the array 215 **/ 216 #define STATUS_ADD_DEFINITIONS(definitions) static StatusDefinitionBucket definitions##_bucket = { definitions, ARRAY_SIZE(definitions), StatusAddDefinitions(&definitions##_bucket) } 217 218 219 /** 220 * generate textual description of a Status. 221 * 222 * @param buf destination buffer (allows generating strings with 223 * the code's numerical value if no definition is found) 224 * @param max_chars size of buffer [characters] 225 * @return buf (allows using this function in expressions) 226 **/ 227 LIB_API wchar_t* StatusDescription(Status status, wchar_t* buf, size_t max_chars); 228 229 /** 230 * @return the errno equivalent of a Status. 231 * 232 * used in wposix - underlying functions return Status but must be 233 * translated to errno at e.g. the mmap interface level. higher-level code 234 * that calls mmap will in turn convert back to Status. 235 **/ 236 extern int ErrnoFromStatus(Status status); 237 238 /** 239 * @return Status equivalent of errno, or ERR::FAIL if there's no equivalent. 240 * 241 * NB: reset errno to 0 before calling POSIX functions to avoid confusion 242 * with previous errors. 243 **/ 244 extern Status StatusFromErrno(); 245 246 // note: other conversion routines (e.g. to/from Win32) are implemented in 247 // the corresponding modules to keep this header portable. 248 249 250 //----------------------------------------------------------------------------- 251 // propagation macros 252 253 // warn and return a status. use when an error is first detected to 254 // begin propagating it to callers. 255 #define WARN_RETURN(status)\ 256 do\ 257 {\ 258 DEBUG_WARN_ERR(status);\ 259 return status;\ 260 }\ 261 while(0) 262 263 // warn if expression is negative, i.e. an error. 264 // (this macro is more convenient than ENSURE) 265 #define WARN_IF_ERR(expression)\ 266 do\ 267 {\ 268 const Status status_ = (expression);\ 269 if(status_ < 0)\ 270 DEBUG_WARN_ERR(status_);\ 271 }\ 272 while(0) 273 274 // return expression if it is negative, i.e. pass on errors to 275 // the caller. use when failures are common/expected. 276 #define RETURN_STATUS_IF_ERR(expression)\ 277 do\ 278 {\ 279 const Status status_ = (expression);\ 280 if(status_ < 0)\ 281 return status_;\ 282 }\ 283 while(0) 284 285 // warn and return expression if it is negative. 286 // use if a function doesn't raise warnings when it returns errors. 287 #define WARN_RETURN_STATUS_IF_ERR(expression)\ 288 do\ 289 {\ 290 const Status status_ = (expression);\ 291 if(status_ < 0)\ 292 {\ 293 DEBUG_WARN_ERR(status_);\ 294 return status_;\ 295 }\ 296 }\ 297 while(0) 298 299 // warn and throw a status. use when an error is first detected to 300 // begin propagating it to callers. 301 #define WARN_THROW(status)\ 302 do\ 303 {\ 304 DEBUG_WARN_ERR(status);\ 305 throw status;\ 306 }\ 307 while(0) 308 309 // throw expression if it is negative. use to propagate 310 // expected errors from constructors. 311 #define THROW_STATUS_IF_ERR(expression)\ 312 do\ 313 {\ 314 const Status status_ = (expression);\ 315 if(status_ < 0)\ 316 throw status_;\ 317 }\ 318 while(0) 319 320 // warn and throw expression if it is negative. use to propagate 321 // errors from constructors. 322 #define WARN_THROW_STATUS_IF_ERR(expression)\ 323 do\ 324 {\ 325 const Status status_ = (expression);\ 326 if(status_ < 0)\ 327 {\ 328 DEBUG_WARN_ERR(status_);\ 329 throw status_;\ 330 }\ 331 }\ 332 while(0) 333 334 // if expression (typically the invocation of a callback) evaluates to: 335 // - INFO::OK, do nothing; 336 // - INFO::ALL_COMPLETE, return INFO::OK; 337 // - anything else, return that. 338 #define RETURN_STATUS_FROM_CALLBACK(expression)\ 339 do\ 340 {\ 341 const Status status_ = (expression);\ 342 if(status_ == INFO::ALL_COMPLETE)\ 343 return INFO::OK;\ 344 else if(status_ != INFO::OK)\ 345 return status_;\ 346 }\ 347 while(0) 348 349 // return 0 if expression is negative. use in functions that return pointers. 350 #define RETURN_0_IF_ERR(expression)\ 351 do\ 352 {\ 353 const Status status_ = (expression);\ 354 if(status_ < 0)\ 355 return 0;\ 356 }\ 357 while(0) 358 359 // warn if expression is false, i.e. zero. 360 #define WARN_IF_FALSE(expression)\ 361 do\ 362 {\ 363 if(!(expression))\ 364 debug_warn(L"FYI: WARN_IF_FALSE reports that a function failed. Feel free to ignore or suppress this warning.");\ 365 }\ 366 while(0) 367 368 // warn and return 0 if expression is false, i.e. zero. 369 #define WARN_RETURN_0_IF_FALSE(expression)\ 370 do\ 371 {\ 372 if(!(expression))\ 373 {\ 374 debug_warn(L"FYI: WARN_RETURN_0_IF_FALSE reports that a function failed. Feel free to ignore or suppress this warning.");\ 375 return 0;\ 376 }\ 377 }\ 378 while(0) 379 380 381 //----------------------------------------------------------------------------- 382 // shared status code definitions 383 384 namespace INFO { 385 386 const Status OK = 0; 387 388 // note: these values are > 100 to allow multiplexing them with 389 // coroutine return values, which return completion percentage. 390 391 // notify caller that nothing was done. 392 const Status SKIPPED = +100001; 393 394 // function is incapable of doing the requested task with the given inputs. 395 // this implies SKIPPED, but also conveys a bit more information. 396 const Status CANNOT_HANDLE = +100002; 397 398 // function is meant to be called repeatedly, and now indicates that 399 // all jobs are complete. 400 const Status ALL_COMPLETE = +100003; 401 402 } // namespace INFO 403 404 namespace ERR { 405 406 const Status FAIL = -1; // unknown failure 407 408 // general 409 const Status LOGIC = -100010; 410 const Status EXCEPTION = -100011; 411 const Status TIMED_OUT = -100012; 412 const Status REENTERED = -100013; 413 const Status CORRUPTED = -100014; 414 const Status ABORTED = -100015; 415 416 // invalid values (usually function arguments) 417 const Status INVALID_ALIGNMENT = -100020; 418 const Status INVALID_OFFSET = -100021; 419 const Status INVALID_HANDLE = -100022; 420 const Status INVALID_POINTER = -100023; 421 const Status INVALID_SIZE = -100024; 422 const Status INVALID_FLAG = -100025; 423 const Status INVALID_PARAM = -100026; 424 const Status INVALID_VERSION = -100027; 425 426 // system limitations 427 const Status AGAIN = -100030; 428 const Status LIMIT = -100031; 429 const Status NOT_SUPPORTED = -100032; 430 const Status NO_MEM = -100033; 431 432 // these are for cases where we just want a distinct value to display and 433 // a symbolic name + string would be overkill (e.g. the various 434 // test cases in a validate() call). they are shared between multiple 435 // functions; when something fails, the stack trace will show in which 436 // one it was => these errors are unambiguous. 437 // there are 3 tiers - 1..9 are used in most functions, 11..19 are 438 // used in a function that calls another validator and 21..29 are 439 // for for functions that call 2 other validators (this avoids 440 // ambiguity as to which error actually happened where) 441 const Status _1 = -100101; 442 const Status _2 = -100102; 443 const Status _3 = -100103; 444 const Status _4 = -100104; 445 const Status _5 = -100105; 446 const Status _6 = -100106; 447 const Status _7 = -100107; 448 const Status _8 = -100108; 449 const Status _9 = -100109; 450 const Status _11 = -100111; 451 const Status _12 = -100112; 452 const Status _13 = -100113; 453 const Status _14 = -100114; 454 const Status _15 = -100115; 455 const Status _16 = -100116; 456 const Status _17 = -100117; 457 const Status _18 = -100118; 458 const Status _19 = -100119; 459 const Status _21 = -100121; 460 const Status _22 = -100122; 461 const Status _23 = -100123; 462 const Status _24 = -100124; 463 const Status _25 = -100125; 464 const Status _26 = -100126; 465 const Status _27 = -100127; 466 const Status _28 = -100128; 467 const Status _29 = -100129; 468 469 } // namespace ERR 470 471 #endif // #ifndef INCLUDED_STATUS 472