Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can the (plain) throw statement in C# cause exceptions?

Tags:

c#

exception

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:

  • Read the throw (C# Reference) page on MSDN - no definitive answer;
  • Checked part 5.3.3.11 of the C# Language Specification - which is probably the wrong place to look for this kind of info;
  • Glossed through the exceptions that I could try to trigger on the throw statement. The OutOfMemoryException comes to mind, but is kind of hard to trigger at the time of the throw.
  • Opened up ILDASM to check the generated code. I can see that 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?

like image 208
Jeroen Avatar asked Jun 25 '12 19:06

Jeroen


People also ask

Does C have Throw?

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.

Where all throw statement can be used?

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.

Are there exceptions in C?

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.


3 Answers

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.

  1. An InvalidProgramException can occur. This admittedly is highly highly improbable but nevertheless theoretically possible (for instance some internal CLR error may result in the throwable object becoming unavailable!!!!).
  2. If CLR does not find enough memory to process the 're-throw' action it will throw an OutOfMemoryException instead (CLR's internal re-throw logic requires to allocate some memory if it is not dealing with 'pre-allocated' exceptions like OutOfMemoryException).
  3. If the CLR is running under some other host (for e.g. SQL server or even your own) and the host decides to terminate the Exception re-throw thread (on the basis of some internal logic) ThreadAbortException (known as rude thread abort in this case) will be raised. Though, I am not sure if the Assert will even execute in this case.
  4. Custom host may have applied escalation policy to CLR (ICLRPolicyManager::SetActionOnFailure). In that case if you are dealing with an OutOfMemoryException, escalation policy may cause ThreadAbortException to occur (again rude thread abort. Not sure what happens if policy dictates a normal thread abort).
  5. Though @Alois Kraus clarifies that 'normal' thread abort exceptions are not possible, from SSCLI research I am still doubtful that (normal) ThreadAbortException can occur.

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.

  1. 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);
    }
    
  2. 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.

  3. 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.

like image 93
Amit Mittal Avatar answered Sep 17 '22 15:09

Amit Mittal


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?

like image 35
Henk Holterman Avatar answered Sep 18 '22 15:09

Henk Holterman


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.)

like image 23
Jon Skeet Avatar answered Sep 19 '22 15:09

Jon Skeet