4

This references my last question which appears to have been abandoned. I am experiencing an odd "bug" if you will with C# and MS VS 2015. To reproduce the error, follow the steps:

  1. Open console app project and copy paste code below.
  2. Set a break point here: enter image description here
  3. First run code past break point, it works! :D
  4. Then run code again but this time STOP at the break point and DRAG the executing statement cursor INTO the if statement from here: enter image description here to here: enter image description here

Hit Continue and an NRE exception is thrown. Why does this happen? Is it just me? What is the technical explination for this?

CODE:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace testapp
{
    class Program
    {
        static void Main(string[] args)
        {
            FILECollection randomCollection = new FILECollection();
            // Fill with junk test data:
            for(int i = 0; i<10; i++)
            {
                FILE junkfile = new FILE() { fileName = i.ToString(), folderName = i.ToString(), fileHashDigest = new byte[1] };
                randomCollection.Add(junkfile);
            }

            if (true)
            {
                Console.WriteLine("testing this weird exception issue...");
                FILE test;
                test = new FILE();
                test.fileName = "3";
                test.folderName = "3";
                test.fileHashDigest = new byte[1];

                FILE exists = randomCollection.Where(f => f.fileName == test.fileName &&
                                              f.fileHashDigest.SequenceEqual(test.fileHashDigest)).First();
            }
        }
    }


    public class FILE
    {
        public FILE() { _fileName = "";}
        private string _fileName;
        public string fileName
        {

            get
            {
                    if (false)
                        return this._fileName.ToUpper();
                    else
                        return this._fileName;
            }
            set
            {

                    if (false)
                        this._fileName = value.ToUpper();
                    else
                        this._fileName = value;
            }
        }
        public string folderName { get; set; }
        public byte[] fileHashDigest { get; set; }
    }

    public class FILECollection : IEnumerable<FILE>, ICollection<FILE>
    {
        private HashSet<FILE> svgHash;
        private static List<FILE> PreallocationList;
        public string FileName = "N/A";

        /// <summary>
        /// Default Constructor, will not 
        /// preallocate memory.
        /// </summary>
        /// <param name="PreallocationSize"></param>
        public FILECollection()
        {
            this.svgHash = new HashSet<FILE>();
            this.svgHash.Clear();
        }

        /// <summary>
        /// Overload Constructor Preallocates
        /// memory to be used for the new 
        /// FILE Collection.
        /// </summary>
        public FILECollection(int PreallocationSize, string fileName = "N/A", int fileHashDigestSize = 32)
        {
            FileName = fileName;
            PreallocationList = new List<FILE>(PreallocationSize);
            for (int i = 0; i <= PreallocationSize; i++)
            {
                byte[] buffer = new byte[fileHashDigestSize];
                FILE preallocationSVG = new FILE()
                {
                    fileName = "",
                    folderName = "",
                    fileHashDigest = buffer
                };
                PreallocationList.Add(preallocationSVG);
            }
            this.svgHash = new HashSet<FILE>(PreallocationList);
            this.svgHash.Clear(); // Capacity remains unchanged until a call to TrimExcess is made.
        }

        /// <summary>
        /// Add an FILE file to 
        /// the FILE Collection.
        /// </summary>
        /// <param name="svg"></param>
        public void Add(FILE svg)
        {
            this.svgHash.Add(svg);
        }

        /// <summary>
        /// Removes all elements 
        /// from the FILE Collection
        /// </summary>
        public void Clear()
        {
            svgHash.Clear();
        }


        /// <summary>
        /// Determine if the FILE collection
        /// contains the EXACT FILE file, folder, 
        /// and byte[] sequence. This guarantees 
        /// that the collection contains the EXACT
        /// file you are looking for.
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        public bool Contains(FILE item)
        {
            return svgHash.Any(f => f.fileHashDigest.SequenceEqual(item.fileHashDigest) &&
                                    f.fileName == item.fileName &&
                                    f.folderName == item.folderName);
        }

        /// <summary>
        /// Determine if the FILE collection 
        /// contains the same file and folder name, 
        /// byte[] sequence is not compared. The file and folder
        /// name may be the same but this does not guarantee the 
        /// file contents are exactly the same. Use Contains() instead.
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        public bool ContainsPartially(FILE item)
        {
            return svgHash.Any(f => f.fileName == item.fileName &&
                                    f.folderName == item.folderName);
        }

        /// <summary>
        /// Returns the total number
        /// of FILE files in the Collection.
        /// </summary>
        public int Count
        { get { return svgHash.Count(); } }

        public bool IsReadOnly
        { get { return true; } }

        public void CopyTo(FILE[] array, int arrayIndex)
        {
            svgHash.CopyTo(array, arrayIndex);
        }

        public bool Remove(FILE item)
        {
            return svgHash.Remove(item);
        }

        public IEnumerator<FILE> GetEnumerator()
        {
            return svgHash.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return svgHash.GetEnumerator();
        }
    }
}

I think either I am debugging in a terribly wrong way, or Microsoft should take a look at this. It's like future code is breaking current code...which is impossible!

enter image description here

Community
  • 1
  • 1
