9

When using C++ exceptions to transport errno state, the compiled code that gets generated by g++ (4.5.3) for code such as the following

#include <cerrno>
#include <stdexcept>
#include <string>

class oserror : public std::runtime_error {
private:
    static std::string errnotostr(int errno_);
public:
    explicit oserror(int errno_) :
        std::runtime_error(errnotostr(errno_)) {
    }
};

void test() {
    throw oserror(errno);
}

is rather unexpectedly (on Linux, x86_64)

    .type   _Z4testv, @function
    ...
    movl    $16, %edi
    call    __cxa_allocate_exception
    movq    %rax, %rbx
    movq    %rbx, %r12
    call    __errno_location
    movl    (%rax), %eax
    movl    %eax, %esi
    movq    %r12, %rdi
    call    _ZN7oserrorC1Ei

What this basically means is that errno as an argument to a C++ exception is pretty much useless due to the call to __cxa_allocate_exception preceding the call to __errno_location (which is the macro content of errno), where the former calls std::malloc and does not save errno state (at least as far as I understood the sources of __cxa_allocate_exception in eh_alloc.cc of libstdc++).

This means that in the case that memory allocation fails, the error number that was actually to be passed into the exception object gets overwritten with the error number that std::malloc set up. std::malloc gives no guarantee to save an existing errno state, anyway, even in the case of successful exit - so the above code is definitely broken in the general case.

On Cygwin, x86, the code that gets compiled (also using g++ 4.5.3) for test() is okay, though:

    .def    __Z4testv;      .scl    2;      .type   32;     .endef
    ...
    call    ___errno
    movl    (%eax), %esi
    movl    $8, (%esp)
    call    ___cxa_allocate_exception
    movl    %eax, %ebx
    movl    %ebx, %eax
    movl    %esi, 4(%esp)
    movl    %eax, (%esp)
    call    __ZN7oserrorC1Ei

Does this mean that for library code to properly wrap errno state in an exception, I'll always have to use a macro which expands to something like

    int curerrno_ = errno;
    throw oserror(curerrno_);

I actually can't seem to find the corresponding section of the C++ standard which says anything about evaluation order in the case of exceptions, but to me it seems that the g++ generated code on x86_64 (on Linux) is broken due to allocating memory for the exception object before collecting the parameters for its constructor, and that this is a compiler bug in some way. Am I right, or is this some fundamentally wrong thinking on my part?

modelnine
  • 1,499
  • 8
  • 11
  • `errnotostr()` is a (static) function of `class oserror` that basically calls `strerror_r()` and returns a `std::string`-ified form of the result. I've not included that part of the code, because it is irrelevant for the example. – modelnine Apr 02 '12 at 21:38
  • Yes, brainfart, sorry. Disregard. – irobot Apr 02 '12 at 21:43

2 Answers2

1

Please note that __cxa_allocate_exception is done before your constructor is actually called.

  32:std_errno.cpp ****     throw oserror( errno );
 352 0007 BF100000      movl    $16, %edi
 ;;;; Exception space allocation:
 355 000c E8000000      call    __cxa_allocate_exception
 356 0011 4889C3        movq    %rax, %rbx
 ;;;; "errno" evaluation:
 357 0014 E8000000      call    __errno_location
 358 0019 8B00          movl    (%rax), %eax
 359 001b 89C6          movl    %eax, %esi
 360 001d 4889DF        movq    %rbx, %rdi
 ;;;; Constructor called here:
 362 0020 E8000000      call    _ZN7oserrorC1Ei

So it makes sense. __cxa_allocate_exception just allocates space to an exception, but does not construct it (libc++abi Specification).

When your exception object is built, errno is arleady evaluated.

I took your example and implemented errnotostr:

// Unexpected flow of control (compiler-bug?) using errno as argument for exception in C++ (g++)

#include    <cerrno>
#include    <stdexcept>
#include    <string>

#include    <iostream>
#include    <cstring>
#include    <sstream>

class oserror : public std::runtime_error
{
private:
    static std::string errnotostr(int errno_)
    {
        std::stringstream   ss;

        ss << "[" << errno_ << "] " << std::strerror( errno_ );

        return  ss.str( );
    }

public:
    explicit oserror( int errno_ )
    :    std::runtime_error( errnotostr( errno_ ) )
    {
    }
};

void test( )
{
    throw oserror( errno );
}

int main( )
{
    try
    {
        std::cout << "Enter a value to errno: ";
        std::cin >> errno;

        std::cout << "Test with errno = " << errno << std::endl;
        test( );
    }
    catch ( oserror &o )
    {
        std::cout << "Exception caught: " << o.what( ) << std::endl;
        return  1;
    }

    return  0;
}

