Debugger won’t properly evaluate C#s base keyword


Public Service Announcement: You may have noticed that trying to evaluate members using C#’s ‘base’ keyword in the debugger still calls the derived members. (The ‘base’ keyword lets you access base class member implementations from within  a derived class, which is very useful when the members are polymorphic and so just calling the member directly would go through virtual dispatch and call the derived implementation).


Here’s an example:




using System;

class Program
{
static void Main(string[] args)
{
DerivedClass d = new DerivedClass();
d.Test();
}
}

class BaseClass
{
public virtual string Thing()
{
return “base”;
}
}
class DerivedClass : BaseClass
{
public override string Thing()
{
return “derived”;
}

public void Test()
{
// C#’s ‘base’ keyword lets us explicitly call the base class method instead
// of using polymorphism to call the derived method.
string b = base.Thing(); // This will be “base”

// This will print “base, derived”
Console.WriteLine(“{0},{1}”, b, this.Thing());

// However, if we evaluate “base.Thing()” in the debugger’s watch window, it will
// still use polymorphism and incorrectly evaluate to “derived”

}
}


 


So if you evaluate “base.Thing()” in the debugger’s immediate / watch windows, it yields “derived” instead of “base”.


Why?
The problem is actually in the underlying CLR debugging services. To evaluate “base.Thing()”, the debugger needs to do a func-eval. However, ICD does all func-evals with virtual dispatch


On one hand, debugger authors really really don’t want to be doing the virtual dispatch themselves. That would involve crawling all over the type / interface hierarchy trying to figure out which method to call. And most of the time, you want virtual dispatch, so that seemed an intelligent default. For example, this is the correct behavior for “this.Thing()”, or inspecting any other variables. In fact, the only place I’ve see this  break in practice is when explicitly inspecting polymorphic members via the ‘base’ keyword.


FWIW, I think the ideal thing to do would be to have a switch on ICorDebugEval that lets you switch between virtual dispatch and non-virtual dispatch.


Whose bug is this?
Clearly, there’s a bug to the end-user, but there’s an interesting philosophical discussion here about which component is at fault. The CLR’s or Visual Studios? And if in VS, which component? The language specific expression evaluator? The debug-engine?

So you could argue the bug could be:
1) ICorDebug should have had better documentation to clearly call out that func-eval is virtual dispatch (the latest version of the idl file does; but I haven’t checked the other docs, such as earlier idl files)
2) ICorDebug really should have had the functionality to begin with because it should ensure that it provides the necessary features for the overall system to work. (I personally wouldn’t call this a “bug” because although it’s missing functionality, the functionality it does have is correct; perhaps a “design shortcoming”)
3) The debugger (Visual Studio) should not even attempt to evaluate virtuals on the ‘base’ keyword because it can’t be implemented properly. So VS should just fail an attempt to evaluate “base.Thing()”.   For this case, you then have to decide which component within VS would be responsible.


FWIW, I think the ideal thing to do would be to have a switch on ICorDebugEval that lets you switch between virtual dispatch and non-virtual dispatch.

Comments (9)

  1. Laura T. says:

    IMHO, first of all, it’s a QA bug.

  2. Steve Steiner says:

    #1 and #3 are steps toward the same user visible mitigation: inform the user the functionality is missing.

    The lack of expression evaluator reach is almost always considered a bug by a user, so transforming it into a proper error message simply makes it a less severe ‘bug’ from the user’s perspective.

    Given that the syntax and naming of features is different from language to language, the Expression Evaluator’s must provide user visible error messages that reference those features.

    Do you think the lack of support for this in ICorDebug would be considered a defect by Andreas Zeller’s definitions?  If not is this not a bug?  Are Zeller’s definitions lacking for some practical cases?

  3. Steve – where are Zeller’s definitions?

  4. SteveJS says:

    Sorry about that.  They are in his book "Why program’s fail". Fortunately Chapter 1 is online.  

    The distinction is defect, infection, failure:

    http://books.elsevier.com/bookscat/samples/1558608664/Sample_Chapters/02~chapter_one.pdf

    I’d claim this is a failure from the user’s point of view.  But is there an infection or defect?

  5. I think you could make an argument to classify it a lot of different ways. I think Zeller’s definitions are hard to apply here.

    I wouldn’t say Incomplete features are bugs.

    The closest he gets is alluding to it here: "In a modular program, a failure may happen because of incompatible interfaces of two modules."

  6. I got a question about MDbg’s func-eval syntax, which brings up a nice point about function resolution:…

  7. Rick Byers says:

    Method calls using the C# ‘ base ’ keyword get compiled to an IL ‘ call ’ instruction, rather than the