Unreachable code, but reachable with an exception
Asked Answered
H

9

109

This code is part of an application that reads from and writes to an ODBC connected database. It creates a record in the database and then checks if a record has been successfully created, then returning true.

My understanding of control flow is as follows:

command.ExecuteNonQuery() is documented to throw an Invalid​Operation​Exception when "a method call is invalid for the object's current state". Therefore, if that would happen, execution of the try block would stop, the finally block would be executed, then would execute the return false; at the bottom.

However, my IDE claims that the return false; is unreachable code. And it seems to be true, I can remove it and it compiles without any complaints. However, for me it looks as if there would be no return value for the code path where the mentioned exception is thrown.

private static bool createRecord(String table,
                                 IDictionary<String,String> data,
                                 System.Data.IDbConnection conn,
                                 OdbcTransaction trans) {

    [... some other code ...]

    int returnValue = 0;
    try {
        command.CommandText = sb.ToString();
        returnValue = command.ExecuteNonQuery();

        return returnValue == 1;
    } finally {
        command.Dispose();
    }

    return false;
}

What is my error of understanding here?

Haystack answered 12/3, 2019 at 8:17 Comment(3)
learn.microsoft.com/en-us/dotnet/csharp/language-reference/…Bohman
Side note: do not call Dispose explicitly, but put using: using (var command = ...) {command.CommandText = sb.ToString(); return command.ExecuteNonQuery(); }Terrify
A finally block means something else than you think.Voidable
W
146

Compiler Warning (level 2) CS0162

Unreachable code detected

The compiler detected code that will never be executed.

Which is just saying, the Compiler understands enough through Static Analysis that it can't be reached and completely omits it from the compiled IL (hence your warning).

Note : You can prove this fact to your self by trying to Step on to the Unreachable Code with the debugger, or using an IL Explorer.

The finally may run on an Exception, (though that aside) it doesn't change the fact (in this case) it will still be an Uncaught Exception. Ergo, the last return will never get hit regardless.

  • If you want the code to continue onto the last return, your only option is to Catch the Exception;

  • If you don't, just leave it the way it is and remove the return.

Example

try 
{
    command.CommandText = sb.ToString();
    returnValue = command.ExecuteNonQuery();

    return returnValue == 1;
}
catch(<some exception>)
{
   // do something
}
finally 
{
    command.Dispose();
}

return false;

To quote the documentation

