Func-eval is evil


Function evaluation (aka “Func-eval”, “property evaluation”, “prop eval”) is the ability for a debugger to have the debuggee call a random function when stopped at a breakpoint. For eg, this lets you call functions from VS’s immediate window.


 


Func-eval is extremely useful.  VS heavily uses it in inspection to evaluate property getters and ToString(). I think VS visualizers (in v2.0) may do even more funcevals.


But Funceval is also extremely dangerous. And that puts us in a real dilemma.


 


In order to funceval, the debugger needs to hijack a thread and then resume the debuggee under the covers so that it can run the function. The debugger gets notified when the function completes and then restores that thread to its pre-funceval state.


 


Func-eval is so evil that most every (managed) debugger dev with a blog has blogged about how evil it is. (Andy, Steve, Gregg, and now me).


Why is funceval evil?


1)      It poses some complicated UI problems for a debugger. For example:


a.       Should the debugger suspend all the other threads or not? If it does, then if the func-eval thread blocks on another thread, it hangs. If it does not, then those other threads can run during the func-eval and cause tremendous side effects.  Visual Studio happens to suspend all other threads.


b.      How should the debugger handle things like hitting a breakpoint in a function called indirectly by the funceval?  Should it stop at it or ignore it? If it stops, the debugger needs to the UI to handle “nested break states”. On the other hand, users expect their breakpoints to be hit, especially if the function has side-effects. VS 2003 skipped the breakpoints, and VS 2005 handles nested break states for certain funcevals (such as ones explicitly initiated from the immediate window).


2)      The function may change state underneath you. For example:


a.       what if that read-only property has some side-effect (e.g. goes and initializes a cache)? Steve Steiner (VS debugger dev) has a great example here. 


b.      An evil function may have bigger changes such as taking (but not releasing) locks or even creating new threads.


c.       What if the function causes a Garbage collection? That could move around all the objects in the process (which is one reason we added object identity)


3)      The function may hang, and that may cause the debugger to hang waiting for the evaluation to complete. The CLR has mechanisms to try and abort a func-eval, but that’s also very risky business and not always possible. Even if it does abort, the user still is blocked for a little bit. And if the abort can’t fully restore state, the debuggee may be permanently corrupted. Reasons it may hang include:


a.       what if the function goes into an infinite loop?  (This can usually be aborted if in managed code)


b.      what if the function p-invokes out into native code and then blocks there? (This can’t be aborted until the thread comes back into managed code).


c.       what if the function tries to make a cross thread call? This is very common for imported COM objects that need to marshal requests back to a single owning thread.  VS suspends all other threads, including message pump threads, to prevent them from causing side effects during the funceval.


d.      what if the function blocks on a lock owned by another (suspended) thread? This can also happen if the function tries to do a garbage collection (which requires bringing all threads in the process to a cooperative suspension) and another thread is not cooperating (likely because it was suspended at a unsafe place for GCs)?


4)      Funceval will introduce code paths that could not normally happen, and that may cause unexpected behavior. What if a function is called at a place where it wasn’t designed to be called?


a.       For example, a read-only property may assert that certain state is valid. If you stop at a breakpoint in the ctor before that state is set and expand the this pointer, VS will still call that property. This can do things like cause asserts in the getter to fire.


b.      What if you go up the stack and then inspect things that cause more func-evals? This could lead to some ugly reentrancy issues.


 


Any debugger that implicitly does funceval must provide a way to turn it off.  Gregg explains how to disable it in VS:


Property evaluation can be turned of by disabling the option ‘Tools->Options->Debugging->General->Allow property evaluation in variables windows’.


He also elaborates that a debugger can be smart about doing the right funcevals if it takes into account things like public/private, just-my-code status, and information from the project system.

Comments (12)

  1. Windbg added func-eval in 6.4. That means something.

    Just don’t turn it on by default.

  2. Mike Stall says:

    Junfeng – true, though it’s interesting to drill into that further.

    Windbg’s funceval is for native code only, not managed. Visual Studio has also had func-eval for native-code since at least V6.0 (don’t know exactly when it first appeared).

    Both those cases of func-eval must be explicitly invoked by the user (contrast to VS’s auto evaluation of managed property-getters and ToString() calls). That mitigates issues 2,3,4.

    Windbg also mitigates issue #1 by giving the end-user full control over the funceval.

  3. Mark Mullin says:

    Yeah, the whole things been a pet peeve of mine too. I have some classes that have exceptionally complex properties and I want them to stay properties – that said, if I’ve got some problem in spinning up the class, I can run smack into this

    I know about the ui setting to suppress this, what I’d love, at least in managed C#, is an attribute I can attach to individual properties to suppress evaluation = far better than the all or nothing option I have at present.

  4. Mike Stall says:

    Mark – I think VS actually has a ton of attributes that you can place on properties and classes to give VS better hints as to how to display them and what to avoid func-evalling on. Andy P. mentioned one that may do exactly what you want:

    DebuggerBrowsable.Never

    I’ll see if I can dig up more about this.

  5. Here’s an sampling of various goofy bugs we’ve had to deal with in the CLR Debugging services over the…

  6. Interop-debugging splits all debug events into "In-band (IB) " and "Out-of-band (OOB)". Inband events…

  7. I created a blog category for Func-eval (aka Property Evaluation), and I updated some of my old…

  8. Func-eval is evil . Func-eval abort is even worse. For those coming in late, Func-eval is when the debugger