Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 546
modelnine Avatar asked Apr 02 '12 21:04

modelnine


2 Answers

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.

like image 158
user1202136 Avatar answered Nov 13 '22 04:11

user1202136


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)

like image 43
j4x Avatar answered Nov 13 '22 06:11

j4x