Some thoughts on Edit and Continue and Design-Time Expression Evaluation (Matt Gertz)

Some thoughts on Edit and Continue and Design-Time Expression Evaluation (Matt Gertz)

  • Comments 4

The story of Edit and Continue (which I’ll refer to as EnC) is a very long one.  Having been a cornerstone of Visual Basic in the past, it had always been planned for us to ship an Edit and Continue experience with Visual Studio 2002.  But we didn’t.  Why?  Well, it all came down to time.  VB7 (let’s call it that for convenience, though it’s not the official moniker) was a total rewrite from VB6 – not one line of code was reused.  The new code had to target a different editor (owned by the VS Shell team) and a different runtime (owned by the CLR team).  By the time we were at a state where we could even contemplate doing EnC (around late 2000 or thereabouts), we were quite deep into the product cycle and trying to wrap things up.  We did manage to get some very basic EnC going towards the end, but it was very slow and very limited – not shippable at all.  So, with reluctance, we had to pull it, and VS2002 went out without EnC support, and neither we nor customers were very happy with that at all.

So, a few months before VS2002 shipped, those of us on the compiler team (most of us being new recruits to that area) were told in no uncertain terms that EnC *would* be part of the next major release, that being “Whidbey” (VS2005) – no excuses.  So, although we were deep at work on the point release (“Everett”/VS2003), we started to collaborate with all of the relevant product units to put together an EnC experience that would work.  It was a multi-team effort, as we realized that we’d need runtime tooling, debugger tooling, project re-working, and so on – I think that ultimately about a dozen developers were involved from several product units, not to mention their test teams and program managers to make sure it was working the way it should.  The coding on EnC also pre-dated any other Whidbey coding by a substantial amount – we started work on it right at the end of 2001, and it took nearly four years of solid work to complete and test before it was all done.  It is a huge feature which touched a lot of pre-existing code, and therefore involved loads of testing.  The end result, I feel, is highly satisfactory and on par with VB6.  So, what can you do with EnC?

