microsoft / CodeContracts

Source code for the CodeContracts tools for .NET
Other
882 stars 150 forks source link

Errors in the re-compiled IL - VS2015 #252

Open IPWright83 opened 8 years ago

IPWright83 commented 8 years ago

I've just found an issue which seems to be originating from CodeContracts after upgrading to Visual Studio 2015. I'm getting a runtime error during execution (NullReferenceException), which appears to be from some incorrectly compiled code. The code however compiles fine with VS2013 (both are targetting .NET 4.5). The issue also goes away in VS2015 if I turn off the Perform Runtime Contract Checking. Here's an example of the error in action:

image

Notice the really strange this in there is that this is throwing a NullReferenceException. So here's the code that's been compiled, where the first constructor has been called, feeding through to the 2nd one:

private BusinessContext(): this(default(UInt32), new ElementMnemonic[0], new InstanceIdentifier [0], Option<BusinessFilter>.None)
{
}

private BusinessContext(UInt32 id, IEnumerable<ElementMnemonic> elements, IEnumerable<InstanceIdentifier> instances, Option<BusinessFilter> filter)
{
   Contract.Requires<ArgumentNullException>(elements != null, "elements");
   Contract.Requires<ArgumentNullException>(instances != null, "instances");
   Contract.Requires<ArgumentNullException>(filter != null, "filter");

   this.identifier = id;
   this.elementsSet = new SortedSet<ElementMnemonic>(elements);
   this.instancesSet = new SortedSet<InstanceIdentifier>(instances);
   this.filterBackingField = filter;

   //Prepare the cache of values.
   this.valueHashCache = new Lazy<int>(() => this.HashList(this.instancesSet, HashList(this.elementsSet, filter.GetHashCode())));
}

You'll probably need to know some of the internals of Option<BusinessFilter> which seems to be the main problem so here's a copy/paste of that entire class:

    public sealed class Option<T> : IOption<T>
    {
        private static Option<T> none = new Option<T>();

        /// <summary>
        /// Implicit cast to opion.</summary>
        /// <param name="value">
        /// The value to cast.</param>
        /// <returns>
        /// Some value option.</returns>
        public static implicit operator Option<T>(T value)
        {
            return Some(value);
        }

        /// <summary>
        /// The none option.</summary>
        public static Option<T> None { get { return none; } }

        /// <summary>
        /// Create some value option.</summary>
        /// <param name="value">
        /// The value to strore.</param>
        /// <returns>
        /// Some value option.</returns>
        public static Option<T> Some(T value)
        {
            return new Option<T>(value);
        }

        private readonly T value;
        private readonly bool hasValue;

        private Option()
        {
        }

        private Option(T value)
        {
            this.hasValue = true;
            this.value = value;
        }

        /// <inheritdoc/>
        public T GetValueForce()
        {
            return this.Match(() => { throw new InvalidOperationException("GetValueForce from a None."); }, s => s);
        }

        /// <inheritdoc/>
        public bool HasValue { get { return hasValue; } }

        /// <inheritdoc/>
        public TOut Match<TOut>(Func<TOut> none, Func<T, TOut> some)
        {
            return hasValue ? some(value) : none();
        }

        /// <inheritdoc/>
        public void Match(Action none, Action<T> some)
        {
            if (hasValue)
            {
                some(value);
                return;
            }
            none();
        }

        /// <inheritdoc/>
        public override bool Equals(object obj)
        {
            // If they are the same object then ==.
            if (object.ReferenceEquals(this, obj))
                return true;

            // if that (obj as Option<T>) is null then !=
            var that = obj as Option<T>;
            if (object.ReferenceEquals(null, that))
                return false;

            // if the has value marker is different then !=.
            if (this.hasValue != that.hasValue)
                return false;

            // if the has value is false on both then ==.
            if (!this.hasValue && !that.hasValue)
                return true;

            // if the stored value is the same object then ==.
            if (object.ReferenceEquals(this.value, that.value))
                return true;

            // if this stored value is null then not equal.
            if (this.value == null)
                return false;

            // compare the stored values.
            return this.value.Equals(that.value);
        }

        /// <inheritdoc/>
        public override int GetHashCode()
        {
            return ((value == null) ? 0 : value.GetHashCode()) * 31 + hasValue.GetHashCode();
        }

        /// <inheritdoc/>
        public override string ToString()
        {
            return hasValue ? "Some: " + value : "None";
        }
    }

So I've taken a look at the compiled output using Reflector and noticed an obvious difference in the DisplayClass, namely that in VS2015 output it hasn't been initialized - explaining why we're encountering the error.

Here is the working VS2013 output:

private BusinessContext(uint id, IEnumerable<ElementMnemonic> elements, IEnumerable<InstanceIdentifier> instances, Option<BusinessFilter> filter)
{
    BusinessContext_<>c__DisplayClass2_0 class_ = new BusinessContext_<>c__DisplayClass2_0 {
        filter = filter
    };
    __ContractsRuntime.Requires<ArgumentNullException>(elements != null, "elements", "elements != null");
    __ContractsRuntime.Requires<ArgumentNullException>(instances != null, "instances", "instances != null");
    __ContractsRuntime.Requires<ArgumentNullException>(class_.filter != null, "filter", "filter != null");
    bool flag = this.$evaluatingInvariant$;
    this.$evaluatingInvariant$ = true;
    Func<int> valueFactory = null;
    this.valueHashCache = null;
    this.identifier = id;
    this.elementsSet = new SortedSet<ElementMnemonic>(elements);
    this.instancesSet = new SortedSet<InstanceIdentifier>(instances);
    this.filterBackingField = filter;
    if (valueFactory == null)
    {
        valueFactory = () => this.HashList<InstanceIdentifier>(this.instancesSet, this.HashList<ElementMnemonic>(this.elementsSet, filter.GetHashCode()));
    }
    this.valueHashCache = new Lazy<int>(valueFactory);
    class_.<>4__this = this;
    this.$evaluatingInvariant$ = flag;
    this.$InvariantMethod$();
}

and here is the broken VS2015 output:

private BusinessContext(uint id, IEnumerable<ElementMnemonic> elements, IEnumerable<InstanceIdentifier> instances, Option<BusinessFilter> filter)
{
    BusinessContext_<>c__DisplayClass20_0_0 class__;
    __ContractsRuntime.Requires<ArgumentNullException>(elements > null, "elements", "elements != null");
    __ContractsRuntime.Requires<ArgumentNullException>(instances > null, "instances", "instances != null");
    __ContractsRuntime.Requires<ArgumentNullException>(class__.filter > null, "filter", "filter != null");
    bool flag = this.$evaluatingInvariant$;
    this.$evaluatingInvariant$ = true;
    this.valueHashCache = null;
    this.identifier = id;
    this.elementsSet = new SortedSet<ElementMnemonic>(elements);
    this.instancesSet = new SortedSet<InstanceIdentifier>(instances);
    this.filterBackingField = filter;
    this.valueHashCache = new Lazy<int>(() => this.HashList<InstanceIdentifier>(this.instancesSet, this.HashList<ElementMnemonic>(this.elementsSet, filter.GetHashCode())));
    class__.<>4__this = this;
    this.$evaluatingInvariant$ = flag;
    this.$InvariantMethod$();
}
fedotovalex commented 8 years ago

This looks very similar to #191.

IPWright83 commented 8 years ago

@fedotovalex Looking at it yeah, the result is certainly the same. I'll leave it open for now in-case the circumstances that trigger the issue are different at all. If not I imagine @SergeyTeplyakov should be able to verify and close.