10

I'm using an API and I find myself writing a lot of error-handling code of the general form:

if (errorCode = functionName(params)) 
    printError(errorCode, "functionName", __LINE__, __FILE__);

where the body of printError might look like:

fprintf(stderr, "Error in function %s on line %d of file %s", 
    functionName, lineNumber, fileNumber);

However, it's a pain to hard-code the function name every time. Is there any way to get the name of the function causing the error, i.e. the last function called, either at runtime or through a macro? I can't modify the API function in any way, of course. There are ways to get the current function as well as the calling function, but neither of these work from outside the function.

Community
  • 1
  • 1
1''
  • 26,823
  • 32
  • 143
  • 200
  • `#define ERROR_LOCATION __func__, __LINE__, __FILE__` and `printError(errorCode, ERROR_LOCATION);` is along the lines of what I usually do, or putting the entire call in a macro, though the former is a bit more flexible to have one macro for all overloads of `printError`. – chris May 16 '13 at 00:36
  • @chris I would be careful with that. The better option is to make a parameter macro, that you invoke like a function. Passing three arguments that look like one lead to some... interesting situations. – Richard J. Ross III May 16 '13 at 00:37
  • @RichardJ.RossIII, As long as it's clear that it should only be used for calling error functions that require the source of the error, I don't see any problems with it being used for that. Is there anything specific you had in mind? – chris May 16 '13 at 00:39
  • @chris `__func__` gives the function I"m currently in, but I want the name of the API function I just left. – 1'' May 16 '13 at 00:39
  • @1'', I see, I usually include that with a custom message if I put it in. It's not hard to adapt whichever macro you use, but I haven't found any better way than macros for this purpose. It's one of the few times I'll use one. – chris May 16 '13 at 00:43

4 Answers4

9

you can have this