Actually, before I answer that question, I should first answer the question “What is EnC?”  Edit and Continue is the ability to make changes to your code while debugging (“Edit”) and have those changes reflected throughout the rest of the debugging session without having to restart (“Continue”).  It’s a very useful feature for making those last-minute tweaks to your code.  (See http://msdn2.microsoft.com/en-us/library/ba77s56w.aspx for more details.)  EnC is convolved with Design-Time Expression Evaluation (DTEE), which is the ability to run individual methods from the Immediate Window, even while you’re debugging your main code.  The combination of these two makes for a powerful debugging tool. Here’s a very simple example:  imagine you have the following (admittedly contrived) code:

    Sub Main()

        Dim x As Integer

        x = 5

        x = x / 0

        foo()

    End Sub

 

    Sub foo()

        Dim y As Integer

        y = 5

        y = y / 0

    End Sub

 

When you press F5, the code will break with an exception at the line “x = x / 0”, because that’s dividing by zero.  So, you can immediately change the 0 to a (for example) 2 while you’re still debugging, have the change applied, and keep on going by pressing F5 or F10 or whatever, all without restarting your program execution.  That’s EnC.  However, let’s say that you suspect that maybe the subroutine foo() is similarly broken, and you want to make sure that it isn’t before letting your application run any further.  To check this, you don’t press F5 to continue, but instead in the Immediate Window, you type foo() <enter>.  The subroutine foo() executes while you are still stopped at the exception in Main.  That’s DTEE.  Foo() throws an exception just like Main did (because it is also dividing by zero), and you are taken to the appropriate location in code to fix the error.  Pressing F5 will allow Foo() to then complete its run, and then you can press F5 again to continue your run in Main (since it’s been stopped all this time).  We call this ability to be at two breakpoints at once “nested breakpoints,” and it really helps you make changes in bits and pieces of your code without having to restart your whole test scenario.

You don’t need to be running you application in order to test individual methods in the Immediate Window, though.  You can just type foo() in the Immediate Window to start the DTEE, and VS will spin up a debugging session to run it – you can do EnC on that just as before to edit and correct any errors.  (If you can’t see the Immediate Window when you’re not debugging, you can bring it up by choosing “Debug,” then “Windows,” the “Immediate,” or also by typing Control-Alt-“I” if you are using the VB profile.)   I’ll refer to this method of running DTEE as “cold,” as opposed to a DTEE usage which happens as the same time as a debugging session (and is thus “warm”).

In any event, you have to be slightly careful when doing DTEE on a method.  Consider the following code:

    Dim q As Integer

    Sub Main()

        q = 5

        foo()

    End Sub

    Sub foo()

        Dim y As Integer

        y = 5

        y = y / q

    End Sub

 

If you run the application by pressing F5, this will work fine.  However, if you try to run foo() in the Immediate Window “cold,” it’ll throw an exception.  This is because the variable “q” will never be set to 5 (as Main() won’t be called), and so will default to zero, causing a divide-by-zero error in foo().  To complicate things, the Immediate Window will use the same state as any application that is currently being debugged, so if you do a “warm” DTEE call on foo() and the F5 execution of the application has passed the point where “q” got set to 5 in Main, then foo() will work.  You need to understand what the state of your variables is when doing DTEE, and how your method will be affected by that state.  Things that impact this include using globals instead of arguments to the method, objects passed as arguments that may need to be initialized outside of the method, and other “external to the method” set-up work.  Furthermore, the method being called “warm” may change state which will impact the main run -- consider the following:

    Dim q As Integer

    Sub Main()

        q = 5

        foo()

        Stop

        Console.WriteLine(q)

    End Sub

    Sub foo()

        q = q + 1

    End Sub

 

If you run the DTEE command “foo()” from the Immediate Window three times when the program execution is stopped at “Stop,” then the Console.WriteLine(q) will print out 9 instead of 6.  It really is using the same value of "q", and it will therefore impact the rest of the F5 run.

But let’s get back to EnC.  In order for EnC (or DTEE) to function, the program needs to be stopped.  You cannot edit a running program, nor run methods in a running program via DTEE.  And even when you are stopped, not all method changes are continuable.  I was going to list the different kinds of changes that can’t be made in EnC and that require a restart of the application (known as “rude edits”), but the lists on MSDN are pretty good about this -- see http://msdn2.microsoft.com/en-us/library/k06a3215.aspx and http://msdn2.microsoft.com/en-us/library/k9x7t598.aspx.  Broadly speaking, additions/deletions of public, generic, or shared methods, public or static variables, method signatures will force you to restart (i.e., anything some other piece of code might also be using), as will certain changes to exception handlers, iteration constraints, and statements which are active higher up the call stack.  The “meat” of the method in which you’re stopped, however, is generally OK to change. 

So, for example, in the following code, if we’ve stopped at the “Stop” line in foo(), then I’m limited to changing the line before and after the call to foo() in Main, the line before “Stop” in foo(), and anything I want in bar() (except its signature).  Anything else will require me to stop my debugging session.  VB helpfully indicates which lines & methods are on the stack by coloring them as grey.

    Dim q As Integer

    Sub Main()

        q = 5

        foo()

        bar()

    Console.WriteLine(q)

    End Sub

    Sub foo()

        q = q + 1

        Stop

    End Sub

    Sub bar()

        q = q + 2

    End Sub

 

Note that, if your current line of execution is the “Stop” in the above code, it’s possible to legally change the “q = 5” line in Main() without requiring a restart, but since your program execution is already past that point, it won’t affect the current debugging session, unless you step out of foo() and either drag the current statement pointer to some point on or before the changed line, or use “Set Next Statement” in the context menu to point to accomplish the same thing.  Changing the “q = q + 2” line (or adding other code) in bar() can also be legal and it will definitely affect the debugging session, since you haven’t gotten to that point in the execution flow yet.

Here’s another example:

    Sub Main()

        Stop

        Dim x As New A

    End Sub

    Class A

        Public a1 As Integer

    End Class

 

If I try removing the “Public a1 as Integer” line from A while debugging, or if I try to add a new public variable to A, then that’s a rude edit since I’ll have changed A’s public interface.  Ditto for protected variables.  However, I can add private variables to A as much as I like, though I can’t delete any existing ones as they may be in use elsewhere by some instances of the class.  Within a method, on the other hand, I can add as many local variables as I like (since they are private to the method), and can delete them if I like, provided in the latter case that I delete all references to them as well.

If, by some chance, you make an edit which requires you to stop & restart, VB will put a grey squiggly line under the affected code to let you know that (complete with tooltip), and will also give you the option of reverting the change if you try to step past the code anyway.

But what if I have the same method running on two different threads, and I change one of them – what happens?  Let’s consider the following code:

    Sub Main()

        Dim x As New A

        Dim y As New A

        Dim t1 As New System.Threading.Thread(AddressOf x.Foo)

        Dim t2 As New System.Threading.Thread(AddressOf y.Foo)

        t1.Start()

        t2.Start()

        Do While True ' Main program does other stuff… manually break when Foo()’s are done.

 

        Loop

        Stop

    End Sub

    Class A

        Dim val As Integer

        Sub Foo()

            Stop

            val = 2 ' Change me

            Stop

        End Sub

    End Class

 

If you run this and, when reaching the first “Stop,” change the “val = 2” line to (for example) “val = 3” keep stepping through, and then “break all” after you’ve stepped through you may end up with x.val = 2 and x.val = 3, or vice-versa, or both equal to 3.  Although both threads will eventually hit the Stop commands, the threads are asynchronous with no precedence regarding execution order, the edit itself is legal, and so you cannot guarantee which thread(s) will have picked up the change.  You can’t even presume that the execution of Foo() is “atomic” in this case – “x” may hit “Stop” first, but the next two stops may belong to “y”.  However, this is a contrived case, so let’s consider something more likely – just a method with an error in it that’s called on two different threads. Let’s say that A.Foo() is defined as follows:

    Class A

        Sub Foo()

            Dim x As Integer

            x = 5

            x = x / 0 ' Oops!  Divide-by-zero error

        End Sub

    End Class

 

In this case, there’s no doubt that both threads will throw an exception roughly simultaneously and that the change would have to be made on the line that threw the exception for both.  One of the threads will throw the exception first; the other will follow, but you won’t see it yet because the debugger will only show one at a time.  If both threads are stopped on that line, then changing the “0” to a (for example) “2” would be a rude edit because more than one thread was blocked on that line, and you would need to restart in order to use the change.  If you commented out the t2.Start() line, however, the EnC would work because t2 would never execute that Foo() method and thus wouldn’t add the additional block on that line.  This is something you’ll need to be aware of if you do EnC on threaded code.

All of that barely scratches the surface of what EnC can do -- when you understand the parameters under which it can operate, it's really a very powerful tool for quickly fixing bugs in your program, especially when combined with DTEE.  I haven’t thought of a topic for my next post, though I want to get back to writing another simple app, and I have a vague idea that it should involve databases since I haven’t hitherto talked about those.  I’ll see what the grey matter can come up with this coming week.  ‘Til then…

--Matt--*

Leave a Comment
  • Please add 2 and 4 and type the answer here:
  • Post
  • PingBack from http://blog.openmindconnections.co.za/?p=39

  • When can we expect to have Edit and Continue for VB when debugging Compact Framework applications?

  • Honest answer is "I don't know."  EnC doesn't work for devices for a couple of reasons -- first, the application is running in a different process unowned by Visual Studio, and second the compact framework would have to be updated to support EnC.  The EnC code isn't small, but the compact framework needs to be small.  In short, two fairly significant problems would have to be worked through.  It would certainly be very cool to be able to do this, I agree.

    --Matt--*

  • [Nacsa Sándor, 2009. január 20. – február 12.] Komplett fejlesztő környezet Windows kliens és web alkalmazások

Page 1 of 1 (4 items)