views:

519

answers:

5

It seems to me that a lot of my debugging time is spent chasing down null-reference exceptions in complex statements. For instance:

For Each game As IHomeGame in _GamesToOpen.GetIterator()

Why, when I get a NullReferenceException, can I get the line number in the stack trace, but not the name of the object that equals null. In other words, why:

Object reference not set to an instance of an object.

instead of

_GamesToOpen is not set to an instance of an object.

or

Anonymous object returned by _GamesToOpen.GetIterator() is null.

or

game was set to null.

Is this strictly a design choice, meant to protect the anonymity of the code or is there a compelling reason in compiler design not to include this information in the debug-time exception?

+7  A: 

Exceptions are runtime things, variables are compile time things.

In fact, the variable in your example is an expression. Expressions are not always simple variables. At runtime, the expression will be evaluated and the method will be called on the resulting object. If the value of that expression is null, the runtime will throw a NullReferenceException. Assume the following:

Dim a as New MyObject
Dim b as String = MyObject.GetNullValue().ToString()

What error message should the runtime return if the GetNullValue() method returns null?

Mehrdad Afshari
Line numbers are also a run-time thing. Debug-time compilation contains all sorts of compile-time things (class and method names, line numbers, etc.) Why not variable names?
Jekke
Classes and methods and parameter names actually do exist at the IL level. But variables are pretty much gone in the generated IL. Basically, there is no specific way to relate an exception to a specific variable: Assume "if (a < b) throw ...;" Is the problem related to "a" or "b"?
Mehrdad Afshari
Accepted because of the comment above.
Jekke
@Mehrdad There is a difference between the VM throwing an Exception and throwing one in the code. If you throw in the code you *can* (should) provide the necessary info. When the VM throws, it cannot do that, like in the OP's case.
eljenso
The variable names are in the PDB symbols, so they are available (in debug mode). This is how Reflector shows them. At the very least, the IDE could kick in to show which statement returned the null.
Paul Stovell
+1  A: 

A simple way to catch this for debugging to to place an Assert statement prior to using an object, check for null and output a meaningful message.

It's not allways possible to have an Assert. For example, you can't add an assert inside a Linq expression.
Franci Penov
+1  A: 

In release builds, the variable names are stripped out from the symbols and the code might even be optimized to not have a specific memory location for a variable, but just keep the reference in one of the registers (depending on the scope of the variable usage). Thus, it might not be possible to deduct the name of the variable from the reference location.

In debug build, there's more information available about the variables. However, the exception object needs to work the same way irregardless of the build flavor. Hence, it acts upon the minimum information it can access in any flavor.

Franci Penov
That's fine. You probably don't want it in release builds anyway. I did specify in the question that I was wondering why you can't see this in debug builds.
Jekke
I though I mentioned this. The exception objects are not a debugging feature and should work the same regardless of the build flavor. You wouldn't expect String class behavior to change radically under Debug flavor, would you?
Franci Penov
+1  A: 

A couple of things...

1) when you make your own exceptions keep this in mind (if you are annoyed it it for this someone else will be annoyed at you if you do it for something else). Given that the exception path should not at all be the typical path the time spent making the exception have useful information is well worth it.

2) as a general programming practive adopt this style and you will have far less issues (yes your code will be longer in terms of lines, but you will save a lot of time):

a) never do a.b().c(); do x = a.b(); x.c(); (on separate lines) that way you can see of a was null or if the return of a.b() is null.

b) never pass the return of a method call as a parameter - always pass variables. a(foo()); should be x = foo(); a(x); This one is more for debugging and being able to see the value.

I don't know why environments like .net and Java do not provide a version of the runtime that does have more information on these sorts of exceptions, such as what the index was on an array out of bounds, the name of the variable when it is null, etc...

TofuBeer
+1  A: 

For languages like Java that are compiled to bytecode that gets interpreted by a VM, suppose you have a class X with a field x, and its value is null for a certain reference. If you write

x.foo()

the bytecode might look like this:

push Xref           >> top of stack is ref to instance of X with X.x = null
getField x          >> pops Xref, pushes 'null' on the stack
invokeMethod foo    >> pops 'null' -> runtime exception

The point is that the operation that needs a non-null reference on the stack to operate on it, like invokeMethod in the example, cannot and does not know where that null reference came from.

eljenso