Bogus longjmp clobber warning?

Ian Pilcher i.pilcher@comcast.net
Wed Feb 23 16:50:00 GMT 2005


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@comcast.net
========================================================================



More information about the Gcc-help mailing list