Hooplator15
  • 1,540
  • 7
  • 31
  • 58
  • Possible duplicate of [What is a NullReferenceException, and how do I fix it?](http://stackoverflow.com/questions/4660142/what-is-a-nullreferenceexception-and-how-do-i-fix-it) – mybirthname Oct 11 '16 at 18:30
  • 1
    You guys are missing the point, I get that it's a NRE, but it ONLY gets thrown when you use the debugger. This is not a code issue (that I know of). – Hooplator15 Oct 11 '16 at 18:31
  • 2
    What's the problem? I would expect a Null Reference Exception for test in the watches because it hasn't been instantiated yet. Put your breakpoint after the test = new FILE() line – Dustin Hodges Oct 11 '16 at 18:37
  • Is the null exception thrown from the code or are you just seeing it in the watch window when evaluating the "test" object? – KMoussa Oct 11 '16 at 18:39
  • It is actually thrown if you hit continue. – Hooplator15 Oct 11 '16 at 18:40
  • I see the exception being thrown just like you say. Please try to gradually remove insignificant code and make your sample as little as possible. That way you'll probably figure it out yourself, or someone from StackOverflow will (after you'll edit your question), without the need to read several pages of weird `FILEcollection` code. – evilkos Oct 11 '16 at 18:43
  • 1
    Are you sure you target in "debug "mode? If you target in "release", the code is optimized and the breakpoint has no correlation with actual code. – Stefano Balzarotti Oct 11 '16 at 18:45
  • I really wish I could record this.. lol I actually may do that tonight when I get home (if it happens at home also that is). I added a picture of the error, I am not able to continue after the exception, it is a fatal error. My thing is, this may be just be if no one else can experience it. I added a picture of the exception. Also, for sure in debug mode here.. – Hooplator15 Oct 11 '16 at 18:46
  • @StefanoBalzarotti I think you're right, I think he's stepping through optimized code and dragging execution past the initialization. Why are you dragging the execution line anyway? Does it throw if you just step through after the break point? – HasaniH Oct 11 '16 at 18:50
  • You guys are most likely correct. If you execute without dragging past the condition, it works fine. – Hooplator15 Oct 11 '16 at 18:51
  • But it says for sure that I am in debug mode. How do I turn code optimization off? – Hooplator15 Oct 11 '16 at 18:52
  • @JohnAugust it's in project settings\Build\Optimize code. But I don't think the optimization is a culprit here. I have optimization turned off and I can repro your issue, albeit with just 4 lines of code – evilkos Oct 11 '16 at 18:53
  • That's interesting, I'd be very interested to see if this is some type of VS bug, or if it's just some debug setting I am missing. My apologies for not stripping the method and code down, I was rushing things a bit. – Hooplator15 Oct 11 '16 at 18:57
  • noticed if you comment out the line `FILE exists = randomCollection.Where(f => f.fileName == test.fileName && f.fileHashDigest.SequenceEqual(test.fileHashDigest)).First();` the exception stops happening, I suspect it's to do with the time at which the compiler converts the lambda (which contains a reference to `test`) to and Expression – KMoussa Oct 11 '16 at 18:59
  • Same behavior reproducible with the following `if (true) { object o; o = new object(); Func m = () => o == null; }` – KMoussa Oct 11 '16 at 19:09

1 Answers1

5

OK here's my best guess..

First, as I mentioned in the comments, the exception doesn't occur if you comment out the line FILE exists = randomCollection.Where(f => f.fileName == test.fileName && f.fileHashDigest.SequenceEqual(test.fileHashDigest)).First()‌​;

Second, I noticed the same behavior can be reproduced with the following code:

if (true)
{
    object o;
    o = new object();
    Func<bool> m = () => o == null;
}

i.e. the cause seems to be related to the variable being used in a lambda expression. So, looking at the same code snippet above in ILSpy I get the following:

Program.<>c__DisplayClass0_0 <>c__DisplayClass0_ = new Program.<>c__DisplayClass0_0();
<>c__DisplayClass0_.o = new object();
Func<bool> func = new Func<bool>(<>c__DisplayClass0_.<Main>b__0);

so my best guess is that the NullReferenceException refers to <>c__DisplayClass0_ intance being null - and I'm therefore inclined to believe that the stepping through the if(true) actually skipped the first line where <>c__DisplayClass0_ is instantiated

KMoussa
  • 1,568
  • 7
  • 11
  • 1
    After some debugging with proper IL offsets, I totally agree with your conclusion. Because there is no `nop` between `L_0003: stloc.s flag` and `L_0005: newobj instance void testapp.Program/<>c__DisplayClass1::.ctor()` to which Visual Studio can jump to in debug mode, it just skips the creation of the `<>c__DisplayClass1`. The next available `L_000b: nop` is then just before the `new object()` line. (The `nops` are a place in debug mode where VS can set breakpoints or jump to if you drag the current statement.) This behavior is very interesting and feels a bit like a bug in the compiler... – haindl Oct 11 '16 at 21:40
  • 2
    I sent this issue to a former Microsoft developer who literally lead the development team for the C# compiler. This was his response: `When you move the point of execution around dynamically in the debugger, there is no guarantee that your program continues to work as expected. The compiler generates code that assumes that the code is going to be run normally. If you violate that assumption then bad things can happen; be very happy that the worst thing that happened was a null reference exception.` – Hooplator15 Oct 17 '16 at 14:07
  • continued: `The compiler is not required to generate code that is robust in the face of you moving around the point of control at your whim, and the debugger is not required to do more than a good-faith effort to move the point of control around, assuming that you know what you are doing and what the consequences will be.` – Hooplator15 Oct 17 '16 at 14:07
  • So basically, what is happening here is that when the compiler generated code, it assumed I would not be pulling the point of control into the body of the if statement. @KMoussa is almost certainly correct in saying that initialization is skipped. – Hooplator15 Oct 17 '16 at 14:11