Discussion:
[Firebird-devel] exception specifications
Nickolay Samofatov
2003-08-25 19:51:04 UTC
Permalink
Hello, All !

I think we need to specify explicitly which firebird functions
and methods can throw exceptions and which cannot. I think that
it is enough to specify throw() or throw(...) because MSVC does not
support anything else. Or we want to introduce compatibilty macros
like all libraries do ? Anyway, having compatibility macros is good
because it is better to declare public API as throw() if headers are
used from C++ programs and omit exception specification if used from C
programs. Look at GLIBC sources for examples. Having exception markup
is good because of the following reasons:
1) when compiler sees throw() specification it can emit much better code
2) some relatively large parts of the engine execute inside the signal
handlers while not all compilers emit signal-safe code for
exceptions, so it would be better to make compiler check if we are
doing something wrong.
--
Best regards,
Nickolay Samofatov mailto:***@bssys.com
Mike Nordell
2003-08-26 01:29:24 UTC
Permalink
Post by Nickolay Samofatov
I think we need to specify explicitly which firebird functions
and methods can throw exceptions and which cannot.
For this to be useful it would have to be performed in one fell swoop. Are
you volunteering?
Post by Nickolay Samofatov
I think that
it is enough to specify throw() or throw(...) because MSVC does not
support anything else.
Worse - it does not support, it only parses the EH, and does nothing at all
about it. Not even their latest compiler calls unexcpected(), but more on
this below.
Post by Nickolay Samofatov
Or we want to introduce compatibilty macros
like all libraries do ? Anyway, having compatibility macros is good
because it is better to declare public API as throw() if headers are
used from C++ programs and omit exception specification if used from C
programs.
Useless. C programs can only use C functions. C functions cannot throw -
alas throw() specification for such functions are not only redundant when
the header is parsed as a C++ translation unit, it can generate even worse
code using a conforming compiler. Besides, a C linkage function with an
exception specification makes no sense.
Post by Nickolay Samofatov
Look at GLIBC sources for examples. Having exception markup
1) when compiler sees throw() specification it can emit much better code
No it can't. I was also once led to believe this - but it's false. Should a
function having a throw() ES be called (C++ compilers does not like Java
have static (compile-time) exception checks), and that function itself calls
function having either no or throw(...) ES, it must itself set up a "hidden"
try/catch(...) to be able to call unexpected() on a conforming
implementation.

The _only_ way to tell VC that a function is not throwing any exceptions is
by using __declspec(nothrow) - which makes the code inherently unportable:

http://groups.google.com/groups?selm=u6f9auco40nfl39cboa56tf3ln6nff4i84%404a
x.com

http://groups.google.com/groups?selm=%25ZJX4.569%24TZ2.39258%40newsread1.pro
d.itd.earthlink.net


There are other issues also, such as that fact that conforming compilers do
call unexpected(), VC does not. I'm not too keen on introducing that
difference of code behaviour depending on what compiler is used.
Post by Nickolay Samofatov
2) some relatively large parts of the engine execute inside the signal
handlers while not all compilers emit signal-safe code for
exceptions, so it would be better to make compiler check if we are
doing something wrong.
Then the engine is broken. There are IIRC exactly three things you can do
within a signal handler:
1. access variables of type volatile sig_atomic_t.
2. (successfully) call signal().
3. return to caller.

That's it. Anything else invokes undefined or implementation defined
behaviour.


/Mike
Mike Nordell
2003-08-26 07:33:28 UTC
Permalink
Correcting and augmenting my own post...
Post by Mike Nordell
Post by Nickolay Samofatov
I think we need to specify explicitly which firebird functions
and methods can throw exceptions and which cannot.
For this to be useful it would have to be performed in one fell swoop. Are
you volunteering?
There should be ":-)" after the question sign.