Then I compiled with -O0 and -O2, run and got the same results, all according to expectations:

> ./std_errno
Enter a value to errno: 1
Test with errno = 1Exception caught: [1] Operation not permitted

> ./std_errno
Enter a value to errno: 11
Test with errno = 11
Exception caught: [11] Resource temporarily unavailable

> ./std_errno
Enter a value to errno: 111
Test with errno = 111
Exception caught: [111] Connection refused

(Running on 64-bits Opensuse 12.1, G++ 4.6.2)

Community
  • 1
  • 1
j4x
  • 3,595
  • 3
  • 33
  • 64
  • I'm not complaining about the fact that errno isn't already evaluated when entering the constructor (I know it is), but rather that `__cxa_allocate_exception` is called before errno is fetched, and as such infrastructure code _may_ alter errno state (see the discussion about std::malloc() being called) before the code to query it gets a chance to run and then pass the evaluated errno to the constructor function. – modelnine Apr 03 '12 at 12:31
  • I understand. Let me be clearer. Considering that the exception itself can change `errno`, the answer to your original question is: Yes, you need to take the value of `errno` prior to throwing. Hope to have helped. – j4x Apr 03 '12 at 13:28
  • Read the reply I just marked as answering the question, and the comments on that - the question isn't about the constructor changing `errno`, but rather about the code generated for the `throw` statement possibly changing errno before its value can be passed to the constructor. Thanks! – modelnine Apr 03 '12 at 15:48
1

What this basically means is that errno as an argument to a C++ exception is pretty much useless due to the call to __cxa_allocate_exception preceding the call to __errno_location (which is the macro content of errno), where the former calls std::malloc and does not save errno state (at least as far as I understood the sources of __cxa_allocate_exception in eh_alloc.cc of libstdc++).

This is not true. As far as I have checked the source code, the only "thing" inside __cxa_allocate_exception that can change errno is malloc(). Two cases may occur:

  • malloc() succeeds, then errno is unchanged;
  • malloc() fails, then std::terminate() is called and your oserror() is never constructed.

Therefore, since calling _cxa_allocate_exception before calling your constructor does not functionally change your program, I believe g++ has the right to do so.

user1202136
  • 11,171
  • 4
  • 41
  • 62
  • First, is there any guarantee that std::malloc() does not change errno state when the alloc succeeds? It's probably paranoid to think does, but AFAIK from SuSv4 there's only the guarantee they don't set it to zero, not that it's unchanged. And, anway, in case std::malloc() fails, std::terminate() is not necessarily called; there's a static buffer used to construct exceptions in low-memory conditions, so errno might be set even though the exception is raised. – modelnine Apr 03 '12 at 12:28
  • First, if I interpret the [POSIX specification](http://pubs.opengroup.org/onlinepubs/9699919799/functions/malloc.html) correctly, `malloc()` should set `errno` only if an error occurs. Second, the code which deals with the low-memory condition only calls pthread functions, which emit error codes as return values and do not use `errno`. – user1202136 Apr 03 '12 at 12:50
  • That's the thing - the specification of malloc() only gives a positive example ("Otherwise, it shall return a null pointer and set errno to indicate the error"), in which case errno has already been overwritten and the original errno-code that I would've liked to pass into oserror() is lost (and it doesn't matter that the pthread_*-methods on lowmem-path don't alter that in this case), but for the no-error case, no provision is made in that spec, and again, AFAIK SuSv4, there's no provision that says that functions _must not_ alter errno on success, just that they don't set it to zero. – modelnine Apr 03 '12 at 13:07
  • 1
    Let me refocus the discussion to your original question. g++ should compile whatever asm code it wants, so that `errno` is preserved when passing it to your exception constructor [ISO/IEC 14882:2011, 15.1.3]. Iff it "knows" (e.g., because g++ comes bundled with stdc++) that `__cxa_allocate_exception` preserves `errno`, then g++ is allowed to call it before `__errno_location`. IMHO, it is `__cxa_allocate_exception` who should ensure it has no undesired side-effects, which it "almost" does, save for the case of low-memory condition. – user1202136 Apr 03 '12 at 14:47
  • Okay - g++ doesn't know that `__cxa_allocate_exception` doesn't have side-effects (simply because it has, at the very least under certain conditions, which we've now pretty much established, and that's what my original question was indeed about). As such that answers the question that this is indeed a compiler (optimization?) bug. I think that I've narrowed it down to the declaration of `__errno_location()`, which has `__attribute__((const))` on glibc, but not on the cygwin libc (and for which g++ produces "correct" code). Thanks! – modelnine Apr 03 '12 at 15:47