This is the mail archive of the gcc@gcc.gnu.org mailing list for the GCC project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

Adding to G++: Adding a warning on throwing unspecified exceptions.


I have been following the development of C++0x and ConceptGCC and it
has got me interested in developing for G++.
I've haven't yet dived far into the G++ code, but I have just been
reading the GCC internals documentation at
http://gcc.gnu.org/onlinedocs/gccint/index.html. (Of course I was
horrified to see it's not written in C++, and it's loaded with macros
--- why??).

I must admit I'm very slow at reading other people's code. However, I
think it might be a good thing to try and add something to G++, mainly
as it would be a great learning experience.

What I'd like to add is something that I've seen many people request,
and something I really want to use:
Adding a -W option to check that all function calls and throws comply
with the throw() clause of the function they are launched from.

This means that if you have a function "void foo() throw(bar);", a
warning will be produced if:
- foo's source calls "bam() throw(zug);", unless inside a try block
that with an associated catch(zug).
- foo's source calls any function declared without a finite set of
throwable exceptions. ie "void fub();"
- if it throws anything except a bar, unless inside a try block with
an appropriate catch.
- if it calls a function pointer whose throw clause is anything except
throw(bar) or throw().
- if it casts a function pointer to another function pointer type
whose throw clause is less restrictive. Ignores explicit casts (unless
specified by an additional warning specifier).

I would expect that using this warning would cause a few annoyances. Namely:
A) Common third party code (perhaps even standard library code) is
likely to not follow the throw() clause rigidly. However, they
probably should.
B) Every function called inside a function with a throw clause (unless
called from a within a try block with a catch(...)) would also have to
have a restricting throw clause, and catch(...)es are not universally
approved.
C) typedef'd function pointer types cannot have throw() clauses,
meaning you can't cast them to throwing function pointer variables
without generating my warning. This pretty much prevents use of
typedef'd function pointers wherever throw restrictions exist.



My questions are:
1) Is my task a sensible one? Is there anything I have got fundamentally wrong?
2) Is there anyone currently doing this? I'd hate to simply duplicate
their effort.
3) I've just been taking a glance at the GCC code. Unfortunately I
don't have a guide that shows how g++ works or what files I should be
reading so it might take some time to figure out.
I can't find much readable documentation about the source. Does anyone
have any good documentation links about the overall program flow
through g++, files/functions etc? I need a primer.



Back to the problem:

I'm thinking of using -Wthrow-comply and -Wthrow-comply-explicit
(which checks explicit function pointer casts).

Additional useful warning flags will be:
-Wthrow-dtor when a destructor isn't declared to throw nothing.
-Wthrow-move for c++0x, when a move constructor isn't declared to
throw nothing[#1].
-Wthrow-all: Same as -Wthrow-comply -Wthrow-dtor -Wthrow-move
-Wthrow-all-explicit: Same as -Wthrow-comply-explicit -Wthrow-dtor -Wthrow-move
4) Is this too complex? Should I just stick with one or two? Or does
this sound appropriate.



Now, I'm starting right from the beginning so this is probably a bit
of an ambitious task.
My guess is that these are the steps I should go through:

Step 1:
Examine how, in GCC, to add and test for a parameter GCC: -Wthrow-except

Step 2:
Learn how to emit a warning, eg:
"Warning: using -Wthrow-test flag."

Step 3:
Learn about how the (generic?) tree of statements inside a function is built.
http://gcc.gnu.org/onlinedocs/gccint/Trees.html
Learn about these objects and macros:
Throw statements: ??
Function calls: CALL_EXPR
Function pointers: TYPE_PTRFN_P
try/catch blocks: TRY_BLOCK and HANDLER.

Step 4:
Write a quick (generic?) tree output to text routine that lists as
much of the tree as is relevant.
This tool probably already exists, but it would be good to learn how to do it.

Step 5:
Learn about how to query a functions' throw() clause with
TREE_RAISES_EXCEPTIONS.

Step 6:
For each function, step through all statements and sub-statements checking for:
- throw statements
- function calls
- try/catch blocks.
And for each of these, test their exception(s) vs the allowed
exceptions in the throw() clause of the function or in the catch()
parameters.
Also test for function pointer assignments.
If any fail, emit appropriate warnings.

5) Does this sound accurate? Am I missing anything?



6) Should a test like what is done in Step 5 be built as a standalone
function that is called once after the tree has been assembled (like a
separate compilation step), or should I fit my per element tests
beside other per-element operations, such as (I'm guessing) the code
that might enforce access permissions (private, protected)?



7) To someone new to the g++ source the included documentation seems
pretty poor and cryptic. README mentions non-existant files,
INSTALL/README says it's obsolete and redirects to a file "index.html"
which doesn't yet exist. And why does the documentation have all these
.texi, .info and (unlinked) manpage .1/.7 files. What's wrong with
.txt, .html or similar? Shouldn't documentation be available from the
download instead of having to go online to actually find out how to
build and read the documentation itself - the documentation doesn't
really take up much space. In this age do manpages actually have any
advantages?





NOTES FROM ABOVE:
[#1] I Suggest reccomending that move constructors ( T::T(T&&); )
throw nothing. Why? Because during handling of the exception the move
constructor may again need to be called, which could lead to the
exception handler throwing and leaving you with two exceptions to
handle, which is unsupported (for good reason) and IIRC causes
immediate process termination. This is the same reason that
destructors shouldn't throw. The reasoning here isn't as definite as
for the case for destructors, as it refers only to containers of the
type.

- In C++0x, move constructors are going to be used often by containers
like std::vector when the container elements need to be opaquely
relocated.
- If a std::vector (or similar container) algorithm calls an element
member function that throws, the vector (or container) should be
restored to its initial state.
- Because the algorithm doing the moving may require the moving of
multiple container elements, the move that throws may not be the first
of the algorithm, and thus other moves may need to be rolled back if
the vector (or container) is to be restored.
- Rolling back these earlier moves require more moves from the
destination back to the source.
- Since the rolling back is occuring in the exception handler, if one
of the roll-back moves also throws an exception you now have two
exceptions.

As I recently found out, C++0x concepts are unable to specify the
throw requirements of functions, so they can't fix this as I had
hoped.

I plan to write this up as a separate idea/proposal later.





So, what do you think?
Is this a good project?
Do you know a good primer for g++ internals?

Thanks for all help.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]