Topic: lifetime of temporary copy of exception


Author: vinoski@apollo.hp.com (Steve Vinoski)
Date: Thu, 1 Jul 1993 15:17:37 GMT
Raw View
In article <rfgC9FH27.D54@netcom.com> rfg@netcom.com (Ronald F. Guilmette) writes:
>In article <C91yt0.7D7@apollo.hp.com> vinoski@apollo.hp.com (Steve Vinoski) writes:
>>The text below it says:
>>
>>"A throw-expression initializes a temporary object of the static type
>>of the operand of throw and uses that temporary to initialize the
>>appropriately-typed variable named in the handler."
>
>Right again.  But what do *you* think is meant by "the appropriately-typed
>variable"?
>
>If you look in section 8.4.3 of the 1/93 X3J16 working paper you will see:
>
> "A variable declared to be a T& ... must be initialized by an
> object of type T or by an object that can be converted into a T."
>
>Now several knowledgable implementors I know, along with several existing
>implementations, interpret the term "object", when used in this context
>to mean exactly (and only) "an lvalue expression".
>
>I admit that the sentence I have quoted is badly in need of clarification
>by x3j16, but this is the interpretation that at least some implementors
>are using... and I happen to think that it is the correct interpretation.
>
>What about the IBM and HP implementations?  Do either of them allow the
>second initialization shown below?  If so, they may be failing to properly
>enforce the above rule.
>
> extern int foobar ();
> const int &cir = foobar (); // definitely OK; see 8.4.3
>       int & ir = foobar (); // error: non-lvalue initializer

HP C++ says:

CC: "foo.cc", line 3: error: initializer for global non-const reference not an lvalue (1730)

I'm sure IBM's C++ compiler gets this right, too.  C'mon Ron, if HP
C++ didn't get this right, then lots of other cfront-based compilers
would be broken too, and we would have known this a long time ago.


