Today I learned a valuable lesson in debugging. Today I learned what it can be like to chase your own tail. I had accomplished the rare feat of introducing a bug through the very act of debugging.
A common feature for software is to behave differently depending on the rights of the user. Super users should see an entire list, while normal users should have less power, only seeing list items pertaining to them. Simple to code, tricky to test. All it takes in an “if” statement, like this:
if(superuser) {
// Run special code
} else {
// Run not-so special code
}
And then you write the code. Of course, with my default rights, I can only check one half of this conditional (I am a superuser). Rather than modifying the code and recompiling, or modifying my rights, I thought it would be great to use a debugging tool to bypass this check entirely, and hop right into the non-privileged. Big mistake. First time I tried this, I got a null reference error while calling a function. Not out of the ordinary, the function called a web service, maybe I wasn’t disposing of something properly. 20 fruitless minutes later, I’d rewritten that function several ways trying every way of calling, disposing, and scoping I knew. When that didn’t work, I looked higher up.
The erroring function was called in one other place, in a non-static context. No luck when I tried changing that. Using an intermediary variable for the result? Still it crashed. After a lot of simplification, I managed to make the null error happen one line further up in the code:
MyClass c = new MyClass();
Now this didn’t make sense. MyClass had a default, empty constructor, and no class-level fields. Yet running this line gave me a Null Reference Exception. Getting tired of stepping the debugger forward each time, I decided to just comment out the check, recompile, and continue debugging. Then, the error stopped. After a lot of hunting through Disassemblies, I found the rub: using the debugger to bypass a line of code changed (or rather, didn’t change) some values in memory that the instructions below expected. Eventually, when it tried to issue a “call” to a memory address, there would be random garbage data there. A seg fault may’ve been happening under the scenes, which .NET happened to present as a… Null Reference Exception.
My attempt to observe program state altered the very state I was trying to observe. Schrödinger’s bug.

Leave a Reply