try-finally (C# Reference)

By using a finally block, you can clean up any resources that are allocated in a try block, and you can run code even if an exception occurs in the try block. Typically, the statements of a finally block run when control leaves a try statement. The transfer of control can occur as a result of normal execution, of execution of a break, continue, goto, or return statement, or of propagation of an exception out of the try statement.

Within a handled exception, the associated finally block is guaranteed to be run. However, if the exception is unhandled, execution of the finally block is dependent on how the exception unwind operation is triggered. That, in turn, is dependent on how your computer is set up.

Usually, when an unhandled exception ends an application, whether or not the finally block is run is not important. However, if you have statements in a finally block that must be run even in that situation, one solution is to add a catch block to the try-finally statement. Alternatively, you can catch the exception that might be thrown in the try block of a try-finally statement higher up the call stack. That is, you can catch the exception in the method that calls the method that contains the try-finally statement, or in the method that calls that method, or in any method in the call stack. If the exception is not caught, execution of the finally block depends on whether the operating system chooses to trigger an exception unwind operation.

Lastly

When using anything that supports the IDisposable interface (which is designed to release unmanaged resources), you can wrap it in a using statement. The compiler will generate a try {} finally {} and internally call Dispose() on the object.

Waechter answered 12/3, 2019 at 8:20 Comment(3)
What do you mean by IL in the first sentences?Trihedron
@Trihedron IL is a product of compilation of code written in high-level .NET languages. Once you compile your code written in one of these languages, you will get a binary that is made out of IL. Note that Intermediate Language is sometimes also called Common Intermediate Language (CIL) or Microsoft Intermediate Language (MSIL).,Waechter
In short terms, because he didn't catch the possibilities are: Either the try runs until it hits return and thus ignores the return below finally OR an exception is thrown and that return is never reached because the function will exit due to an exception being thrown.Toomey
W
86

the finally block would be executed, then would execute the return false; at the bottom.

Wrong. finally doesn't swallow the exception. It honors it and the exception will be thrown as normal. It will only execute the code in the finally before the block ends (with or without an exception).

If you want the exception to be swallowed, you should use a catch block with no throw in it.

Whereupon answered 12/3, 2019 at 8:19 Comment(8)
will the above sinppet compile in case exception, what will be returned?Doubleness
It does compile, but it will never hit return false since it will throw an exception instead @EhsanSajjadWhereupon
seems strange, compiles because either it will return a value for bool in case of no exception and in case of exception nothing will be, so legit for to satisfy method's return type ?Doubleness
The compiler will just ignore the line, that is what the warning is for. So why is that strange? @EhsanSajjadWhereupon
means it's just warning, but will build successfully right ?Doubleness
Yes, but that line does nothing. It is left out. @EhsanSajjadWhereupon
makes sense now, there is only path (if try goes fine), then it will return value otherwise there is no path out of the method.Doubleness
Fun fact: It is actually not guaranteed that a finally block will run if the exception is not caught in the program. The spec doesn't guarantee this and early CLRs did NOT execute the finally block. I think starting with 4.0 (might have been earlier) that behavior changed, but other runtimes might still behave differently. Makes for rather surprising behavior.Bashee
J
27

The warning is because you didn't use catch and your method is basically written like this:

bool SomeMethod()
{
    return true;
    return false; // CS0162 Unreachable code detected
}

Since you use finally solely to dispose, the preferred solution is to utilize using pattern:

using(var command = new WhateverCommand())
{
     ...
}

That's enough, to ensure what Dispose will be called. It's guaranteed to be called either after successful execution of code block or upon (before) some catch down in call stack (parent calls are down, right?).

If it wouldn't be about disposing, then

try { ...; return true; } // only one return
finally { ... }

is enough, since you will never have to return false at the end of method (there is no need for that line). Your method is either return result of command execution (true or false) or will throw an exception otherwise.


Consider also to throw own exceptions by wrapping expected exceptions (check out InvalidOperationException constructor):

try { ... }
catch(SomeExpectedException e)
{
    throw new SomeBetterExceptionWithExplanaition("...", e);
}

This is typically used to say something more meaningful (useful) to the caller than nested call exception would be telling.


Most of times you don't really care about unhandled exceptions. Sometimes you need to ensure that finally is called even if exception is unhandled. In this case you simply catch it yourself and re-throw (see this answer):

try { ... }
catch { ...; throw; } // re-throw
finally { ... }
Jestinejesting answered 12/3, 2019 at 9:29 Comment(0)
T
14

It seems, you are looking for something like this:

private static bool createRecord(string table,
                                 IDictionary<String,String> data,
                                 System.Data.IDbConnection conn,
                                 OdbcTransaction trans) {
  [... some other code ...]

  // Using: do not call Dispose() explicitly, but wrap IDisposable into using
  using (var command = ...) {
    try {
      // Normal flow:
      command.CommandText = sb.ToString();

      // True if and only if exactly one record affected
      return command.ExecuteNonQuery() == 1;
    }
    catch (DbException) {
      // Exceptional flow (all database exceptions)
      return false;
    }
  }
}

Please, note, that finally doesn't swallow any exception

finally {
  // This code will be executed; the exception will be efficently re-thrown
}

// And this code will never be reached
Terrify answered 12/3, 2019 at 8:30 Comment(0)
S
8

You don't have a catch block, so the exception is still thrown, which blocks the return.

the finally block would be executed, then would execute the return false; at the bottom.

This is wrong, because the finally block would be executed, and then there would be an uncaught exception.

finally blocks are used for cleanup, and they do not catch the exception. The exception is thrown before the return, therefore, the return will never be reached, because an exception is thrown before.

Your IDE is correct that it will never be reached, because the exception will be thrown. Only catch blocks are able to catch exceptions.

Reading from the documentation,

Usually, when an unhandled exception ends an application, whether or not the finally block is run is not important. However, if you have statements in a finally block that must be run even in that situation, one solution is to add a catch block to the try-finally statement. Alternatively, you can catch the exception that might be thrown in the try block of a try-finally statement higher up the call stack. That is, you can catch the exception in the method that calls the method that contains the try-finally statement, or in the method that calls that method, or in any method in the call stack. If the exception is not caught, execution of the finally block depends on whether the operating system chooses to trigger an exception unwind operation.

This clearly shows that the finally is not intended to catch the exception, and you would have been correct if there had been an empty catch statement before the finally statement.

Shakta answered 12/3, 2019 at 23:45 Comment(0)
D
7

When the exception is thrown, the stack will unwind (execution will move out of the function) without returning a value, and any catch block in the stack frames above the function will catch the exception instead.

Hence, return false will never execute.

Try manually throwing an exception to understand the control flow:

try {
    command.CommandText = sb.ToString();
    returnValue = command.ExecuteNonQuery();

    // Try this.
    throw new Exception("See where this goes.");

    return returnValue == 1;
} finally {
    command.Dispose();
}
Demetri answered 12/3, 2019 at 8:20 Comment(0)
S
5

On your code:

private static bool createRecord(String table, IDictionary<String,String> data, System.Data.IDbConnection conn, OdbcTransaction trans) {

    [... some other code ...]

    int returnValue = 0;
    try {
        command.CommandText = sb.ToString();
        returnValue = command.ExecuteNonQuery();

        return returnValue == 1; // You return here in case no exception is thrown
    } finally {
        command.Dispose(); //You don't have a catch so the exception is passed on if thrown
    }

    return false; // This is never executed because there was either one of the above two exit points of the method reached.
}

the finally block would be executed, then would execute the return false; at the bottom

This is the flaw in your logic because the finally block won't catch the exception and it will never reach the last return statement.

Spermatozoon answered 12/3, 2019 at 8:25 Comment(0)
R
4

The last statement return false is unreachable, because the try block is missing a catch part that would handle the exception, so the exception is rethrown after the finally block and the execution never reaches the last statement.

Ribera answered 12/3, 2019 at 8:20 Comment(0)
P
2

You have two return paths in your code, the second of which is unreachable because of the first. The last statement in your try block return returnValue == 1; provides your normal return, so you can never reach the return false; at the end of the method block.

FWIW, order of exection related to the finally block is: the expression supplying the return value in the try block will be evaluated first, then the finally block will be executed, and then the calculated expression value will be returned (inside the try block).

Regarding flow on exception... without a catch, the finally will be executed upon exception before the exception is then rethrown out of the method; there is no "return" path.

Proscribe answered 13/3, 2019 at 21:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.