[MSVC exception specifications]
Post by Mike Nordell
Worse - it does not support, it only parses the EH, and does nothing at all
about it. Not even their latest compiler calls unexcpected(), but more on
this below.
s/unexcpected/unexpected/
Post by Mike Nordell
C programs can only use C functions. C functions cannot throw -
alas throw() specification for such functions are not only redundant when
the header is parsed as a C++ translation unit, it can generate even worse
code using a conforming compiler. Besides, a C linkage function with an
exception specification makes no sense.
Here I should add that I believe (but haven't checked) Firebirds own
internal code could be in violation - for I do believe it has more than a
few internal [extern "C"] functions that in reality are C++ functions, that
currently can throw exceptions. The way to correct this would obviously be
to move those (internal) functions out of C linkage and into C++ linkage.
Even if it (currently) might be only a theoretical violation, it still would
be a violation.

/Mike
Neil McCalden
2003-08-27 22:41:08 UTC
Permalink
Post by Mike Nordell
Here I should add that I believe (but haven't checked) Firebirds own
internal code could be in violation - for I do believe it has more than a
few internal [extern "C"] functions that in reality are C++ functions, that
currently can throw exceptions. The way to correct this would obviously be
to move those (internal) functions out of C linkage and into C++ linkage.
Even if it (currently) might be only a theoretical violation, it still would
be a violation.
I think I might currently hitting a problem with this in err.cpp
ERR_punt()

#pragma FB_COMPILER_MESSAGE("FIXME! C functions can not throw! FIXME!")
Firebird::status_exception::raise(tdbb->tdbb_status_vector[1]);

Trying to do a gbak restore with newly built Solaris FB1.5 B1_5_Release
fails going through above line on the way out (fails PIO_open that tests
if new db already exists which it doesn't).
--
Neil McCalden
Mike Nordell
2003-08-28 02:36:02 UTC
Permalink
Post by Neil McCalden
I think I might currently hitting a problem with this in err.cpp
ERR_punt()
#pragma FB_COMPILER_MESSAGE("FIXME! C functions can not throw! FIXME!")
Firebird::status_exception::raise(tdbb->tdbb_status_vector[1]);
That is indeed a problem... What I had in mind previously was to add another
function ERR_punt_CPP. That function would have C++ linkage and would indeed
allow exceptions to be propagated. All current usage of ERR_punt (from C++
code) would then be changed to use that new function.

If you want to hav a look at it, it would be much appreciated.

/Mike

Nickolay Samofatov
2003-08-26 13:19:22 UTC
Permalink
Hello, Mike !
Post by Mike Nordell
Post by Nickolay Samofatov
I think we need to specify explicitly which firebird functions
and methods can throw exceptions and which cannot.
For this to be useful it would have to be performed in one fell swoop. Are
you volunteering?
No. I'm just fixing code which I touch.
Post by Mike Nordell
Post by Nickolay Samofatov
Or we want to introduce compatibilty macros
like all libraries do ? Anyway, having compatibility macros is good
because it is better to declare public API as throw() if headers are
used from C++ programs and omit exception specification if used from C
programs.
Useless. C programs can only use C functions. C functions cannot throw -
alas throw() specification for such functions are not only redundant when
the header is parsed as a C++ translation unit, it can generate even worse
code using a conforming compiler.
No. Conforming compilers perform exception initiate frame analysis
optimisation pass (GCC and MSVC are documented to do it) and if all
called functions cannot throw they do not install exception handling
frame to unwind local objects and enforce exception specification
(MSVC doesn't enforce it anyway). Having explicit exception
specification should improve results of this optimization.
Post by Mike Nordell
Besides, a C linkage function with an exception specification makes no sense.
No. You cannot specify exception clause in "C" linkage in some
compilers, but MSVC6 always assumes that such functions can throw
exceptions. MSVC7 has /EHc+ clause which is turned off by default
(meaning that "C" functions cannot throw), but it is turned on in
our MSVC7 build otherwise source doesn't compile cleanly (as our
"C" functions do throw exceptions). AFAIR, GCC accepts and uses
exception clause in "C" linkage functions.
Post by Mike Nordell
Post by Nickolay Samofatov
Look at GLIBC sources for examples. Having exception markup
1) when compiler sees throw() specification it can emit much better code
No it can't. I was also once led to believe this - but it's false. Should a
function having a throw() ES be called (C++ compilers does not like Java
have static (compile-time) exception checks), and that function itself calls
function having either no or throw(...) ES, it must itself set up a "hidden"
try/catch(...) to be able to call unexpected() on a conforming
implementation.
The _only_ way to tell VC that a function is not throwing any exceptions is
MSVC 7.0/7.1 is documented to have "throw()" equivalent to
"__declspec(nothrow)". Could you check it ?

In fact, throw(...) is Microsoft extension so we can use
throw(std::exception) instead and disable C4290 warning on MSVC.
This will add some compile-time safety because compilers may be asked
to emit warning if you use functions throwing exceptions from functions
that cannot throw.
Post by Mike Nordell
Post by Nickolay Samofatov
2) some relatively large parts of the engine execute inside the signal
handlers while not all compilers emit signal-safe code for
exceptions, so it would be better to make compiler check if we are
doing something wrong.
Then the engine is broken. There are IIRC exactly three things you can do
1. access variables of type volatile sig_atomic_t.
2. (successfully) call signal().
3. return to caller.
That's it. Anything else invokes undefined or implementation defined
behaviour.
No, it seems you have looked at very old spec. Now signal() call is
deprecated and may not be defined unless you explicitly request simulation
of old logic (obviously broken). sigaction() and setsigops() rule now.
You can also call signal-safe functions (they are not difficult to
write - you can cheaply disable signals for short periods of time).
POSIX specifies a few functions explicitly as signal-safe. kill(),
read(), write() must be singal-safe, for example.
Post by Mike Nordell
/Mike
--
Nickolay Samofatov
Mike Nordell
2003-08-27 01:59:02 UTC
Permalink
Post by Nickolay Samofatov
Post by Mike Nordell
Useless. C programs can only use C functions. C functions cannot throw -
alas throw() specification for such functions are not only redundant when
the header is parsed as a C++ translation unit, it can generate even worse
code using a conforming compiler.
No. Conforming compilers perform exception initiate frame analysis
optimisation pass
Iff some compilers do, it has nothing to do with whether they are conforming
or not - it's just a matter of QOI.
Post by Nickolay Samofatov
(GCC and MSVC are documented to do it)
I think compiler versions would be worth listing, since I believe this to be
rather recent additions.
Post by Nickolay Samofatov
and if all
called functions cannot throw they do not install exception handling
frame to unwind local objects and enforce exception specification
(MSVC doesn't enforce it anyway).
Introducing large differences in how the code behaves depending on what
compiler it's compiled with. This I don't like.
Post by Nickolay Samofatov
Having explicit exception
specification should improve results of this optimization.
... for some compilers. (I'm not arguing, merely making the point clear)
Post by Nickolay Samofatov
Post by Mike Nordell
Besides, a C linkage function with an exception specification makes no sense.
No. You cannot specify exception clause in "C" linkage in some
compilers, but MSVC6 always assumes that such functions can throw
exceptions.
/EHc takes care of that.
Post by Nickolay Samofatov
MSVC7 has /EHc+ clause which is turned off by default
(meaning that "C" functions cannot throw), but it is turned on in
our MSVC7 build otherwise source doesn't compile cleanly (as our
"C" functions do throw exceptions).
That was what I was afraid of, and something that must be fixed. If planning
to add (at least "throw()") ES as functions are visited, it would be wise to
at the same time move those functions from C to C++ linkage.
Post by Nickolay Samofatov
AFAIR, GCC accepts and uses exception clause in "C" linkage functions.
I wouldn't approve of adding g++ extensions into the code, adding even more
differences in how it's parsed and/or behaves depending on what compiler is
used.
Post by Nickolay Samofatov
Post by Mike Nordell
The _only_ way to tell VC that a function is not throwing any exceptions is
MSVC 7.0/7.1 is documented to have "throw()" equivalent to
"__declspec(nothrow)". Could you check it ?
Shit. Yes, I did check it now, and once again I've been fooled by the words
of a Microsoft compiler "engineer". Before 7.0 was released I was told
*explicitly* that if VC(7+) was to become conforming, throw() ES would have
to generate EH frame to be able to call unexpected(), and that the *only*
way to make the compiler really believe you didn't throw any exceptions
would be by __declspec(nothrow).

How come I'm so stupid to trust the words of even a compiler "engineer"? I
should know by now that anything and everything Microsoft says is with
almost 100% certainty a big fat lie, shouldn't I?

OK, yet another inconsistency, and difference between compilers.
Post by Nickolay Samofatov
In fact, throw(...) is Microsoft extension so we can use
throw(std::exception) instead and disable C4290 warning on MSVC.
I'd rather have no ES at all on such functions. For one thing, as you
yourself pointed out too, MSVC doesn't honor any other ES than throw(), and
adding "real" ES to such functions would include another source of compilers
generating different code, making the final binary behave differently
depending on what compiler was used to produce it. That's not a can of worms
I'm willing to open.
Post by Nickolay Samofatov
Post by Mike Nordell
Post by Nickolay Samofatov
2) some relatively large parts of the engine execute inside the signal
handlers while not all compilers emit signal-safe code for
exceptions, so it would be better to make compiler check if we are
doing something wrong.
Then the engine is broken. There are IIRC exactly three things you can do
1. access variables of type volatile sig_atomic_t.
2. (successfully) call signal().
3. return to caller.
That's it. Anything else invokes undefined or implementation defined
behaviour.
No, it seems you have looked at very old spec.
It's almost exactly five years old. Furthermore it's the *only* spec in this
case:
The ISO/IEC 14882 document - also known as the C++ standard. To quote:

1.9/9
"When the processing of the abstract machine is interrupted by receipt of a
signal, the values of objects with type other than volatile sig_atomic_t are
unspecified, and the value of any object not of volatile sig_atomic_t that
is modified by the handler becomes undefined."

If *in practice* a POSIX system does something else, and a C++ program is
making use of that assumption, it is explicitly invoking undefined
behaviour.


But in all, I agree with adding "throw()" ES to (obviously, only C++
linkage) functions that can't throw exceptions. C linkage functions should
(again, obviously) not have any ES. I'm so far not convinced any other ES
would be of value, especially with the mentioned difference in final binary
behaviour it would introduce.


/Mike
Nickolay Samofatov
2003-08-27 10:46:12 UTC
Permalink
Hello, Mike,
Post by Mike Nordell
Post by Nickolay Samofatov
No. Conforming compilers perform exception initiate frame analysis
optimisation pass
Iff some compilers do, it has nothing to do with whether they are conforming
or not - it's just a matter of QOI.
I meant that compilers that really conform to the spec need to
implement this optimization otherwise they would generate extremely
inefficient code.
Post by Mike Nordell
Post by Nickolay Samofatov
(GCC and MSVC are documented to do it)
I think compiler versions would be worth listing, since I believe this to be
rather recent additions.
I played with MSVC7 and GCC 3.2 regarding the subject. Cannot say
anything about other compilers.
Post by Mike Nordell
Post by Nickolay Samofatov
and if all
called functions cannot throw they do not install exception handling
frame to unwind local objects and enforce exception specification
(MSVC doesn't enforce it anyway).
Introducing large differences in how the code behaves depending on what
compiler it's compiled with. This I don't like.
If our code behaves correctly, explicit declaration of exception
specification cannot make it behave incorrectly.
Engine is not ready to handle anything except std::exception.
BTW, handling of generic std::exception descendants is not correct
now. User doesn't get any error message for such exceptions.
Post by Mike Nordell
Post by Nickolay Samofatov
Having explicit exception
specification should improve results of this optimization.
... for some compilers. (I'm not arguing, merely making the point clear)
Post by Nickolay Samofatov
Post by Mike Nordell
Besides, a C linkage function with an exception specification makes no
sense.
Post by Nickolay Samofatov
No. You cannot specify exception clause in "C" linkage in some
compilers, but MSVC6 always assumes that such functions can throw
exceptions.
/EHc takes care of that.
Yes, but this switch is only for MSVC7+. ;)
Post by Mike Nordell
Post by Nickolay Samofatov
MSVC7 has /EHc+ clause which is turned off by default
(meaning that "C" functions cannot throw), but it is turned on in
our MSVC7 build otherwise source doesn't compile cleanly (as our
"C" functions do throw exceptions).
That was what I was afraid of, and something that must be fixed. If planning
to add (at least "throw()") ES as functions are visited, it would be wise to
at the same time move those functions from C to C++ linkage.
Yes, this needs to be done. But I do not have plans to do it.
I'm trying to fix only parts of code that are releated to my current
tasks (otherwise they will never be finished).
Post by Mike Nordell
Post by Nickolay Samofatov
AFAIR, GCC accepts and uses exception clause in "C" linkage functions.
I wouldn't approve of adding g++ extensions into the code, adding even more
differences in how it's parsed and/or behaves depending on what compiler is
used.
throw() specification is beneficial for compilers that support it.
Look at public headers for widely used modern "C"-language libraries.
All declarations there look like this:

extern "C" {
void some_function() NOTHROW_SPEC;
}

while NOTHROW_SPEC is defined as "throw()" for all compilers that
support it. Otherwise compiler is forced to generate less-efficient
code in many cases.
Post by Mike Nordell
Post by Nickolay Samofatov
In fact, throw(...) is Microsoft extension so we can use
throw(std::exception) instead and disable C4290 warning on MSVC.
I'd rather have no ES at all on such functions. For one thing, as you
yourself pointed out too, MSVC doesn't honor any other ES than throw(), and
adding "real" ES to such functions would include another source of compilers
generating different code, making the final binary behave differently
depending on what compiler was used to produce it. That's not a can of worms
I'm willing to open.
I also suppose that we should add "throw()" to functions that are not
expected to throw exceptions. I also think that we should move all
functions except the ones from public API out of extern "C".
And add NOTHROW_SPEC to functions from public API.
Blas, could you help with this subject ? This should improve quality
of resulting binary code while should not be very difficult for you.
Post by Mike Nordell
Post by Nickolay Samofatov
Post by Mike Nordell
Post by Nickolay Samofatov
2) some relatively large parts of the engine execute inside the signal
handlers while not all compilers emit signal-safe code for
exceptions, so it would be better to make compiler check if we are
doing something wrong.
Then the engine is broken. There are IIRC exactly three things you can
do
Post by Nickolay Samofatov
Post by Mike Nordell
1. access variables of type volatile sig_atomic_t.
2. (successfully) call signal().
3. return to caller.
That's it. Anything else invokes undefined or implementation defined
behaviour.
No, it seems you have looked at very old spec.
It's almost exactly five years old. Furthermore it's the *only* spec in this
POSIX standard weakens this requirement because it specifies masking
of signals (which may be used like mutex to protect data) and
signal-safe functions. Anyway, singal masking is not used to access
ast_flags and this variables indeed should be "volatile sig_atomic_t".
In addition, the same flags are used for AST manipulation and normal
access in some places (BDB flags is an example). Looking at code I
suppose those were recent additions made by Borland as original code
clearly separates flags and ast_flags.