>Anyway, the point is that I believe that there is a general and overriding
>rule, which applies to the entire language, which requires that the value
>of a non-lvalue expression can ONLY be used to initialize a reference-to-const
>type of reference.
>
>If you agree that there is indeed such a rule, and that it applies universally
>throughout the language (which it really should, or else we can get all sorts
>of nasty surprizes) then I think you would have to agree that any throw
>which throws a non-lvalue T must not be caught by a catch-clause which is
>declared to catch T& values.  (Such catching would involve a kind of an
>initialization which is invalid according to 8.4.3.)  Such a thrown value
>could however by caught by some catch-clause declared to catch a `const T&'
>value.

I have no problem agreeing that the sentence you quoted above from
section 8.4.3 applies to the whole language where appropriate.
However, that section is not appropriate to the exception handling
mechanism; section 15.2 of the 1/93 WP states:

"Throwing an exception transfers control to a handler.  An
****object**** [emphasis mine] is passed and the type of that object
determines which handlers can catch it."

In other words, throwing an object always results in a copy operation.
If you throw an int, the int is copied.  If you throw a pointer, the
pointer is copied.  If you throw an object of a class type, that
object is copied.  You cannot throw a reference.  No throw can result
in a dangling reference because the thrown objects are always copied.
Since the object is copied, a non-const reference to it in the catch
clause is legal.

The area to which the object is copied is what Kim Knuttila hinted at
in a previous posting (<C991Ey.BoE@hawnews.watson.ibm.com>):

kim> I, too, think Marc is reading things correctly. While the Working
kim> Paper of the ISO/ANSI committee may be slightly vague on the
kim> exact lifetime of the exception temporary, certainly throwing
kim> objects and catching them as references works. (The
kim> implementations that I know of use a separate pool of storage for
kim> exception objects.) The exception object lives as long as it is
kim> 'needed'. This is essentially until the normal-exit from its
kim> handler is encountered, and the compiler/EH Run Time is expected
kim> to manage this.

Again, from section 15.2 of the 1/92 WP:

"A throw-expression initializes a ****temporary object**** [emphasis
mine] of the static type of the operand of throw and uses that
temporary to initilaize the appropriately-typed variable named in the
handler.  Except for the restrictions on type matching mentioned in
15.4 and the ****use of a temporary variable**** [emphasis mine], the
operand of throw is treated exactly as a function argument in a call
or the operand of a return statement."

In HP C++, the thrown object is copied into static storage protected
by runtime support.  From Kim's posting it seems that IBM's
implementation does the same thing.


>>"A handler with type T, const T, T&, or const T& is a match for a
>>throw expression with an object of type E if
>>
>>  [1] T and E are the same type, or
>>  [2] T is an accessible base class of E at the throw point, or
>>  [3] T is a pointer type and E is a pointer type that can be
>>      converted to T by a standard pointer conversion at the throw
>>      point."
>>
>
>I'd say this is just a minor mistake in the ARM (and in the WP).  The
>first list of types in the above quotation should NOT include T&.  If
>you take that out, it all makes sense again.

Given what I've shown above, you still want to stand by your claims?


>>I agree that the rules are murky and are not finalized, but I think
>>he's reading things correctly.  I know that the HP C++ exceptions
>>implementation goes to great lengths to ensure that such temporaries
>>are properly available to the catch clause.
>
>I claim that the HP implementation is erroneously permitting violations
>of the rule I quoted above (from section 8.4.3).

I don't think so.

An aside: HP C++ is implemented according to the specification.  You
may argue that the spec is wrong, or you can say that HP C++ does not
adhere to the spec, but you cannot claim that both are wrong.

--steve

Steve Vinoski  (508)436-5904   vinoski@apollo.hp.com
Distributed Object Computing Program
Hewlett-Packard, Chelmsford, MA 01824       These are my opinions.




Author: krk@charmed.torolab.ibm.com (Kim Knuttila)
Date: Sat, 26 Jun 1993 22:06:34 GMT
Raw View
In article <C91yt0.7D7@apollo.hp.com>, vinoski@apollo.hp.com (Steve
Vinoski) writes:
> In article <rfgC908nE.A42@netcom.com> rfg@netcom.com (Ronald F.
> Guilmette) writes:
> >In article <GIROD.93Jun16122007@dshp01.trs.ntc.nokia.com>
> marc.girod@ntc.nokia.com writes:
> >>Hi netters!
> >>
> >>I am about to recommend to people in my team the following way of
> >>using exceptions:
> >>
> >>class Exception { /*...*/ };
> >>
> >>void foo() {
> >>  // ...
> >>  throw Exception(/*...*/);
> >>  // ...
> >>}
> >>
> >>void bar() {
> >>  try {
> >>    // ...
> >>    foo();
> >>    // ...
> >>  }
> >>  catch (Exception& e) { /*...*/
> >>}
> >>
> >>I am a bit worried by the lifetime of the temporary copy of the

[ snip, snip... lines of ancilliary issues and correct quotes deleted as ]
[        I'm only commenting on the 'lifetime of exception temp's issue' ]

Steve Vinoski writes (and I agree :-):
>
> I agree that the rules are murky and are not finalized, but I think
> he's reading things correctly.  I know that the HP C++ exceptions
> implementation goes to great lengths to ensure that such temporaries
> are properly available to the catch clause.  See the paper from the HP
> California Language Labs folks in the 1992 Usenix C++ Conference
> proceedings for some good clarification of these issues.
>

I, too, think Marc is reading things correctly. While the Working Paper
of the ISO/ANSI committee may be slightly vague on the exact lifetime
of the exception temporary, certainly throwing objects and catching them as
references works. (The implementations that I know of use a separate
pool of storage for exception objects.) The exception object lives as
long as it is 'needed'. This is essentially until the normal-exit from its
handler is encountered, and the compiler/EH Run Time is expected to
manage this.

See p.358 in the ARM for a further hint that this must be the case.

  "The definition of throw; implies that the exception handling implementation
   needs a concept of a ''current exception.'' It also implies that although
   an exception is considered handled on entry to a handler, the current
   exception cannot be deallocated until exit from its handler"

[Remember that
    catch(A x) {
      // do some stuff with x...
      throw; // rethrow the original
    }
 causes the original exception object to be re-thrown. To make this
 work, the original one is hanging around in limbo, waiting for a
 normal exit from the handler to occur. In fact, the run time must be
 able to live with a stack of these limbo guys, since in the do-stuff step
 above, you could have... more throw statements!]

The IBM implementations of C++ (xlC++ on RS/6000 and C Set ++ on OS/2)
both support EH in this fashion, and I believe this is in agreement with
the HP implementation.

> --steve
>
> Steve Vinoski  (508)436-5904   vinoski@apollo.hp.com
> Distributed Object Computing Program
> Hewlett-Packard, Chelmsford, MA 01824       These are my opinions.

--
Regards,
Kim Knuttila (knuttila@vnet.ibm.com)
C/C++ Development,
IBM Toronto Laboratory       All opinions are mine, and not necessarily
                             related to opinions of my employer.




Author: rfg@netcom.com (Ronald F. Guilmette)
Date: Wed, 30 Jun 1993 09:30:07 GMT
Raw View
In article <C91yt0.7D7@apollo.hp.com> vinoski@apollo.hp.com (Steve Vinoski) writes:
>In article <rfgC908nE.A42@netcom.com> rfg@netcom.com (Ronald F. Guilmette) writes:
>>In article <GIROD.93Jun16122007@dshp01.trs.ntc.nokia.com> marc.girod@ntc.nokia.com writes:
...
>>>  throw Exception(/*...*/);
...
>>>  catch (Exception& e) { /*...*/ }
...
>>>I am a bit worried by the lifetime of the temporary copy of the
>>
>>As well you should be.  This looks like a similar issue to:
>>
>> int a, b;
>> const int& returner () { return a+b; }
>>
>>I believe that the memory used to hold the sum in this case may be recycled
>>for other uses after the function exits.  That would leave you with a
>>dangling reference.
>
>I don't think your example is the same as his.  The 01/28/93 ANSI C++
>draft shows an example in section 15.2 on page 15-2 that is exactly
>like Marc Girod's example (see also page 357 of the ARM).

You're right.  It does.  But that hardly proves anything.  I have found
several errors in the X3J16 WP examples in the past.  Perhaps this is just
another one.

>The text below it says:
>
>"A throw-expression initializes a temporary object of the static type
>of the operand of throw and uses that temporary to initialize the
>appropriately-typed variable named in the handler."

Right again.  But what do *you* think is meant by "the appropriately-typed
variable"?

If you look in section 8.4.3 of the 1/93 X3J16 working paper you will see:

 "A variable declared to be a T& ... must be initialized by an
 object of type T or by an object that can be converted into a T."

Now several knowledgable implementors I know, along with several existing
implementations, interpret the term "object", when used in this context
to mean exactly (and only) "an lvalue expression".

I admit that the sentence I have quoted is badly in need of clarification
by x3j16, but this is the interpretation that at least some implementors
are using... and I happen to think that it is the correct interpretation.

What about the IBM and HP implementations?  Do either of them allow the
second initialization shown below?  If so, they may be failing to properly
enforce the above rule.

 extern int foobar ();
 const int &cir = foobar (); // definitely OK; see 8.4.3
       int & ir = foobar (); // error: non-lvalue initializer

Anyway, the point is that I believe that there is a general and overriding
rule, which applies to the entire language, which requires that the value
of a non-lvalue expression can ONLY be used to initialize a reference-to-const
type of reference.

If you agree that there is indeed such a rule, and that it applies universally
throughout the language (which it really should, or else we can get all sorts
of nasty surprizes) then I think you would have to agree that any throw
which throws a non-lvalue T must not be caught by a catch-clause which is
declared to catch T& values.  (Such catching would involve a kind of an
initialization which is invalid according to 8.4.3.)  Such a thrown value
could however by caught by some catch-clause declared to catch a `const T&'
value.

