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