Apologies in advance for the length of this post.
BACKGROUND
I'm working on a multi-threaded server program, and I've been tearing my hair out over error logging. My goal is to be able to use a construct such as:
if ((pt_errno = pthread_function(...)) != 0) { FOO_LOG(FOO_ERROR, "Error in pthread_function: %s", FOO_MSG(pt_errno)); return -1; }
Almost as important, I'd like to minimize the number of errors that can occur in the error-logging code itself. My initial attempt to write a thread-safe strerror equivalent got derailed by this; it had so many pthread calls, each of which could fail, which required logging, etc., etc.
I thought that I had found a rather clever solution, using a combination of macros and setjmp/longjmp:
extern void foo_log_fn(int pri, const char *format, ...);
#define BUFFER_CHUNK 128
const char *foo_msg_fn(int buf_size, char *buf, int errnum, jmp_buf env) { if (buf_size == 0) { do { assert(buf_size <= INT_MAX - BUFFER_CHUNK); buf_size += BUFFER_CHUNK; { char test_buf[buf_size]; if (strerror_r(errnum, buf, buf_size) == 0) return buf_size; } } while (errno == ERANGE); if (errno == EINVAL) return "INTERNAL ERROR: Invalid error number"; else return "INTERNAL ERROR: Unexpected strerror_r error"; } else { if (strerror_r(errnum, buf, buf_size) == 0) return buf; else FOO_UNREACHABLE; } }
#define FOO_MSG(errnum) foo_msg_fn(buf_size, buf, (errnum), env)
#define FOO_LOG(pri, ...) \ \ do \ { \ int save_errno = errno; \ int buf_size; \ jmp_buf env; \ \ if ((buf_size = setjmp(env)) == 0) \ { \ char *buf = NULL; /* suppress warning */ \ foo_log_fn((pri), __VA_ARGS__); \ buf = NULL; /* suppress warning */ \ } \ else \ { \ char buf[buf_size]; \ errno = save_errno; \ foo_log_fn((pri), __VA_ARGS__); \ buf[0] = '\0'; /* suppress warning */ \ } \ \ errno = save_errno; \ } \ while (0) \
THE PROBLEM
Here is a self-contained usage example, with the macros expanded:
#include <stddef.h> #include <setjmp.h> #include <errno.h>
extern void foo_log_fn(int pri, const char *format, ...); extern int foo_msg_fn(int buf_size, char *buf, int errnum, jmp_buf env);
extern int pthread_rwlock_rdlock(void); extern int pthread_rwlock_unlock(void);
extern int foo_exit_flag;
int foo_set_exit_flag(void) { int rv, pt_errno;
if ((pt_errno = pthread_rwlock_rdlock()) != 0) { do { int save_errno = errno; int buf_size; jmp_buf env; if ((buf_size = setjmp(env)) == 0) { char *buf = NULL; foo_log_fn(0, "Error: %s", foo_msg_fn(buf_size, buf, pt_errno, env)); buf = NULL; } else { char buf[buf_size]; errno = save_errno; foo_log_fn(0, "Error: %s", foo_msg_fn(buf_size, buf, pt_errno, env)); buf[0] = '\0'; } errno = save_errno; } while (0); return -1; }
rv = foo_exit_flag;
if ((pt_errno = pthread_rwlock_unlock()) != 0) /* **** */ return -1; /* **** */
return rv; }
If this is compiled without optimization, no warnings are emitted. If any optimization is turned on, I get a warning that pt_errno might be clobbered by longjmp. Interestingly, if the second usage of pt_errno (the lines marked with "/* **** */") is commented out, no warning is emitted, even with optimization turned on.
The description of longjmp on The Open Group's web site states:
All accessible objects have values, and all other components of the abstract machine have state (for example, floating-point status flags and open files), as of the time longjmp() was called, except that values of objects of automatic storage duration are unspecified if they meet the following conditions:
* They are local to the function containing the corresponding setjmp() invocation.
* They do not have volatile-qualified type.
* They are changed between the setjmp() invocation and longjmp() call.
pt_errno definitely meets the first two criteria, but it just as definitely doesn't meet the third. So is this:
1. buggy code,
2. code that the GCC optimizer just can't handle, or
3. a spurious warning?
If it's #1, what's the bug? If it's #3, is there a code change to suppress it or a switch to turn this particular warning off?
If you got all the way down here, thanks!
-- ======================================================================== Ian Pilcher i.pilcher@xxxxxxxxxxx ========================================================================