>Later on page 15-5 in section 15.7 the working paper states:
>
>"The formal argument of a catch clause obeys the same access rules as
>an argument of the function in which the catch clause occurs."

I don't see how that relates to this question at all.  We weren't talking
about accessability (public/private/protected) issues here at all.

>I take these two statements to mean that no dangling reference will
>occur if I throw a temporary and catch it by reference.  For what it's
>worth, the HP C++ implementation of exceptions works in this manner.

For what it's worth, I still think the HP implementation is doing the
Wrong Thing.

>
>"A handler with type T, const T, T&, or const T& is a match for a
>throw expression with an object of type E if
>
>  [1] T and E are the same type, or
>  [2] T is an accessible base class of E at the throw point, or
>  [3] T is a pointer type and E is a pointer type that can be
>      converted to T by a standard pointer conversion at the throw
>      point."
>

I'd say this is just a minor mistake in the ARM (and in the WP).  The
first list of types in the above quotation should NOT include T&.  If
you take that out, it all makes sense again.

>I agree that the rules are murky and are not finalized, but I think
>he's reading things correctly.  I know that the HP C++ exceptions
>implementation goes to great lengths to ensure that such temporaries
>are properly available to the catch clause.

I claim that the HP implementation is erroneously permitting violations
of the rule I quoted above (from section 8.4.3).

