2017-11-26

error handling

overview of programming error handling techniques

about what to do when routines can not quite calculate what they are supposed to

this document needs revision

common reasons for exceptional states

side effects, interfaces

resource unavailable, filesystem path not accessible, error response from remote interface

unexpected input

divide by zero, invalid data type, invalid data structure, invalid data encoding/format, missing data

ways to communicate exceptional stati in the program

single return value

using special return values: for example of specific value range, sign, type or a compound data structure

using output arguments: return value gives meta information about normal or exceptional result. output arguments store calculated data

flow

at each routine call, code is added to react to exceptional stati

checking code is possibly duplicated. stati not resolved in the current routine might be passed upwards in the call hierarchy, which interrupts composition

what to return where

output arguments storage locations may need to be allocated before a routine call. generally, this creates a dependency on values controled by the caller

consider the following type signatures

with the status value as an output argument
input-value ... output-value ... return-status-value -> output-value

output arguments are often specified together with input arguments, without any further distinction. it is more complicated to discern the purposes of arguments

in this case, output values are split between interfaces: one as the return status value, and none or many as output values

with the status value as the return value
input-value ... output-value ... -> return-status-value

it is more likely to have only one status value and none or many output arguments

multiple return values

in case it is possible that routines return multiple values, status information may be passed among other return values to the caller

avoids the need to explicitly create compound data structures or to use output arguments

they still have to be unpacked syntactically, which needs additional forms to create and accept multiple return values. in many case the syntax is very similar to the use of other compound data structures

the position of the status code to check after return in the collection of output values might have to be agreed on between different libraries for consistency

quite similar to returning a single compound value or a special data type

continuation passing

error handler routines to be called on error can be passed as arguments to a routine

error handlers can then accept multiple arguments, and unpacking of multiple arguments can be done with more common fundamental routine syntax

like with multiple return values, the position of status information in the collection of output values might be difficult to predict unless there a convention is followed

instead of a caller checking return values for errors and handling them, the responsibility to check for and handle errors is moved to callees. this can make the code smaller with less redundancy when the routine is called at many places by callers not having to duplicate error checking code

a routine that called error handlers might still return at some point. it could return information for resuming the origin routine, otherwise, special case return or status values might still have to expected by the origin routines caller

global variable

for example using a global variable that is updated with current status information

data structures with some kind of thread-local storage should be used or concurrency becomes impractical

the state persists between routine calls and the responsibility of resetting the global variable arises

general problems with global variables apply, for example possibly less optimisation opportunities for the compiler because the variable can be changed from other code locations

local jump

parts of the routine code are skipped when events to be handled occur. goto might be used to skip to a position where clean-up is done

several goto destinations for different error types might be used, or local variables that are set on error and a single goto destination that evaluates those variables to react to the status

helpful for not having to make additional unnecessary checks after an error occurred, and to have clean-up operations specified at a single position instead of at every error origin, as would be the case for direct returns

allocated heap memory could be tracked locally and freed at the goto destination. if it would not be tracked, it might be difficult to know what has been allocated up to the point where the error occurred

local jumps are also helpful for exiting loops

goto is simpler than passing routine references and incurs little overhead

non-local jump

status information can be stored in an object and execution transferred to somewhere in or outside the call hierarchy, without regard to caller/callee return semantics

this corresponds to exception handling using throw and catch

usually jump destinations can be specified at arbitrary positions in the call hierarchy, and all or selected errors from all sub-routine calls can be handled there

callers do not always have to check for return errors. routine composition is not interrupted by status checks and can therefore be simple in notation. still, at some place all errors might eventually need to be handled

a default exception handler that exits the program is commonly installed

has to deal with state deinitialisation / clean-up (heap-memory, file handles) on non-local exit. garbage collection helps with the memory management at least

it adds another way how routines can exit. normally they exit at the point of their call or with the process

often more error information is collected when the error occurs to select for errors to handle and to give debugging information like a backtrace for localising the exact source of the jump because it is further away in the code

the error handling process might be specified mostly out of context at another program position, where it checks the source and type and perhaps tries to resume while considering the context of the jump origin. hidden control flow paths might emerge

not purely functional because even if the local function call is disregarded because it does not return at all, the program as a whole violates referential transparency because it calls a continuation that depends on context that was not an explicit argument to the function

may be implemented with delimited continuations

see also en.wikipedia.org/wiki/Exception_handling

what data to transfer

general

none: without status information it is unclear if a routine ran successfully. it might be assumed that it ran successfully if execution proceeded

boolean: success and failure. no specifics about possible causes for a failure unless there is only one

other data types might contain information to allow more differentiation between statuses and analysis by users

integers

integers are particularly fast to process and so create comparatively little cpu and memory overhead for error checking

if multiple libraries are used together and all use integer values, error values of different libraries might overlap and conflict. for this case, it can be necessary to encode additional information with the status code, either in the integer value or by using a compound data structure

success and failure

to check the return status, it must be decided which value designates success and which designates failure

in conditionals in c for example, 0 is considered to be boolean false and any other value to be true. nevertheless, 0 is usually chosen to be the success value. reasons for this are that other values are used as identifiers for specific errors, that checks for zero are fast, and errors should be rare and checks are prevalent, and that zero is either the lowest or center value that an integer variable can take, somewhat unique compared to other values

negative or positive numbers

some libraries give different meaning to positive and negative values, for example to differentiate system errors from other program errors

some libraries start at very low or high values to avoid identifier overlaps with other libraries

sometimes a routine is supposed to return positive non-status result values on success, and negative values for signaling errors. for example a read procedure that returns the size of the data read

error details

sometimes routines for retrieving additional descriptive text for error codes are available. for example to then use it to select error messages sent to a user

strings or symbols

can be used if the use of named variables for integers is impractical. more processing overhead and maybe less portable, otherwise like integers. strings would eventually have to be duplicated at many places

other compound data structures

other results and the return status can be combined and be passed as a single return value. the caller then has to access object members, which might be associated with additional overhead compared to just checking integers

example fields a detailed error might have

name description origin-routine-name source-line-number source-file origin-module-name identifier contextual-data backtrace assertion-expression

designated types

choosing types that will only be returned on error

this has the benefit of being relatively fast, for example compared to bigger compound data structures

default types

for example boolean false on error, or negative values if otherwise only positive values are valid

this does not work for cases where successful results can be of all types

a special error type distinct from all other types

reporting

error reporting is the process of collecting details that may be helpful to fix the problem to the user. for example a displayed message or written into a log file

examples

glibc

negative integers as return value are errors

error numbers can be translated to description strings with a specific routine

integer comparison to check for errors is fast

posix

routines return -1 on failure and 0 on success

a global variable named "errno" is set to an error number

error numbers can be translated to description strings with a specific routine

not-null checks are particularly fast. errno must be a thread-local (has overhead) or parallel error handling would not be possible


tags: programming start document guide computer general error return status error-handling