tags:

views:

234

answers:

8

Why can’t compiler detect at compile-time that obj references object of type B and thus reports an error when we try to cast it to type A?

public class A { }
public class B { }

static void Main(string[] args)
{
   B b = new B();
   object obj = (object)b;
   A a = (A)obj; // exception

thanx

+7  A: 

You want the compiler to follow the control flow, and determine ahead of time that the cast will cause an exception? Why bother? With a real program, the control flow will be too complicated to figure this out.

John Saunders
+! beat me to it.
Kevin
+1  A: 

Because you'd sit there for days while compilers tried every possible path through your code.

Josh Einstein
+1  A: 

As others have mentioned, the general problem is that the compiler would have to trace back through all possible execution paths to see where that variable may have come from - and then determine if the cast is valid.

Imagine if the object was passed in to the function, which then downcast it. The compiler would have to know the run-time type of the object passed in. The calling code may not even exist at compile time, if this is a library.

Draemon
+7  A: 

A compiler certainly could implement checks that would work in trivial cases like this. But doing so would be unlikely to help "real" code very much, since programmers rarely write such obviously wrong code.

To handle more complicated cases, a compiler would have to perform much more complicated analysis. This is harder for the compiler writer to do, and is also slower for your machine to run, and it still wouldn't be able to catch every possible bad cast. And again, because most code doesn't have easily-identifiable errors of this sort, it's not clear that the payoff would be worth the cost of writing the analysis.

Two drawbacks of more complicated static analysis are error messages and false positives. First, having a tool explain a problem in code is often an order of magnitude harder than having the tool merely check for the problem. Second, as checked-for problems turn from "bad thing X will definitely happen" to "bad thing Y might happen", it becomes much more likely that the tool will flag things that aren't ever a problem in practice.

There's an interesting essay written by a company, selling static analysis tools, that was spun off from academic research. One thing they discovered is that they often made fewer sales with more complicated analyses! A Few Billion Lines of Code Later: Using Static Analysis to Find Bugs in the Real World

Ben Karel
thank you all for your help
AspOnMyNet
+12  A: 

Because of the Halting problem. This essentially means that you cannot decide which execution path will the program follow (and there is a mathematical proof for that). For example the following code may or may not be correct:

object o = SomeTest() ? (new A()) : (new B());
A a = (A)o;

If the SomeTest method always returns true then it is correct. Unfortunatelly, it is not possible to decide that. However, there is a lot of research going on in this field. Even though it cannot be always checked, there are tools that can sometimes verify that something will always succeed or give you an example of execution path for which the assumption fails.

A good example of this technique are Code Contracts, which will be a part of Visual Studio 2010. I believe you could use them to give prove that your down-casting will be correct. However, there is no explicit support for this - although, it would be useful!

Tomas Petricek
Reference to the halting problem is irrelevant.
Moron
Reference to the halting problem is 100% relevant.
Eric Lippert
Halting problem only talks about there being an algorithm to determine if a given program _halts_ on a given input. I would say it is irrelevant. It might sound _similar_, but it is still irrelevant.
Moron
For instance, consider the very code given: You can tell that it will halt with reasonable confidence. But you cannot tell whether it is valid to cast o to A. The difference here being that you have to consider it over _all_ possible inputs to determine 'type castability'. The halting problem is only about a _specific_ input.
Moron
But this is trivially convertible to the Halting Problem by creating a program that will halt if the downcast is valid. Likewise, any example of the Halting Problem can be trivially convertible to this by embedding the condition in types and only allowing the cast in the halt condition. So, while you can determine specific cases of the Halting Problem, you can determine certain cases of this problem. It cannot, however, be determined in the general case.
kyoryu
Read the referenced wikipedia article. Here is a quote _One such consequence of the halting problem's undecidability is that there cannot be a general algorithm that decides whether a given statement about natural numbers is true or not_. Now, say that `SomeTest` tests some statement about natural numbers. As a result, you cannot know what it will return (in a fully general case). In a specific case, you very often can do that (e.g. Code Contracts), but not in a fully general case.
Tomas Petricek
@kyoru: You are forgetting something. Some programs might not even has any casts. You are not solving the halting problem by solving the cast problem. The otherway round too, by putting in the cast condition, you have reduced the set of programs. So, what you say is irrelevant.
Moron
@Tomas: So? What does it imply about the cast problem? The reason we cannot determine if a cast is valid, does not following from Halting problem. It might be as hard (or even harder) than the halting problem, but is not a consquence of it.
Moron
@Moron: It shows that the "cast problem" is hard (or even harder) than the halting problem (to solve "cast problem" you must be able to solve "halting problem"). And since "halting problem" is unsolvable, the "cast problem" is unsolvable too.
Tomas Petricek
@Tomas:So you are saying that if the compiler could tell at compile time if a cast is valid, then we could have it tell if SomeTest returns true or false for all input and hence would make the Halting problem decidable. btw, Why do we even have to bring up the halting problem? Just: _if (boolean_input_by_user) {o = new A} else {o = new B}_ is enough. Halting problem for LBA is decideable, which is what it is for the most programming languages. So in theory, we should be able to tell if _some_ input causes a particular execution path to be taken, and that is enough for the cast problem.
Moron
So basically what I am trying to say is that, even if the halting problem _were decidable_, the cast problem remains undecidable and so Halting problem is irrelevant in that sense. Besides, I would say C# is an LBA (and so halting problem _is_ decidable here), but I am no C# expert and will leave it to Eric.
Moron
And to look at it differently: Consider this code: if (SomeTest()) { object_of_typeA.MethodA() } else { object_of_typeA.MethodB()}. object_of_typeA does not have MethodB defined at all. Now, would the halting problem prevent the compiler from flagging an error?
Moron
So let's sum up this fascinating discussion: determining the run-time type of the contents of a variable based on information available at compile time is *at least as hard* as solving the halting problem for some model of computation -- whether linear bounded automaton, or full on Turing Machine, as we see fit. On a TM, the halting problem is undecidable. On an LBA, we can come up with situations that require arbitrarily much time and space to determine. Either way, we have a problem that is not for practical purposes solvable at compile time. Therefore: casts are necessary.
Eric Lippert
@Eric: Exactly, casts are necessary and that is true even if Halting problem were decidable. My _nitpick_ was about the claim that casts are necessary _because_ of halting problem. It is not as if the compiler spec writer/implementor would have gone: _lets not have this feature because of the halting problem_. The same applies to missing methods. My claim is the reason is that it is just too impractical, halting problem or not. IMO, most compilers can make the reasonable assumption that _all_ code paths will be taken on different inputs and thus it becomes too impractical to detect bad casts.
Moron
Just to clarify - my statement that "casts are necessary because of halting problem" should be interpreted as "halting problem is a sufficient reason for casts being necessary". Halting problem is just the simplest argument that you can use when you want to say that proving some property about programs is impossible (in general case). Making an assumption that _all_ code paths will be taken is oversimplification (and it doesn't work with loops - are you going to assume that a loop keeps running forever?). We can do much better than that. See: http://research.microsoft.com/en-us/people/bycook/
Tomas Petricek
@Tomas. No, just assume that you can have the loop execute it however many times you want it to, to prove something about the program. Of course this assumption has limitations and we can do better: making this assumption about code paths does not deny that fact. I would still say that halting problem is _not_ the reason we do not check for casts, practically speaking. Undecidability of the halting does imply undecidabilty of the cast problem (and a lot of others which compilers actually do check for, like missing methods),but, is not enough to state as a reason for not having that feature.
Moron
Anyway, this discussion has dragged on too long, but I hope you got the point I was trying to make. (And I understand what you are saying).
Moron
+1  A: 

In a basic example like yours, one might think it would be easy for a compiler to intelligently look for all references to a particular object and then see if it's being illegally cast. But consider this counterexample:

public class A { } 
public class B { } 

static void Main(string[] args) 
{ 
   B b = new B(); 
   object obj = (object)b;
   // re-using the obj reference
   obj = new A();
   A a = (A)obj; // cast is now valid

There are so many possible permutations of ways you could re-use and cast a particular base reference that a compiler writer would need to foresee. It gets even more complicated when the obj reference is passed in a parameter to a method. Compile-time checking becomes non-deterministic, making compilations times potentially much longer and still not guaranteeing it would be able to catch all invalid casts.

Tim Trout
+2  A: 

Even static analysis tools wouldn't be able to solve this problem. What if your code uses reflection?

void Test(string typeName)
{
    Type t = Type.GetType(typeName);
    object obj = Activator.CreateInstance(t);
    A a = (A)obj;
    // etc.
}

Will this throw an exception? There is absolutely no possible way to know the answer without actually running it. No amount of code-path analysis will unravel a bug that depends on the value of some particular parameter. And if you have to run the code to detect the bug, then that makes it a runtime error, not compile-time.

This is exactly the reason why you need to test your code. Compilers can't ensure that your code is correct, only that it's syntactically valid and follows whatever rules are in the grammar.

And although this might seem like a contrived example, reflection is used pretty much everywhere these days, from your O/R mapper to your DI framework. It's actually quite common in a modern application not to know the type of some instance, or at least not the specific concrete type, until runtime.

Aaronaught
+7  A: 

Let me turn the question around: if the compiler could prove that, then why would we need casts at all? The purpose of a cast is tell the compiler "I know more about this code than you do, and I promise you that this cast is valid. I am so sure of that fact that I am willing to let you generate code that throws an exception if I'm wrong." The compiler can't prove that the cast is valid precisely because the cast is for scenarios where the compiler can't prove that it is valid.

Eric Lippert