But the code of engine is generally signal-safe at least when
built with GCC on Linux x86. I can say it because I examined assembly
that GCC generates and I also looked at signal-dispatching code inside
the Linux kernel. I suppose that on other architectures there should
be no problems unless this platforms use some kernel-driven memory
caching technology. Firebird doesn't support such platforms now and
I have never seen such machines working. I know about their existence
only because Java language specification has some interesting
tweaks to enable support for them - you must wrap multi-threaded access
to non-volatile variables in monitor locking instructions otherwise you
are not guarantied to get recent copy of the data for them.
--
Nickolay Samofatov
Mike Nordell
2003-08-28 02:35:43 UTC
Permalink
Post by Nickolay Samofatov
Post by Mike Nordell
and if all called functions
cannot throw they do not install exception handling
frame to unwind local objects and enforce exception
specification (MSVC doesn't enforce it anyway).
Introducing large differences in how the code behaves depending
on what compiler it's compiled with. This I don't like.
If our code behaves correctly, explicit declaration of exception
specification cannot make it behave incorrectly.
If adding any other exception specifications than "throw()", the code will
perform differently depending on what compiler is used.
Post by Nickolay Samofatov
Engine is not ready to handle anything except std::exception.
... and all types inheriting std::exception. This includes *any* exception
in a reasonably standard C++ program.
Post by Nickolay Samofatov
BTW, handling of generic std::exception descendants is not correct
now. User doesn't get any error message for such exceptions.
Could you please elaborate?

I'm aware the code currently doesn't even use the exception argument in its
catch clause, which is odd, but is instead currently modeled to use the
explicitly set error values. For an example, have a look at alice.cpp, line
618. There you should find EXIT(FINI_OK);, and a few lines down you'll find
"catch (const std::exception&)". The code never uses the exception thrown,
but it indeed gets the error value by means of the definition of the macro
EXIT.

It's by no means an optimal design, but I'd say it's still way better than
the previous setjmp/longjmp mess. It was also the path of least resistance
(the minimum of work for the maximum of effect) when doing the conversion.

If you look at the definition of e.g. Firebird::status_exception it's
already got in place the needed functionality (the value() member function)
to "do the right thing", why it's just to "dig in" and start doing the
exception catching even better. :-)
Post by Nickolay Samofatov
Post by Mike Nordell
No. You cannot specify exception clause in "C" linkage in some
compilers, but MSVC6 always assumes that such functions can
throw exceptions.
/EHc takes care of that.
Yes, but this switch is only for MSVC7+. ;)
May I suggest a piece of documentation, or maybe even just starting the VC6
compiler from command line with argument "/?" ? :->

