gnu-arch-users
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [Gnu-arch-users] Nit


From: Tom Lord
Subject: Re: [Gnu-arch-users] Nit
Date: Tue, 21 Oct 2003 11:02:20 -0700 (PDT)



    Mark:
    >>> Because many authors will think that their software layer should throw
    >>> certain exceptions and only those exceptions.

    >> Tom
    >> That's a good principle but it's only half the story.

    >> Each call should only _expect_ certain exceptions and no exception
    >> should get past a call-site that doesn't explicitly say it's expected.

    >> Suppose I add a new throw to a function I maintain -- a new exception
    >> that that function might throw.  Must I find all callers and make sure
    >> that the new exception type won't screw them up?

    > [lots of stuff about catching all possible exceptions and passing ones 
    > you don't intend to handle]

    > Dustin:

    > The part that you're missing here

I don't have Java on the tip of my tongue and, indeed, forgot about
"throws" clauses -- but they don't cut it.   You still want call-site
enumeration of what exceptions to forward.


    > is that exceptions are part of the API of a method call in java.
    > If you add a new exception type, code that ignores it will fail
    > to compile.

Not always, no.   For example:

At time T0:

*) A method, B_fn, in class B is declared to throw 'no_such_user'.
*) A method, C_fn, in class C is declared to throw 'no_such_user'.
*) A method, D_fn, in class D is _not_ declared to throw 'no_such_user'.

*) The implementation of B_fn calls C_fn and D_fn.  These calls are
   not lexicially inside of a `try' construct.

   The author of B_fn has thought "Well, my function can generate
   no_such_user when it calls C_fn.  It's reasonable for B_fn to
   generate that exception if the call to C_fn fails.  I'll just let
   the exception forward to my callers automatically.  Meanwhile, D_fn
   can throw `landshark_candygram' and I want to forward that in
   exactly the same way.


At time T1:

*) D_fn is modified.   It can now throw 'no_such_user'.

Everything still compiles just fine, but now B_fn can wind up throwing
no_such_user for an entirely new reason.  A previously written handler
around a call to B_fn will handle it: incorrectly.

The problem here is that exceptions should not, in general, be
forwarded blindly.   There is some code where that's the right thing,
but plenty of code where it isn't.    To be really robust, each call
site should be explicitly decorated with a list describing what
exceptions to forward.


    > I think this is part of the confusion regarding exceptions.  Languages 
    > that don't support compile-time checked exceptions have frightening 
    > side-effects of using exceptions.  Those seem to be the problems you're 
    > discussing.  

Static vs. dynamic checking is really a red herring here.   Those are
not the problems I'm discussing.   But let's look at it:

The checking implemented in Java has a specific purpose:

        "This compile-time checking for the presence of exception
        handlers is designed to reduce the number of exceptions which
        are not properly handled."
            Java Language Specification, second edition, 11.2

That's a pretty weaselly statement.  What the static checking actually
_does_ is:

        eliminate exceptions which are not handled

but what it fails to do is:

        ensure that exceptions are handled by a handler 
        written to receive them 

If static checking is important to you, you could semi-fix this
situation with a new tool, and a new coding convention:

*) the tool

   java-exceptions-lint:   
        Verify that every method invocation is wrapped in a try
        clause, unique to that method invocation, and that all 
        of the exceptions the method might generate are handled
        by that try clause.

        (As an exception, just as with traditional C lint, perhaps
         you could just put a special comment near some method calls,
         indicating that those calls should not be checked.)

*) the convention

   the rule of consistent exception meanings
        Suppose that you add a new throw statement to a method.
        What type of exception should it throw?   Before adding the
        new throw, the method will already have a set of exceptions
        it may cause.   There are two cases:

        1) same handlers

           If all conceivable handlers of an already present throw or
           forward are appropriate to handle the new throw, then the
           new throw may be use an exception of the same class as the
           old throw or a subclass.

        2) different handlers

           If any conceivable handlers of an already present throw
           are _not_ appropriate for the new throw, then the new
           throw _must_not_ throw an error of the same class or of
           any subclass.

   Heck, the java-exceptions-lint program could help enforce this
   rule if every exception-generating expression has to be decorated
   with a comment like:


                /* $reason-14, $reason-21 */

   and each method declaration with comments like:

        // $throws foo.bar.no_such_user
        // 
        // reason-14:  the `user' parameter to this procedure names
        //             a non-existent user
        // 
        // $throws bing.bop.admin_user_missing
        // 
        // reason-15:  the system does not have a user named "admin"
        // 


   A call that can accidentally forward foo.bar.no_such_user will be
   flagged by lint until either the programmer adds $reason-14 or
   converts the exception to bing.bop.admin_user_missing and
   references $reason-15.


There's also a technique that might be useful.   I've talked about
turning all "unrecognized" exceptions at a given call into aborts.
That might be too drasting for some libraries.   The alternative is to
convert them into a "library abort".   In other words, if you have
library Foo, then methods in that library might be declared to throw
'Foo_abort' and if, deep in a callstack into Foo, you get an unhandled
exception:  rethrow it under a new type: `Foo_abort'.


    > If the language only has unchecked exceptions (C#, OCaml, 
    > etc...) then you have to really know what can go wrong whenever you 
    > call a function, and you've got to hope it doesn't change.  IMO, 
    > checked exceptions are necessary for a stable API.

Checked exceptions, in the Java sense, are too weak to accomplish your
goal.   They check that all of your callers who might forward your
exceptions are declared to forward all the types of exceptions you
might generate -- but they fail to check that at each call-site in
your callers, the forwarding is intentional.    As the example above
illustrates, code can easily change under Java-style checking and lead
to the wrong handler being invoked.

This doesn't look any better to me than the possibility that a C
programmer might call malloc without checking for 0 or open without
checking for -1.  If anything, the false sense of security you get
from an exception checker that claims to "reduce the number of
exceptions which are not properly handled" makes Java exceptions the
more dangerous approach.

The bottom line: really robust code needs to check for every possible
error condition at every call.  Everything else is mostly just a
choice of syntax and implementation details.

-t




reply via email to

[Prev in Thread] Current Thread [Next in Thread]