Question: Can the plain throw
statement in C# ever cause a new exception in itself?
Note that I ask this question out of curiosity, not because I have any practical or real-world situation where it would matter much. Also note that my gut feeling and experience tell me that the answer is "No", but I'm looking to validate that answer somehow (see further down on sources I've tried so far).
Here's some sample code to illustrate my question:
try
{
int x = 0, y = 1 / x;
}
catch (Exception outerException)
{
try
{
throw;
}
catch (Exception innerException)
{
// Q: Does this Assert ever fail??
System.Diagnostics.Debug.Assert(outerException.Equals(innerException));
}
}
I'm wondering if there's any way at all to alter the circumstances such that the Assert
will fail, without touching the inner try/catch
block.
What I've tried or was looking to try to answer this:
throw
.throw
translates to a rethrow
instruction, but I'm lost where to look further to check if that statement can or cannot throw an exception.This is what ILDASM shows for the inner try
bit:
.try
{
IL_000d: nop
IL_000e: rethrow
} // end .try
So, to summarize: can a throw statement (used to rethrow an exception) ever cause an exception itself?
C doesn't support exception handling. To throw an exception in C, you need to use something platform specific such as Win32's structured exception handling -- but to give any help with that, we'll need to know the platform you care about.
A Throw statement with no expression can only be used in a Catch statement, in which case the statement rethrows the exception currently being handled by the Catch statement. The Throw statement resets the call stack for the expression exception. If expression is not provided, the call stack is left unchanged.
The C programming language does not support exception handling nor error handling. It is an additional feature offered by C. In spite of the absence of this feature, there are certain ways to implement error handling in C. Generally, in case of an error, most of the functions either return a null value or -1.
In my honest opinion, theoretically the assert can 'fail' (practically I don't think so).
How?
Note: Below are just my 'opinion' on the basis of some research I earlier did on SSCLI.
Edit:
As I earlier said that the assert can fail theoretically but practically it is highly improbable. Hence it is very hard to develop a POC for this.
In order to provide more 'evidence', following are the snippets from SSCLI code for processing rethow
IL instruction which validate my above points.
Warning: Commercial CLR can differ very widely from SSCLI.
InvalidProgramException :
if (throwable != NULL)
{
...
}
else
{
// This can only be the result of bad IL (or some internal EE failure).
RealCOMPlusThrow(kInvalidProgramException, (UINT)IDS_EE_RETHROW_NOT_ALLOWED);
}
Rude Thread Abort :
if (pThread->IsRudeAbortInitiated())
{
// Nobody should be able to swallow rude thread abort.
throwable = CLRException::GetPreallocatedRudeThreadAbortException();
}
This means that if 'rude thread abort' has been initiated, any exception gets changed to rude thread abort exception.
Now most interesting of all, the OutOfMemoryException
. Since rethrow IL instruction essentially re-throws the same Exception object (i.e. object.ReferenceEquals
returns true) it seems impossible that OutOfMemoryException can occur on re-throw. However, following SSCLI code shows that it is possible:
// Always save the current object in the handle so on rethrow we can reuse it. This is important as it
// contains stack trace info.
//
// Note: we use SafeSetLastThrownObject, which will try to set the throwable and if there are any problems,
// it will set the throwable to something appropiate (like OOM exception) and return the new
// exception. Thus, the user's exception object can be replaced here.
throwable = pThread->SafeSetLastThrownObject(throwable);
SafeSetLastThrownObject
calls SetLastThrownObject and if it fails raises OutOfMemoryException
. Here is the snippet from SetLastThrownObject
(with my comments added)
...
if (m_LastThrownObjectHandle != NULL)
{
// We'll somtimes use a handle for a preallocated exception object. We should never, ever destroy one of
// these handles... they'll be destroyed when the Runtime shuts down.
if (!CLRException::IsPreallocatedExceptionHandle(m_LastThrownObjectHandle))
{
//Destroys the GC handle only but not the throwable object itself
DestroyHandle(m_LastThrownObjectHandle);
}
}
...
//This step can fail if there is no space left for a new handle
m_LastThrownObjectHandle = GetDomain()->CreateHandle(throwable);
Above code snippets shows that the throwable object's GC handle is destroyed (i.e frees up a slot in GC table) and then a new handle is created. Since a slot has just been released, new handle creation will never fail until off-course in a highly rare scenario of a new thread getting scheduled just at the right time and consuming up all the available GC handles.
Apart from this all exceptions (including rethrows) are raised through RaiseException win api. The code that catches this exception to prepare the corresponding managed exception can itself raise OutOfMemoryException
.
Can the plain throw statement in C# ever cause a new exception in itself?
By definition it won't. The very point of throw;
is to preserve the active exception (especially the stack-trace).
Theoretically an implementation could maybe clone the exception but what would be the point?
I suspect the bit you're missing may be the specification for rethrow
, which is within ECMA-335, partition III, section 4.24:
4.24 rethrow – rethrow the current exception
Description:
The rethrow instruction is only permitted within the body of a catch handler (see Partition I). It throws the same exception that was caught by this handler. A rethrow does not change the stack trace in the object.Exceptions:
The original exception is thrown.
(Emphasis mine)
So yes, it looks like your assertion is guaranteed to work according to the spec. (Of course this is assuming an implementation follows the spec...)
The relevant part of the C# specification is section 8.9.5 (C# 4 version):
A throw statement with no expression can be used only in a catch block, in which case that statement re-throws the exception that is currently being handled by that catch block.
Which again, suggests that the original exception and only that exception will be thrown.
(Section 5.3.3.11 which you referred to is just talking about definite assignment, not the behaviour of the throw
statement itself.)
None of this invalidates Amit's points, of course, which are for situations which are somewhat outside the scope of what's specified in either place. (When hosts apply additional rules, it's hard for a language specification to take account of them.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With