This switch has been present since at least VC6 without service packs (about
half a decade by now). What was added in VC7 was the [+-] postfix.
Post by Nickolay Samofatov
Post by Mike Nordell
(as our "C" functions do throw exceptions).
That was what I was afraid of, and something that must be fixed.
If planning to add (at least "throw()") ES as functions are visited,
it would be wise to at the same time move those functions from
C to C++ linkage.
Yes, this needs to be done. But I do not have plans to do it.
Then it seems you're not going to add even "throw()" ES after all for those
functions, right?
Post by Nickolay Samofatov
I'm trying to fix only parts of code that are releated to my current
tasks (otherwise they will never be finished).
I can appreciate that, but if that includes adding "throw()" ES to
functions, and those functions are currently declared extern "C",
surely they must be modified to have C++ linkage.

My personal opinion is that we have more than enough language violations in
the code and should instead work towards decreasing, not increasing their
count. But that's just me...
Post by Nickolay Samofatov
throw() specification is beneficial for compilers that support it.
Look at public headers for widely used modern "C"-language libraries.
Will not. "Modern" C implies C99 - something C++ couldn't care less about.
Post by Nickolay Samofatov
extern "C" {
void some_function() NOTHROW_SPEC;
}
Speak about language violations. This can't be anything but gcc-isms, can
it? Could you provide a pointer to one single (publicly available) library
using such language violations?
Post by Nickolay Samofatov
I also suppose that we should add "throw()" to functions that are
not expected to throw exceptions.
Not sure about that. I'm all for adding it to (again, only C++) functions
that are expected to not throw exceptions. I'm however not so sure it should
be added for functions that are not expected to throw exceptions.
Post by Nickolay Samofatov
I also think that we should move all
functions except the ones from public API out of extern "C".
I most certainly agree. Putting them in extern "C" was a Q&D way to be able
to change the code incrementally from C->C++. Since that is now complete,
extern "C" for those functions (and types) has served its purpose and should
in the end be all but a memory.
Post by Nickolay Samofatov
And add NOTHROW_SPEC to functions from public API.
No! There is now way a C function can legally throw a C++ exception. It
makes no sense to have ES for C functions. It's actually explicitly
sensless. C++ compilers that can't (be made to) understand this are IMNSHO
broken.

<snip>
Post by Nickolay Samofatov
But the code of engine is generally signal-safe at least when
built with GCC on Linux x86.
So if using Linux x86 and compiling the code with GCC it generally works,
but any other (POSIX-) platform or compiler might or might not work (at
all) - possibly thanks to the premeditated language violations? Maybe I'm
missing something here, but how could this be considered a Good Thing(tm)?
:-)


/Mike
Loading...