Unexpected consequences when changing the next line of execution in Visual Studio

Why does it go boom?

using System; using System.Linq; namespace Test { internal class Program { private static void Main(string[] args) { try { // 1. Hit F10 to step into debugging. string[] one = {"1"}; //2. Drag arrow to make this next statement executed // 3. Hit f5. Enumerable.Range(1,1) .Where(x => one.Contains(x.ToString())); } catch (Exception exception) { Console.Write("BOOM!"); } } } } 
+7
debugging c # visual-studio
source share
2 answers

Looking at ILDASM's conclusion, there might be an explanation ...

  .locals init ([0] class Test.Program/'<>c__DisplayClass1' 'CS$<>8__locals2', [1] class [mscorlib]System.Exception exception, [2] string[] CS$0$0000) IL_0000: nop .try { IL_0001: newobj instance void Test.Program/'<>c__DisplayClass1'::.ctor() IL_0006: stloc.0 IL_0007: nop IL_0008: ldloc.0 IL_0009: ldc.i4.1 IL_000a: newarr [mscorlib]System.String IL_000f: stloc.2 IL_0010: ldloc.2 IL_0011: ldc.i4.0 IL_0012: ldstr "1" IL_0017: stelem.ref IL_0018: ldloc.2 IL_0019: stfld string[] Test.Program/'<>c__DisplayClass1'::one IL_001e: ldc.i4.1 IL_001f: ldc.i4.1 IL_0020: call class [mscorlib]System.Collections.Generic.IEnumerable`1<int32> [System.Core]System.Linq.Enumerable::Range(int32, int32) IL_0025: ldloc.0 IL_0026: ldftn instance bool Test.Program/'<>c__DisplayClass1'::'<Main>b__0'(int32) IL_002c: newobj instance void class [mscorlib]System.Func`2<int32,bool>::.ctor(object, native int) IL_0031: call class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0> [System.Core]System.Linq.Enumerable::Where<int32>(class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0>, class [mscorlib]System.Func`2<!!0,bool>) IL_0036: pop IL_0037: nop IL_0038: leave.s IL_004a } // end .try catch [mscorlib]System.Exception { 

When you drag the execution cursor, you run the risk of damaging the call stack. This is because dragging the cursor literally skips these lines. When starting in the debugger after pressing F10, the cursor stops at the beginning of the Main procedure before trying. If you drag to create an array, you will skip this line:

IL_0001: newobj instance void Test.Program/'<>c__DisplayClass1'::.ctor()

Creates an instance of the Program class. Then the following class of the program is used:

IL_0019: stfld string[] Test.Program/'<>c__DisplayClass1'::one

That due to the fact that you missed it, you did not create this object, so when you start, you get a NullReferenceException .

Why people cannot reproduce this on VS2012, I'm not sure. It may be that the compiler outputs different ILs, but this is far, since I can come up with VS2013 Ultimate and C # 4.5.

Interestingly, when you comment on try / catch, the start of a program in IL looks like this:

 .locals init ([0] class Test.Program/'<>c__DisplayClass1' 'CS$<>8__locals2', [1] string[] CS$0$0000) IL_0000: newobj instance void Test.Program/'<>c__DisplayClass1'::.ctor() IL_0005: stloc.0 

What you see the first line in the routine, creates a Program object. Why did the compiler decide to put this line inside try / catch outside of me.

EDIT

Digging a little deeper, changing your program to this:

  private static void Main(string[] args) { string[] one; try { // 1. Hit F10 to step into debugging. one = new string[] { "1" }; //2. Drag arrow to this // 3. Hit f5. Enumerable.Range(1, 1) .Where(x => one.Contains(x.ToString())); } catch (Exception exception) { Console.Write("BOOM!"); } } 

Results in working code. After examining IL, you will see that instantiation has been moved outside of try:

  .locals init ([0] class [mscorlib]System.Exception exception, [1] class [mscorlib]System.Func`2<int32,bool> 'CS$<>9__CachedAnonymousMethodDelegate1', [2] class Test.Program/'<>c__DisplayClass2' 'CS$<>8__locals3', [3] string[] CS$0$0000) IL_0000: ldnull IL_0001: stloc.1 IL_0002: newobj instance void Test.Program/'<>c__DisplayClass2'::.ctor() IL_0007: stloc.2 IL_0008: nop .try { 

The compiler was good enough to move the creation of an array of strings due to an attempt to try inside try, so skipping this string still leads to a valid object. The code works, so I assume that the NullReferenceException indeed an instance of the Program class.

+3
source share

In short, when you drag the arrow, there is no memory allocation for the variable one , so basically I think that you get a reference to the Null Pointer in a nice wrapper

It's funny to think that the same thing happens in .NET4.0, NET4.5, VS2013 and VS2015 RC, which another compiler (Roslyn) should have. Look at the image below.

My build is pretty bad, so I will not try to understand what is happening.

Start here

enter image description here

Normal execution

enter image description here

I highlighted the changes in your null array, but it exists, also you can see the registry changes.

Do not watch what happens when I drag

enter image description here

Your variable does not even exist (and also almost looks at registry changes), so I assume that you just skipped a few lines or assemblies.

Now you can also look at the memory and not see anything there.

Box memory

enter image description here

Empty space, but it is.

If I find him, he even matters

enter image description here

Drag and Drop Memory

enter image description here

There is nothing.

Considering the fact that I tried to use 2 versions of .NET and 2 different compilers, I think that this should be a VisualStudio problem, you can post this error here, it can be fixed in a future update.

+1
source share

All Articles