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.