http://gcc.gnu.org/bugzilla/show_bug.cgi?id=49243

           Summary: attribute((returns_twice)) doesn't work
           Product: gcc
           Version: 4.7.0
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: c
        AssignedTo: unassig...@gcc.gnu.org
        ReportedBy: mi...@it.uu.se


According to the documentation, attribute((returns_twice)) is supposed to allow
people to call alternate implementations of setjmp-like functions.

It doesn't work.

In my test case, calling "_setjmp" (glibc's name) works and prevents inlining
and other optimizations that are invalid in the presence of setjmp and longjmp,
but calling "my_setjmp" (declared with returns_twice) doesn't prevent those
invalid optimizations.  This results in important side-effects disappearing if
my longjmp replacement is called.

The test case is a little long to inline here (114 lines) so I'll summarize.

There's a decoder that reads and processes characters, and it's called in a
loop to process strings of unknown length.

In the real application it's possible for the input to be followed by an
inaccessible page.  This is handled by a wrapper function that calls setjmp()
before calling the decoding loop, and having the read fault handler longjmp to
the wrapper, which then returns a special value.  The test case simulates the
read fault by calling longjmp() on the second input character.

In case of a read fault the toplevel needs to know the corresponding invalid
input address.  To do this it passes down the address of a local variable that
initially points to the start of the input.  The decoder loop increments this
variable after each successful decoding step.  After a read fault, the toplevel
uses the updated variable to see how much was successfully decoded.

The purpose of the intermediate wrapper function and passing down a pointer by
reference is to limit the number of auto variables that become indeterminate
after a longjmp, and to avoid having to mark auto variables as volatile, see
the longjmp spec in C99 TC2 or C1X.

The real application is sort-of embedded and uses it's own mini-libc, including
setjmp/longjmp replacements written in assembly code.  The test case simulates
that by wrapping libc's setjmp behind new names, and declares the setjmp()
replacement with __attribute__((returns_twice)) following gcc's documentation. 
The test case can also be compiled to use libc's setjmp as-is.

First try with libc's setjmp as-is:
> gcc -O2 returns-twice-bug.c; ./a.out
(No abort == success.)

Now try with the setjmp replacement:
> gcc -O2 -DMYSETJMP returns-twice-bug.c ; ./a.out
Abort

What happened is that the read fault was detected but the toplevel variable
that should have been updated to point to the invalid address wasn't.

Repeating the above two gcc calls with -S and comparing the .s files one sees
that the wrapper function with the setjmp call is eliminated and inlined into
the toplevel, and the decoding loop no longer writes to the in-memory pointer
variable.

Now try again with the setjmp replacement but declare the wrapper function with
__attribute__((noinline)):
> gcc -O2 -DMYSETJMP -DKLUDGE returns-twice-bug.c ; ./a.out
(No abort == success.)

Comparing the assembly code from the first and third steps we see that they are
now essentially the same, differing only in the third step code having the
setjmp/longjmp wrapper function definitions, the labels in subsequent code, and
the name of the setjmp function called from the wrapper.

So the important effect of calling libc's setjmp is that it prevents inlining
of the calling function, but declaring a function of a different name but with
returns_twice doesn't have that required effect.  That's the bug.

A quick check shows that glibc's setjmp() isn't declared with returns_twice,
and gcc recognizes it from its name alone.  Apparently the name-based checks
cause more magic to happen than attribute((returns_twice)).

This bug occurs on x86_64-linux and i686-linux with every gcc-4.x including
current 4.7 trunk.  With gcc-3.x it doesn't occur.  I haven't checked non-x86
targets.

Reply via email to