29

I'm trying to figure out why the compiler has a problem with this function. It gives me the "Not all code paths return a value" error, however I cannot see a situation where control-flow would pass to the if( a ) expression without a being true (so the if( a ) is superfluous, but the compiler doesn't seem to recognize that).

public static Boolean Foo(Boolean x)
{
    Boolean a = false;
    if( x )
    {
        a = true;
    }
    else
    {
        try
        {
            SomethingThatMightThrow();
            Assert.IsFalse( a );
            return a;
        }
        catch(Exception)
        {
            a = true;
        }
    }

    if( a )
    {
        return x;
    }
}

The immediate fix is to simply remove the if( a ) guard statement completely and just return x immediately - but why does the compiler complain even though it should be able to statically prove all possible code paths will hit a return statement? Crucially, there are no loops, which are often the main reason for it failing to prove return-ness.

I'm using VS2015 Update 3.

jscs
  • 63,694
  • 13
  • 151
  • 195
Dai
  • 141,631
  • 28
  • 261
  • 374
  • What if at the end of this code _printUsage_ is false? Remember that the compiler cannot guess if at runtime your variable is always true when reach the exit point – Steve Jan 21 '17 at 08:38
  • @Steve But `printUsage` will never be false by the end of the `Main` function - if it's ever `false` then the `return` statement inside the `try{}` will have been followed. – Dai Jan 21 '17 at 08:39
  • 4
    If the variables is always set to true, why use a variable in the first place? Remove the condition, and always print the usage if you have not returned from the method yet. – knittl Jan 21 '17 at 08:40
  • And what if for some reason, in a month or a year, somebody modifies your code and `printUsage` becomes false when you check for it? – Martin Verjans Jan 21 '17 at 08:40
  • @MartinVerjans In your proposed case then yes, the compiler would be correct in asserting there is a code-path without a `return` statement. But my question is *why the compiler fails to statically-prove* the correctness of my `Main` method. I'm aware the current code structure is suboptimal, but it is still "correct". – Dai Jan 21 '17 at 08:42
  • 4
    I think you could find this article interesting http://blog.coverity.com/2013/11/06/c-reachability/#.WIMgWIWcGdI – Steve Jan 21 '17 at 08:50
  • 2
    If you know that `a` is always `true` why not remove `if` altogether? – Niyoko Jan 21 '17 at 08:54
  • 21
    **C# Specification 8.1 End points and reachability:** *To determine whether a particular statement or end point is reachable, the compiler performs flow analysis according to the reachability rules defined for each statement. The flow analysis takes into account the values of constant expressions (§7.19) that control the behavior of statements, but **the possible values of non-constant expressions are not considered**. In other words, for purposes of control flow analysis, **a non-constant expression of a given type is considered to have any possible value of that type**.* – user4003407 Jan 21 '17 at 09:02
  • 7
    Just comment of Style. You are making your code more complex in order to put a single point of exit. This is arguably discouraged. See http://softwareengineering.stackexchange.com/questions/104551/should-a-function-use-premature-returns-or-wrap-everything-in-if-clauses and http://softwareengineering.stackexchange.com/questions/118703/where-did-the-notion-of-one-return-only-come-from – borjab Jan 21 '17 at 14:41
  • 1
    May I point out that a **code path** is not the same as a **control flow path**... – user541686 Jan 22 '17 at 08:39
  • 1
    @Mehrdad What is the difference? – Dai Jan 22 '17 at 09:55
  • 1
    @Dai: Code refers to what you wrote, flow of control (a.k.a. control flow) refers to what actually gets executed. Not everything you wrote can actually get executed. So not every path through your code is a path in the control flow. You're thinking that all control flows here return, whereas the compiler is saying not all code paths return... so you're talking about different things. (Though there are situations where the compiler ignores some code paths that it can trivially prove are not control flow paths, but that's the opposite of the question you asked and requires a different example.) – user541686 Jan 22 '17 at 10:19
  • This is also a result of *Rice's theorem* I think: there will always be cases where a certain condition will always hold, but it is an undecidable problem to find these in *all* scenarios. – Willem Van Onsem Jan 26 '17 at 18:19

3 Answers3

53

There is a supported scenario were a is false when you reach the end of your function. That scenario is when you're debugging your code and use your debugger to set a to false.

The C# compiler rules are by design simple. In languages that C# borrows from, the problem of functions potentially not returning anything was a problem that could not be covered by compiler warnings. There were too many false positives, therefore warnings were tweaked to only warn about the obvious cases, introducing false negatives. C#'s rules are a compromise where false positives are acceptable if they're understandable to a human familiar with the rules, and false negatives are unacceptable. You're guaranteed that if your function has a code path which doesn't return a value, the compiler detects it.

One part of those simple rules is that the values of variables aren't considered. Even if a is statically guaranteed to be true, the compiler by design cannot make use of that fact.

@PetSerAl already quoted the relevant wording in the C# language specification:

8.1 End points and reachability

[...]

To determine whether a particular statement or end point is reachable, the compiler performs flow analysis according to the reachability rules defined for each statement. The flow analysis takes into account the values of constant expressions (§7.19) that control the behavior of statements, but the possible values of non-constant expressions are not considered. In other words, for purposes of control flow analysis, a non-constant expression of a given type is considered to have any possible value of that type.

This is part of the last currently published language spec, the one for C# 5.0. The version you're using, C# 6.0 (that's what VS2015 offers), doesn't have a published spec yet, so it's possible that the wording will be slightly different, but as your compiler has shown, effectively the same rule still applies.

Community
  • 1
  • 1
  • This a great answer, but since this question has become so popular, you might consider improving it by referencing the relevant portion of the C# language specification. [PetSerAl already cited it in a comment](http://stackoverflow.com/questions/41777289/why-does-the-compiler-complain-that-not-all-code-paths-return-a-value-when-i-c#comment70745748_41777289). – Cody Gray - on strike Jan 21 '17 at 14:44
  • @CodyGray The OP is using VS2015, which implements C# 6, which as far as I know still doesn't have a language specification. –  Jan 21 '17 at 15:14
  • @hvd VS2015 uses whatever C# version you choose for your project, and there seems to be nothing C#6 specific in the question. – BartoszKP Jan 21 '17 at 21:48
  • 1
    @BartoszKP No, each C# compiler to date only implements one version of the language. They have options to issue errors for new language features, but they do not have options to implement an earlier version of the spec. (The best-known example is probably the `foreach` variable change in C# 5: https://stackoverflow.com/questions/12112881/has-foreachs-use-of-variables-been-changed-in-c-sharp-5) And while there may not be anything C# 6 specific in the question, I suspect this particular bit of the spec would need changing for C# 6's `async` methods, though I can't be 100% certain. –  Jan 21 '17 at 22:00
  • Oh wait - `async` was a C# 5 feature, merely refined a bit in C# 6. And nothing else in C# 6 should require changes to that part of the spec... –  Jan 21 '17 at 23:08
  • You have a really good point about setting a to `false` using a debugger. +1 – Matty Jan 22 '17 at 12:21
  • I doubt that "changing things in a debugger" is a supported scenario for language design. After all basically every guarantee of the C# language specification can be broken with a debugger (maybe not the VS one, but still). If you change the local bool to a constant member you *can* actually write code as shown and you can still jump over the return even in VS. Hijinks ensues. – Voo Jan 22 '17 at 14:51
  • @Voo Unmanaged code can break all guarantees, not just debuggers. But debuggers which work on the managed code and use the supported methods for modifying variables are supposed to be at least somewhat safe. You've given a very specific scenario where you say that isn't the case. I'll check it when I get the chance, and if you're right I'll amend my answer. Thanks for the comment. –  Jan 22 '17 at 15:19
  • @Voo Tested now, in VS2017RC. It doesn't allow modification of constants. –  Jan 22 '17 at 17:19
  • @hvd I did not say modify constants (which would be impossible to do without source code if you think about it - constants are inlined after all) but "jump over the return" which is easily possible (rightclick on the line and select something like "set next address") and has the same consequences as changing the variable would have. – Voo Jan 22 '17 at 17:23
  • @Voo Ah, misunderstood the point you were making. That's possible even without any constants: `static int Main() { return 0; }` allows moving the execution point to the closing `}` before `return 0;` has executed as well. Interesting. –  Jan 22 '17 at 17:28
  • @hvd Yeah that was unnecessarily convoluted. I was quite surprised about support for that feature too the first time I learned about it.. it is surprisingly useful though. – Voo Jan 22 '17 at 17:36
26

It's run-time vs. compile-time

Your example is far too complicated. This won't compile either:

static int Test()
{
    bool f = true;
    if (f)
    {
        return 1;
    }
    else
    {
        //Not all code paths return a value
    }
}

On the other hand, this will:

static int Test()
{
    if (true)
    {
        return 1;
    }
    else
    {
        //No error
    }
}

I'm guessing that whatever validation mechanisms are in place do not have sufficient logic to infer the contents of a run-time variable. Compile-time variables are no problem.

John Wu
  • 50,556
  • 8
  • 44
  • 80
6

I think the compiler does a very simple analysis on the code and thus the return must be explicitly given.

This might look like a wrong decision, but when dealing with complex code, returned value might not be clear. So, the programmer is forced to return it.

Your example can be reduced to a minimum like this:

public static Int32 Main(String[] args)
{
    var printUsage = true;
    if (printUsage)
    {
        return 0;
    }

    // return nothing, so compiler is not happy
}

while still getting the error.

NOTE: if you use Resharper, it will perform the analysis you want and warn you accordingly:

if (printUsage)         // Warning: expression is always true 
Alexei - check Codidact
  • 22,016
  • 16
  • 145
  • 164
  • 1
    Interesting - though according to the article that @steve linked to (by Eric Lippert, no less!) it says the compiler computes reachability using compile-time constants - so according to that article the compiler should not report any problems with your `Main` method either ( http://blog.coverity.com/2013/11/06/c-reachability/#.WIMgWIWcGdI ) – Dai Jan 21 '17 at 08:57
  • 1
    @Dai `printUsage` is not a compile-time constant. –  Jan 21 '17 at 08:59
  • 1
    What's more, even `printUsage` is declared `static readonly` class member, it will still complaint. – Niyoko Jan 21 '17 at 09:00