--

-- Ronald F. Guilmette ------------------------------------------------------
------ domain address: rfg@netcom.com ---------------------------------------
------ uucp address: ...!uunet!netcom.com!rfg -------------------------------




Author: rfg@netcom.com (Ronald F. Guilmette)
Date: Tue, 22 Jun 1993 04:04:25 GMT
Raw View
In article <GIROD.93Jun16122007@dshp01.trs.ntc.nokia.com> marc.girod@ntc.nokia.com writes:
>Hi netters!
>
>I am about to recommend to people in my team the following way of
>using exceptions:
>
>class Exception { /*...*/ };
>
>void foo() {
>  // ...
>  throw Exception(/*...*/);
>  // ...
>}
>
>void bar() {
>  try {
>    // ...
>    foo();
>    // ...
>  }
>  catch (Exception& e) { /*...*/ }
>}
>
>I am a bit worried by the lifetime of the temporary copy of the

As well you should be.  This looks like a similar issue to:

 int a, b;
 const int& returner () { return a+b; }

I believe that the memory used to hold the sum in this case may be recycled
for other uses after the function exits.  That would leave you with a
dangling reference.

In the case of your example however, I don't think that your catch clase
would be invoked after the throw.  If however you had said:

 catch (const Exception& e) { /*...*/ }

...then maybe it would be.

I think this is the way thing have to work, because your constructor does
not (I believe) yield an lvalue.  Thus, the value it yields cannot be used
to initialize a reference to a non-const thing of any type.

>Exception thrown, used for the initialisation of the parameter of the
>catch block.
>
>The best I found to tranquilise me, was the comment of an example in
>the ARM:
>
>Section 15.2c, p 357
>  "Here the reference _oo_ will refer to a copy of _dummy_. It cannot
>  refer to the automatic storage of function _g()_ from which control
>  is returned by _throw_. The copy of the expression created by
>  _throw_, however, will persist until exit from the exception
>  handler."

I think you may be misreading that, but I would agree that the rules are
rather murkey here.

>Should this suffice, or may somebody provide something stronger?

Like what?  Black coffee? :-)

--

-- Ronald F. Guilmette ------------------------------------------------------
------ domain address: rfg@netcom.com ---------------------------------------
------ uucp address: ...!uunet!netcom.com!rfg -------------------------------




Author: vinoski@apollo.hp.com (Steve Vinoski)
Date: Wed, 23 Jun 1993 02:26:59 GMT
Raw View
In article <rfgC908nE.A42@netcom.com> rfg@netcom.com (Ronald F. Guilmette) writes:
>In article <GIROD.93Jun16122007@dshp01.trs.ntc.nokia.com> marc.girod@ntc.nokia.com writes:
>>Hi netters!
>>
>>I am about to recommend to people in my team the following way of
>>using exceptions:
>>
>>class Exception { /*...*/ };
>>
>>void foo() {
>>  // ...
>>  throw Exception(/*...*/);
>>  // ...
>>}
>>
>>void bar() {
>>  try {
>>    // ...
>>    foo();
>>    // ...
>>  }
>>  catch (Exception& e) { /*...*/ }
>>}
>>
>>I am a bit worried by the lifetime of the temporary copy of the
>
>As well you should be.  This looks like a similar issue to:
>
> int a, b;
> const int& returner () { return a+b; }
>
>I believe that the memory used to hold the sum in this case may be recycled
>for other uses after the function exits.  That would leave you with a
>dangling reference.

