-
Notifications
You must be signed in to change notification settings - Fork 9
dev Error handling
This page will be moved to source code documentation when finalized
Inspired from these recommendations, and this presentation.
As a general practice, functions should success or error code when possible, but for small functions (example: computing a cosine), it is more convenient to return the result of the computation.
In-band error indicators are bad practice (see links above) and should be avoided whenever possible. One issue with this option is that is makes it impossible to adopt a standard error code shared by all/most function, as a "useful" value is a per-function concept.
Using EXIT_SUCCESS and EXIT_FAILURE for anything else than calls to the exit() function or the return from main() is bad practice, and does not allow for fine-grained error checking.
Proposed guidelines for developers are provided in this document.
Whenever practical: function should return error code, and computation results should be returned by pointer
The function should return a value of type errno_t. See examples and guidelines here
errno_t myfunction(
int *returnval
);
Note that errno_t is part of C11's annex K, so it may not be supported by all compiles. Add the following code
#ifndef __STDC_LIB_EXT1__
typedef int errno_t;
#endif
Functions that return an error code would be clearly identified by the errno_t return type:
errno_t computecosine(
float inputval,
float *returnval
); // returns error code
float computecosine(
float inputval
); // returns computation result
Use the FUNC_RETURN_FAILURE
to handle errors.
errno_t fret = function1(arg0, arg1);
if( fret != RETURN_SUCCESS)
{
FUNC_RETURN_FAILURE("Call to function1 failed with arguments %d %d", arg0, arg1);
}
The macro will print an error message and exit the function returning an error code.
The FUNC_CHECK_RETURN
macro runs a function, checks its return value and handles error return value :
FUNC_CHECK_RETURN(
function1(arg0, arg1)
);
Error codes should be included in CLIcore.h, as follows:
#define RETURN_SUCCESS 0
#define RETURN_FAILURE 1 // generic error code
#define RETURN_MISSINGFILE 2
The PRINT_WARNING and PRINT_ERROR macros are provided in CLIcore.h
PRINT_ERROR();
PRINT_ERROR records the current state of the program (testpoint), which is displayed upon calling abort(). It is recommended to call abort() after PRINT_ERROR, as this macro is intended for error conditions that cannot be handled otherwise.
PRINT_WARNING();
Use PRINT_WARNING to issue warning and continue execution.
PRINT_WARNING("something is not quite right");
if(a<0)
PRINT_WARNING("a should be positive, but a = %f", a);
if(a<0) {
PRINT_ERROR("a has to be positive, but a = %f", a);
abort();
}
Note that PRINT_ERROR can be followed by abort() if the error cannot be handled otherwise.
Always test return value of std functions
The macros, defined in CLIcore.h, perform conservative error checking and will abort on error.:
# execute system command. Use instead of system()
# checks for command string buffer overflow
# checks for command return
int a = 4;
EXECUTE_SYSTEM_COMMAND("echo \"%d\"", a);
# write image name to existing image string
char imtest[STRINGMAXLEN_IMGNAME];
int a = 426;
WRITE_IMAGENAME(imtest, "fr53im_%d", a);
# write fille name to existing string
char filetest[STRINGMAXLEN_FILENAME];
int a = 426;
WRITE_FILENAME(filetest, "fr53im_%d.txt", a);
# write image name to existing string
char ffiletest[STRINGMAXLEN_FULLFILENAME];
int a = 426;
WRITE_FULLFILENAME(ffiletest, "/home/me/fr53im_%d.txt", a);
int fscanfcnt;
fscanfcnt = fscanf(fp, "%s", streamfname);
if(fscanfcnt == EOF) {
if(ferror(fp)) {
perror("fscanf");
} else {
fprintf(stderr, "Error: fscanf reached end of file, no matching characters, no matching failure\n");
}
return RETURN_FAILURE;
} else if(fscanfcnt != 2) {
fprintf(stderr, "Error: fscanf successfully matched and assigned %i input items, 2 expected\n", fscanfcnt);
return RETURN_FAILURE;
}
if(system("ls") != 0) {
PRINT_ERROR("system() returns non-zero value");
}
if(sprintf(name, "image1", loop)<1) {
PRINT_ERROR("sprintf wrote <1 char");
}
Use snprintf instead of sprintf to avoid buffer overflow.
char namestring[MAXSTRLEN];
{ // code block write image name
int slen = snprintf(imname, STRINGMAXLEN_IMGNAME, "somefilename");
if(slen<1) {
PRINT_ERROR("snprintf wrote <1 char");
abort(); // can't handle this error any other way
}
if(slen >= STRINGMAXLEN_IMGNAME) {
PRINT_ERROR("snprintf string truncation");
abort(); // can't handle this error any other way
}
} // end code block
int msize=10;
farray = (float*) malloc(sizeof(float)*msize);
if(farray == NULL) {
PRINT_ERROR("malloc returns NULL pointer, size %d", msize);
abort(); // or handle error in other ways
}
fp_test = fopen("testfile.log", "w");
if(fp_test == NULL) {
PRINT_ERROR("Cannot open file testfile.log");
abort();
}
Some bugs can be found with the printf-based method: include printf statements to find where/why the code crashes. Use stderr instead of stdout: as the stdout is buffered, you application can crash before flushing the stdout buffer. For fast debug, it's safer to you stderr. Alternatively, use fflush(stdout).
Instead of using the printf method, we recommend compiling it with the NDEBUG flag.
General Practices:
- Use assertions (assert()), which are enabled with the NDEBUG flag
- Use DEBUG_TRACEPOINT macro which insert test points, also enabled by NDEBUG flag
- When an error cannot be handled, call abort()
Examples
errno_t function_example(int a1, int a2, int * outval)
{
DEBUG_TRACE_FSTART(); // entering function
int sum;
# FARG : function argument
DEBUG_TRACEPOINT("FARG %d %d", a1, a2):
DEBUG("summing two numbers");
sum = a1 + a2;
# FOUT : functiom result
DEBUG_TRACEPOINT("FOUT %d", sum):
if(outval != NULL)
{
*outval = sum;
}
DEBUG_TRACE_FEXIT(); // exiting function
return RETURN_SUCCESS;
}
cd _build
rm CMakeCache.txt
make clean
# add additional cmake options as needed
cmake -DCMAKE_BUILD_TYPE=Debug ..
sudo make install
The cmake Debug build type sets NDEBUG to enables debugging features, and uses compilation options "-Wall -g -O0".
To enable the exit-on-error mode, set env variable MILK_ERROREXIT
MILK_ERROREXIT=1 milk
milk will then exit on error, and write file milk-codetracepoint.log.
To enable signals catching, the following function is called:
errno_t set_signal_catching(); // code in CLIcore.c
This is enabled by default.vSignal catching will make use of test points (see below): upon exit, it will print the state of the code as last probed by the DEBUG_TRACEPOINT macro.
Call the DEBUG_TRACEPOINT
macro to collect and stores information at any point of the source code. The corresponding information will be included in the exit report written upon abnormal program exit.
The macro source code is in CLIcore.h.
To use testpoints, compile in DEBUG mode, and then call the DEBUG_TRACEPOINT
macro. The macro should always be called before abort().
Examples :
DEBUG_TRACEPOINT("some informative comment");
assert(a>0);
DEBUG_TRACEPOINT(" "); // no comment. Note that space char is needed.
if(a<b) {
DEBUG_TRACEPOINT("a is %f, should be less than %f", a, b);
abort();
}
MILK_WRITECODETRACE=1 milk
Modular Image processing Library toolKit (milk) - https://github.com/milk-org/milk