#define SAFE_CALL(func) \
do {\
    if (errorCode = (func)) \
        printError(errorCode, #func, __LINE__, __FILE__);\
} while(0)

SAFE_CALL(functionName(params));
Bryan Chen
  • 45,816
  • 18
  • 112
  • 143
  • What does the `#func` do? Unlike `#line`, it's not a stand-alone pre-processing directive. – 1'' May 16 '13 at 01:00
  • @1" It converts a macro argument to a string. – Richard J. Ross III May 16 '13 at 01:01
  • +1 had the same thought, but there are some drawbacks. The biggest is that the success or failure of the callee is hidden from the caller. If every error causes the program to `exit`, that's fine, but if the handling is more nuanced, this becomes a significant hurdle. – jerry May 16 '13 at 02:20
  • @jerry you can tweak this to make it "return" the errorCode and check it for function that you want extra care. something like `#define SAFE_CALL(func) ({if(errorCode=(func))printError(...);errorCode;})` – Bryan Chen May 16 '13 at 05:34
  • 2
    That won't "return" anything, it shouldn't compile regardless of how you use it [though I've been surprised before : ) ]. – jerry May 16 '13 at 14:10
  • @jerry `({})` is GCC extension http://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html#Statement-Exprs – Bryan Chen May 16 '13 at 23:46
  • Well, as I said, I've been surprised before : ) Still, it can be done in a more portable fashion with the ternary (aka conditional) operator or a function call (see my answer for example, though it's pretty straight forward). – jerry May 17 '13 at 01:01
5

The solution here is to make a macro that invokes your function, and if it fails, invoke a print function with your special parameters. If you're using C99, then you can (ab)use a variadic macro, like this:

void printError(const char *fmt, const char *func, const int line, const char *file, ...) {
    // the first three arguments are our function, line and file, so first, print out that first
    va_list list;
    va_start(list, file);

    // only print until the first '('
    int nchars = (int) (strchr(func, '(') - func);

    fprintf(stderr, "Error in function %.*s on line %d of file %s: ", nchars, func, line, file);
    vfprintf(stderr, fmt, list);

    va_end(list);
}

#define checkError(invoke, fmt, ...)\
do {\
    int err;\
    if ((err = (invoke))) {\
        printError(fmt, #invoke, __LINE__, __FILE__, ## __VA_ARGS__);\
    }\
} while(0)

Note that the above makes use of a GCC extension (which is also supported in clang) which allows for variadic arguments to be properly passed if none were provided.

Which could then be used as such:

checkError(fail(), "Couldn't validate results: %s", "Context-Sensitive Debug Info");

Which outputs this lovely text for your debugging pleasure:

Error in function fail on line 703 of file /Users/rross/Documents/TestProj/TestProj/main.mm: Couldn't validate results: Context-Sensitive Debug Info

You may want to play around with other macros, such as GCC/Clang's __PRETTY_FUNCTION__, but this should get you started!

Community
  • 1
  • 1
Richard J. Ross III
  • 55,009
  • 24
  • 135
  • 201
  • Note that C++11 has variadic macros as well :) – chris May 16 '13 at 00:52
  • @chris indeed it does, but in C++, you probably could do something much better with templates. – Richard J. Ross III May 16 '13 at 01:03
  • Maybe underneath, but not for passing the file and line :( – chris May 16 '13 at 01:04
  • @chris What are you talking about? You can pass a `const char *` in a template just fine. – Richard J. Ross III May 16 '13 at 01:05
  • Sorry, I meant including `__LINE__` and `__FILE__` without explicitly specifying them. Only the text substitution of macros can do that. You can make one solely to do that and hand all the other work off to a template, though. – chris May 16 '13 at 01:07
  • +1, although the error ouputs `success` for the function name when you had invoked `fail()`. – jxh May 16 '13 at 01:14
  • Elegant, but what's the benefit of the variable argument list over something simpler like xlc's solution? – 1'' May 16 '13 at 01:16
  • @1" If you need to put more debugging info, such as when using `bind`, you could output socket numbers, file descriptors, etc. before crashing. It's always good to have lots of places to debug stuff. – Richard J. Ross III May 16 '13 at 01:17
  • 1
    It allows you to make a more complicated `printf` like format string and associated arguments. – jxh May 16 '13 at 01:18
  • That's pretty cool, actually! I'm going to accept xlc's though, because it's the one I actually used. – 1'' May 16 '13 at 01:20
2

NB: This solution only works if the API can be modified or wrapped to modify the return type of the error code.

I would augment the errorCode return value with the name of the function that is returning the code. Then, the printError() function can extract the information from the errorCode itself.

typedef struct error_code_type {
    unsigned code;
    const char *function;
} error_code_type;

error_code_type error_code (unsigned code. const char *function) {
    error_code_type x = { code, function };
    return x;
}

#define ERROR_CODE(code) error_code(code, __func__)

So, the function when returning an error would use: return ERROR_CODE(xyz);. The receiver of the error code receives the function name of the function that returned the error. The innermost function that triggered the error can even be back propagated properly if the error code is copied instead of reformed.

For example:

void foo () {
    error_code_type errorCode;
    if ((errorCode = the_api_function()).code) {
        printError(errorCode.code, errorCode.function, __LINE__, __FILE__);
    }
}

If you are implementing in C++, you can use operator overloading to make the error_code_type act more like an integral type if that is important.

jxh
  • 69,070
  • 8
  • 110
  • 193
  • You, like me, misread the quesiton unfortunately. He's looking for the name of the function he's calling, not the name of the one he's currently in. – Richard J. Ross III May 16 '13 at 00:51
  • @RichardJ.RossIII: No, I caught that. Read my answer carefully. – jxh May 16 '13 at 00:52
  • What you're missing is that `__func__` is useless for what he's trying to do here. – Richard J. Ross III May 16 '13 at 00:52
  • @RichardJ.RossIII: The `__func__` is used by the called function that caused the error. It gets returned to the caller, so the caller knows which function caused the error. – jxh May 16 '13 at 00:55
  • 1
    But I can't modify the function that caused the error, because it's part of the API. – 1'' May 16 '13 at 00:55
  • @1'': I would wrap the APIs myself so that I could have complete control over the how the API is used. If that is not an option, I would recommend xlc's or RichardJ.RossIII's solution. – jxh May 16 '13 at 01:00
1

Updated Solution

If you don't have errorCode defined (or don't always want it set), use this instead:

#define CALL_AND_LOG_ERROR(callee) \
    CheckCallAndPrintError(callee, #callee, __func__, __LINE__, __FILE__)
 
int CheckCallAndPrintError( int result, const char* callee,
                            const char* caller, int line, const char* file)
{
    if(result)
    {
        fprintf(stderr, "Error %d returned by function %s called from function %s on line \
                         %d of file %s\n", result, callee, caller, line, file);
    }
 
    return result;
}

Then you can use CALL_AND_LOG_ERROR as below except that you explicitly have to assign to a variable if you want to keep the value around:

int errorCode = CALL_AND_LOG_ERROR(SomeFunc(a, 0, NULL));

While this solves the dependency on an external variable, I don't know how often the call will be inlined by the optimizer. I doubt the performance impact will be significant either way. Note that both solutions assume the API return values are ints. If they're some other scalar type (or different scalar types for different functions), you may have to change how they are passed/output.


Original Solution

Calling a macro which calls your function is definitely the way to go here, but I suggest using the ternary operator instead of a control structure. This allows you to use the macro:

  • as a standalone statement (and inspect errorCode later, if you wish)
  • as the controlling statement of a control structure
  • in an expression
  • as an operand to a function

Sample code (you can play around with it at http://ideone.com/NG8U16):

#include <stdio.h>
 
// these macros rely on the fact that errorCode is defined in the the caller
 
#define FUNC_RETURN_ISNT(func, unexpected) \
    ((errorCode = func) == unexpected) ? printf("error: call %s shouldn't have returned %d but did (from function %s at line %d of file %s)\n", #func, unexpected, __func__, __LINE__, __FILE__), 0 : 1
 
#define FUNC_RETURN_IS(func, expected) \
    ((errorCode = func) != expected) ? printf("error: call %s returned %d instead of %d (from function %s at line %d of file %s)\n", #func, errorCode, expected, __func__, __LINE__, __FILE__), 0 : 1

#define ERROR_CHECK(func) \
    (errorCode = func) ? printf("error: call %s returned %d (from function %s at line %d of file %s)\n", #func, errorCode, __func__, __LINE__, __FILE__), errorCode : 0
 
int func(int a, int b, int c)
{
    return a^b^c;
}
 
int func2(void)
{
    return -1;
}

int func3(void)
{
   static int i = 3;

   return i--;
}

int main(void)
{
    int a = 0, b = 0, c = 0;
    int errorCode;
 
    int (*funcPoint)(void) = func2;
 
    FUNC_RETURN_ISNT(func(1,1,1), 1);
    
    FUNC_RETURN_IS(func(a,b,c), 1);

    ERROR_CHECK(func(a,b,1));

    if(ERROR_CHECK(func2()))
    {
       printf("func2 failed error check\n");
    }
    else
    {
       printf("func2 passed error check\n");
    }
 
    if(ERROR_CHECK(funcPoint()))
    {
       printf("funcPoint failed error check\n");
    }
    else
    {
       printf("funcPoint passed error check\n");
    }
 
    if(ERROR_CHECK(func(0,0,0)))
    {
       printf("func failed error check\n");
    }
    else
    {
       printf("func passed error check\n");
    }

    while(ERROR_CHECK(func3()))
    {
        printf("retry...\n");
    }

    switch(ERROR_CHECK(func(1,2,4)))
    {
        case 0:
            printf("okay\n");
            break;
        case 1:
            printf("non-fatal error 1\n");
            break;
        case 7:
            printf("fatal error 7\n");
            return 1;
    }

    return 0;
}

Example output:

error: call func(1,1,1) shouldn't have returned 1 but did (from function main at line 38 of file prog.c)
error: call func(a,b,c) returned 0 instead of 1 (from function main at line 40 of file prog.c)
error: call func(a,b,1) returned 1 (from function main at line 42 of file prog.c)
error: call func2() returned -1 (from function main at line 44 of file prog.c)
func2 failed error check
error: call funcPoint() returned -1 (from function main at line 53 of file prog.c)
funcPoint failed error check
func passed error check
error: call func3() returned 3 (from function main at line 71 of file prog.c)
retry...
error: call func3() returned 2 (from function main at line 71 of file prog.c)
retry...
error: call func3() returned 1 (from function main at line 71 of file prog.c)
retry...
error: call func(1,2,4) returned 7 (from function main at line 76 of file prog.c)
fatal error 7

There are still some drawbacks, however:

  • It's not exactly pretty. The syntax feels awkward to me and it could run afoul of some coding standards.
  • It's probably brittle. I haven't given much thought to it, but I'd bet you could break it by passing something relativley reasonable looking.
  • It requires errorCode to be already defined (and it has to be an integral type due to the printf). To be fair, however, so does the original code snippet. The macros could be modified to accept the variable as a parameter (it wouldn't always have to be named errorCode, but it would still have to be defined outside the macro).
  • It prints the exact piece of code used to invoke the function. This may actually be a good thing in some (or even most) cases, however I think it's not ideal in the function pointer scenario (granted, probably uncommon when calling into an API). Also, its harder to print the actual values of the passed parameters. Both these could be worked around to a degree, but only at the cost of making it more awkward and/or less portable.
Community
  • 1
  • 1
jerry
  • 2,581
  • 1
  • 21
  • 32