Pyrogenesis  trunk
status.h
Go to the documentation of this file.
1 /* Copyright (C) 2022 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 // an integral type allows defining error codes in separate headers,
165 // but is not as type-safe as an enum. use Lint's 'strong type' checking
166 // to catch errors such as Status Func() { return 1; }.
167 // this must be i64 because some functions may multiplex Status with
168 // file offsets/sizes in their return value.
169 typedef i64 Status;
170 
171 // associates a status code with a description [and errno_equivalent].
172 struct StatusDefinition // POD
173 {
175 
176  // typically a string literal; must remain valid until end of program.
177  const wchar_t* description;
178 
179  // omit initializer (or initialize to 0) if there is no errno equivalent.
181 };
182 
183 // retrieving description and errno_equivalent requires going through all
184 // StatusDefinition instances. we avoid dynamic memory allocation (which
185 // is problematic because status codes may be needed before _cinit) by
186 // organizing them into a linked list, with nodes residing in static storage.
187 // since modules may introduce many status codes, they are stored in an
188 // array, aka "bucket", which includes a link to the next bucket.
189 // initialized via STATUS_ADD_DEFINITIONS; opaque.
191 {
195 };
196 
197 /**
198  * (called via STATUS_ADD_DEFINITIONS)
199  *
200  * @param bucket is being added; its definitions and numDefinitions must
201  * already be initialized.
202  * @return previous bucket in list, suitable for initializing bucket->next.
203  *
204  * (this function must be callable as a static initializer; initializing
205  * next avoids the need for a separate dummy variable)
206  **/
208 
209 /**
210  * add a module's array of StatusDefinition to the list.
211  * typically invoked at file scope.
212  * @param definitions name (identifier) of the array
213  **/
214 #define STATUS_ADD_DEFINITIONS(definitions) static StatusDefinitionBucket definitions##_bucket = { definitions, ARRAY_SIZE(definitions), StatusAddDefinitions(&definitions##_bucket) }
215 
216 
217 /**
218  * generate textual description of a Status.
219  *
220  * @param buf destination buffer (allows generating strings with
221  * the code's numerical value if no definition is found)
222  * @param max_chars size of buffer [characters]
223  * @return buf (allows using this function in expressions)
224  **/
225 wchar_t* StatusDescription(Status status, wchar_t* buf, size_t max_chars);
226 
227 /**
228  * @return the errno equivalent of a Status.
229  *
230  * used in wposix - underlying functions return Status but must be
231  * translated to errno at e.g. the mmap interface level. higher-level code
232  * that calls mmap will in turn convert back to Status.
233  **/
234 extern int ErrnoFromStatus(Status status);
235 
236 /**
237  * @return Status equivalent of errno, or ERR::FAIL if there's no equivalent.
238  *
239  * NB: reset errno to 0 before calling POSIX functions to avoid confusion
240  * with previous errors.
241  **/
242 extern Status StatusFromErrno();
243 
244 // note: other conversion routines (e.g. to/from Win32) are implemented in
245 // the corresponding modules to keep this header portable.
246 
247 
248 //-----------------------------------------------------------------------------
249 // propagation macros
250 
251 // warn and return a status. use when an error is first detected to
252 // begin propagating it to callers.
253 #define WARN_RETURN(status)\
254  do\
255  {\
256  DEBUG_WARN_ERR(status);\
257  return status;\
258  }\
259  while(0)
260 
261 // warn if expression is negative, i.e. an error.
262 // (this macro is more convenient than ENSURE)
263 #define WARN_IF_ERR(expression)\
264  do\
265  {\
266  const Status status_ = (expression);\
267  if(status_ < 0)\
268  DEBUG_WARN_ERR(status_);\
269  }\
270  while(0)
271 
272 // return expression if it is negative, i.e. pass on errors to
273 // the caller. use when failures are common/expected.
274 #define RETURN_STATUS_IF_ERR(expression)\
275  do\
276  {\
277  const Status status_ = (expression);\
278  if(status_ < 0)\
279  return status_;\
280  }\
281  while(0)
282 
283 // warn and return expression if it is negative.
284 // use if a function doesn't raise warnings when it returns errors.
285 #define WARN_RETURN_STATUS_IF_ERR(expression)\
286  do\
287  {\
288  const Status status_ = (expression);\
289  if(status_ < 0)\
290  {\
291  DEBUG_WARN_ERR(status_);\
292  return status_;\
293  }\
294  }\
295  while(0)
296 
297 // warn and throw a status. use when an error is first detected to
298 // begin propagating it to callers.
299 #define WARN_THROW(status)\
300  do\
301  {\
302  DEBUG_WARN_ERR(status);\
303  throw status;\
304  }\
305  while(0)
306 
307 // throw expression if it is negative. use to propagate
308 // expected errors from constructors.
309 #define THROW_STATUS_IF_ERR(expression)\
310  do\
311  {\
312  const Status status_ = (expression);\
313  if(status_ < 0)\
314  throw status_;\
315  }\
316  while(0)
317 
318 // warn and throw expression if it is negative. use to propagate
319 // errors from constructors.
320 #define WARN_THROW_STATUS_IF_ERR(expression)\
321  do\
322  {\
323  const Status status_ = (expression);\
324  if(status_ < 0)\
325  {\
326  DEBUG_WARN_ERR(status_);\
327  throw status_;\
328  }\
329  }\
330  while(0)
331 
332 // if expression (typically the invocation of a callback) evaluates to:
333 // - INFO::OK, do nothing;
334 // - INFO::ALL_COMPLETE, return INFO::OK;
335 // - anything else, return that.
336 #define RETURN_STATUS_FROM_CALLBACK(expression)\
337  do\
338  {\
339  const Status status_ = (expression);\
340  if(status_ == INFO::ALL_COMPLETE)\
341  return INFO::OK;\
342  else if(status_ != INFO::OK)\
343  return status_;\
344  }\
345  while(0)
346 
347 // return 0 if expression is negative. use in functions that return pointers.
348 #define RETURN_0_IF_ERR(expression)\
349  do\
350  {\
351  const Status status_ = (expression);\
352  if(status_ < 0)\
353  return 0;\
354  }\
355  while(0)
356 
357 // warn if expression is false, i.e. zero.
358 #define WARN_IF_FALSE(expression)\
359  do\
360  {\
361  if(!(expression))\
362  debug_warn(L"FYI: WARN_IF_FALSE reports that a function failed. Feel free to ignore or suppress this warning.");\
363  }\
364  while(0)
365 
366 // warn and return 0 if expression is false, i.e. zero.
367 #define WARN_RETURN_0_IF_FALSE(expression)\
368  do\
369  {\
370  if(!(expression))\
371  {\
372  debug_warn(L"FYI: WARN_RETURN_0_IF_FALSE reports that a function failed. Feel free to ignore or suppress this warning.");\
373  return 0;\
374  }\
375  }\
376  while(0)
377 
378 
379 //-----------------------------------------------------------------------------
380 // shared status code definitions
381 
382 namespace INFO {
383 
384  const Status OK = 0;
385 
386  // note: these values are > 100 to allow multiplexing them with
387  // coroutine return values, which return completion percentage.
388 
389  // notify caller that nothing was done.
390  const Status SKIPPED = +100001;
391 
392  // function is incapable of doing the requested task with the given inputs.
393  // this implies SKIPPED, but also conveys a bit more information.
394  const Status CANNOT_HANDLE = +100002;
395 
396  // function is meant to be called repeatedly, and now indicates that
397  // all jobs are complete.
398  const Status ALL_COMPLETE = +100003;
399 
400 } // namespace INFO
401 
402 namespace ERR {
403 
404  const Status FAIL = -1; // unknown failure
405 
406  // general
407  const Status LOGIC = -100010;
408  const Status EXCEPTION = -100011;
409  const Status TIMED_OUT = -100012;
410  const Status REENTERED = -100013;
411  const Status CORRUPTED = -100014;
412  const Status ABORTED = -100015;
413 
414  // invalid values (usually function arguments)
415  const Status INVALID_ALIGNMENT = -100020;
416  const Status INVALID_OFFSET = -100021;
417  const Status INVALID_HANDLE = -100022;
418  const Status INVALID_POINTER = -100023;
419  const Status INVALID_SIZE = -100024;
420  const Status INVALID_FLAG = -100025;
421  const Status INVALID_PARAM = -100026;
422  const Status INVALID_VERSION = -100027;
423 
424  // system limitations
425  const Status AGAIN = -100030;
426  const Status LIMIT = -100031;
427  const Status NOT_SUPPORTED = -100032;
428  const Status NO_MEM = -100033;
429 
430  // these are for cases where we just want a distinct value to display and
431  // a symbolic name + string would be overkill (e.g. the various
432  // test cases in a validate() call). they are shared between multiple
433  // functions; when something fails, the stack trace will show in which
434  // one it was => these errors are unambiguous.
435  // there are 3 tiers - 1..9 are used in most functions, 11..19 are
436  // used in a function that calls another validator and 21..29 are
437  // for for functions that call 2 other validators (this avoids
438  // ambiguity as to which error actually happened where)
439  const Status _1 = -100101;
440  const Status _2 = -100102;
441  const Status _3 = -100103;
442  const Status _4 = -100104;
443  const Status _5 = -100105;
444  const Status _6 = -100106;
445  const Status _7 = -100107;
446  const Status _8 = -100108;
447  const Status _9 = -100109;
448  const Status _11 = -100111;
449  const Status _12 = -100112;
450  const Status _13 = -100113;
451  const Status _14 = -100114;
452  const Status _15 = -100115;
453  const Status _16 = -100116;
454  const Status _17 = -100117;
455  const Status _18 = -100118;
456  const Status _19 = -100119;
457  const Status _21 = -100121;
458  const Status _22 = -100122;
459  const Status _23 = -100123;
460  const Status _24 = -100124;
461  const Status _25 = -100125;
462  const Status _26 = -100126;
463  const Status _27 = -100127;
464  const Status _28 = -100128;
465  const Status _29 = -100129;
466 
467 } // namespace ERR
468 
469 #endif // #ifndef INCLUDED_STATUS
Definition: status.h:190
const Status _7
Definition: status.h:445
const Status LOGIC
Definition: status.h:407
int64_t i64
Definition: types.h:35
const Status _29
Definition: status.h:465
const Status _14
Definition: status.h:451
const Status _4
Definition: status.h:442
const Status _1
Definition: status.h:439
const Status _6
Definition: status.h:444
const Status _24
Definition: status.h:460
const Status OK
Definition: status.h:384
const Status _13
Definition: status.h:450
const Status _9
Definition: status.h:447
const Status _16
Definition: status.h:453
StatusDefinitionBucket * next
Definition: status.h:194
const Status TIMED_OUT
Definition: status.h:409
const Status _3
Definition: status.h:441
const Status CORRUPTED
Definition: status.h:411
StatusDefinitionBucket * StatusAddDefinitions(StatusDefinitionBucket *bucket)
(called via STATUS_ADD_DEFINITIONS)
Definition: status.cpp:40
const Status CANNOT_HANDLE
Definition: status.h:394
Status StatusFromErrno()
Definition: status.cpp:105
const Status INVALID_HANDLE
Definition: status.h:417
const Status ALL_COMPLETE
Definition: status.h:398
const Status AGAIN
Definition: status.h:425
Definition: debug.h:423
const Status _25
Definition: status.h:461
const Status ABORTED
Definition: status.h:412
const Status NOT_SUPPORTED
Definition: status.h:427
const Status INVALID_OFFSET
Definition: status.h:416
const Status _17
Definition: status.h:454
const Status INVALID_VERSION
Definition: status.h:422
const Status _21
Definition: status.h:457
const wchar_t * description
Definition: status.h:177
const Status LIMIT
Definition: status.h:426
const Status INVALID_POINTER
Definition: status.h:418
const Status _5
Definition: status.h:443
const Status _27
Definition: status.h:463
const Status REENTERED
Definition: status.h:410
size_t numDefinitions
Definition: status.h:193
const Status _15
Definition: status.h:452
const Status _19
Definition: status.h:456
const Status INVALID_PARAM
Definition: status.h:421
i64 Status
Error handling system.
Definition: status.h:169
const Status INVALID_SIZE
Definition: status.h:419
const Status INVALID_ALIGNMENT
Definition: status.h:415
int ErrnoFromStatus(Status status)
Definition: status.cpp:93
Introduction
Definition: debug.h:407
const Status SKIPPED
Definition: status.h:390
const Status _26
Definition: status.h:462
wchar_t * StatusDescription(Status status, wchar_t *buf, size_t max_chars)
generate textual description of a Status.
Definition: status.cpp:79
const Status _11
Definition: status.h:448
const Status _28
Definition: status.h:464
const Status _18
Definition: status.h:455
Definition: status.h:172
const StatusDefinition * definitions
Definition: status.h:192
Status status
Definition: status.h:174
const Status _8
Definition: status.h:446
const Status INVALID_FLAG
Definition: status.h:420
const Status _22
Definition: status.h:458
const Status FAIL
Definition: status.h:404
const Status _2
Definition: status.h:440
const Status EXCEPTION
Definition: status.h:408
const Status NO_MEM
Definition: status.h:428
const Status _23
Definition: status.h:459
const Status _12
Definition: status.h:449
int errno_equivalent
Definition: status.h:180