I don't think your example is the same as his.  The 01/28/93 ANSI C++
draft shows an example in section 15.2 on page 15-2 that is exactly
like Marc Girod's example (see also page 357 of the ARM).

The text below it says:

"A throw-expression initializes a temporary object of the static type
of the operand of throw and uses that temporary to initialize the
appropriately-typed variable named in the handler."

Later on page 15-5 in section 15.7 the working paper states:

"The formal argument of a catch clause obeys the same access rules as
an argument of the function in which the catch clause occurs."

I take these two statements to mean that no dangling reference will
occur if I throw a temporary and catch it by reference.  For what it's
worth, the HP C++ implementation of exceptions works in this manner.

>In the case of your example however, I don't think that your catch clase
>would be invoked after the throw.  If however you had said:
>
> catch (const Exception& e) { /*...*/ }
>
>...then maybe it would be.
>
>I think this is the way thing have to work, because your constructor does
>not (I believe) yield an lvalue.  Thus, the value it yields cannot be used
>to initialize a reference to a non-const thing of any type.

In section 15.4 on page 15-3 (page 359 of the ARM) it says:

"A handler with type T, const T, T&, or const T& is a match for a
throw expression with an object of type E if

  [1] T and E are the same type, or
  [2] T is an accessible base class of E at the throw point, or
  [3] T is a pointer type and E is a pointer type that can be
      converted to T by a standard pointer conversion at the throw
      point."

Thus, I don't think the presence of const in the catch clause matters
in Marc's example.

>>Exception thrown, used for the initialisation of the parameter of the
>>catch block.
>>
>>The best I found to tranquilise me, was the comment of an example in
>>the ARM:
>>
>>Section 15.2c, p 357
>>  "Here the reference _oo_ will refer to a copy of _dummy_. It cannot
>>  refer to the automatic storage of function _g()_ from which control
>>  is returned by _throw_. The copy of the expression created by
>>  _throw_, however, will persist until exit from the exception
>>  handler."
>
>I think you may be misreading that, but I would agree that the rules are
>rather murkey here.

I agree that the rules are murky and are not finalized, but I think
he's reading things correctly.  I know that the HP C++ exceptions
implementation goes to great lengths to ensure that such temporaries
are properly available to the catch clause.  See the paper from the HP
California Language Labs folks in the 1992 Usenix C++ Conference
proceedings for some good clarification of these issues.

--steve

Steve Vinoski  (508)436-5904   vinoski@apollo.hp.com
Distributed Object Computing Program
Hewlett-Packard, Chelmsford, MA 01824       These are my opinions.




Author: girod@dshp01.trs.ntc.nokia.com (Marc Girod)
Date: Wed, 16 Jun 1993 10:20:07 GMT
Raw View
Hi netters!

I am about to recommend to people in my team the following way of
using exceptions:

class Exception { /*...*/ };

void foo() {
  // ...
  throw Exception(/*...*/);
  // ...
}

void bar() {
  try {
    // ...
    foo();
    // ...
  }
  catch (Exception& e) { /*...*/ }
}

I am a bit worried by the lifetime of the temporary copy of the
Exception thrown, used for the initialisation of the parameter of the
catch block.

The best I found to tranquilise me, was the comment of an example in
the ARM:

Section 15.2c, p 357
  "Here the reference _oo_ will refer to a copy of _dummy_. It cannot
  refer to the automatic storage of function _g()_ from which control
  is returned by _throw_. The copy of the expression created by
  _throw_, however, will persist until exit from the exception
  handler."

Should this suffice, or may somebody provide something stronger?
--
+-----------------------------------------------------------------------------+
| Marc Girod - Nokia Telecommunications       Phone: +358-0-511 7703          |
| TL4E - P.O. Box 12                            Fax: +358-0-511 7432          |
| SF-02611 Espoo 61 - Finland              Internet: marc.girod@ntc.nokia.com |
|    X.400: C=FI, A=Elisa, P=Nokia Telecom, UNIT=TRS, SUR=Girod, GIV=Marc     |
+-----------------------------------------------------------------------------+