1

I've just started playing with code contracts, and while promising, they seem to have some limitations with respect to value types. For instance:

public struct Wrap<T>
    where T : class
{
    readonly T value;
    public Wrap(T value)
    {
        Contract.Requires(value != null);
        this.value = value;
    }
    public T Value
    {
        get
        {
            Contract.Requires(Value != null);
            return value;
        }
    }
    [Pure]
    [ContractInvariantMethod]
    void Invariant()
    {
        Contract.Invariant(value != null);
    }
    public static T BigError()
    {
        Contract.Ensures(Contract.Result<T>() != null);
        var x = default(Wrap<T>);
        Contract.Assert(x.Value != null);
        return x.Value;
    }
}

Wrap.BigError clearly demonstrates the problem. This sample compiles and ccheck verifies 4 assertions, yet the assertions will clearly fail at runtime. Some of these assertions are redundant and I inserted them just be sure the verifier is checking these properties at the designated points.

I don't see this sort of thing listed as a known problem in MS's docs for code contracts, but it seems too obvious to be an omission. Am I missing something?

naasking
  • 2,514
  • 1
  • 27
  • 32

2 Answers2

0

Turns out the problem was the invariant specified in the struct. If you remove the invariant, you get the expected errors. Contrary to MS's documentation, the static checker does seem to account for struct invariants.

naasking
  • 2,514
  • 1
  • 27
  • 32
0

There's something strange in your getter contract for the Value property. You recursively require that this.Value != null. I'm sure you meant something else, e.g.

Contract.Ensures( Contract.Result<T>() != null );

With that fix, the specification looks perfectly reasonable to me. The limitation of the checker (including runtime) is that you can always create default values of structs and we cannot check the invariant on these default values. So, the checking is modulo the construction of default values.

Manuel Fahndrich
  • 665
  • 4
  • 12
  • No, I explicitly wanted to recursively require that value is not null to ensure that the user is not using a default struct instance. The whole point of these contracts was to require clients to construct a struct via a constructor. See: http://higherlogics.blogspot.ca/2013/04/impressions-of-code-contracts.html – naasking Apr 25 '13 at 14:40
  • I understand your intention, but it doesn't work that way. If you want to make sure people use instantiated structs, add a field and property `Initialized` that gets set in a real constructor. Then make all methods `Requires(this.Initialized)`. It's painful, but I think that is what you are trying to do. – Manuel Fahndrich Apr 25 '13 at 16:44
  • It does work. See the linked blog post. As long as the property is public, the precondition can be specified recursively as I've done here and the static checker will return an error if it detects an invalid use. – naasking Apr 26 '13 at 02:54
  • Here's what's happening. The static checker actually infers a post condition for your Value getter which is `Ensures(Contract.Result() == this.value)`. In the case of your example code, the static checker also knows that the default(T) leaves the value field as null. Thus, it can prove that the precondition is false in this case. In terms of runtime checking, your contrac will cause some recursive evaluation that we cut at runtime after 3-4 levels. – Manuel Fahndrich Apr 26 '13 at 20:30
  • Note that your invariant is still not really an invariant. It isn't true for the default constructor. Thus, it is misleading. Also, if you are using this code from another assembly, you will need more contracts, namely a post condition on your constructor that states that the Value is not null (within the assembly, we infer that). – Manuel Fahndrich Apr 26 '13 at 20:32
  • Re: invariant not actually being true, correct, that seems to be a bug in the static checker for value types and I already submitted a connect bug report a few days ago. Re: inferring a postcondition, regardless of how it's happening, my point is simply that it works just fine as a static check. I also wrote a few private examples, like a struct array/vector whose length is guaranteed to be greater than 0 using the same recursive precondition technique. It's not great if you're just using runtime contract checking due to looping, but my purpose was to flex the static checker to its limits. – naasking Apr 26 '13 at 22:03
  • It's a cool experiment that you are doing. Re: the invariant, I have to disagree with you on that one strongly. It's not a static checker bug, instead it is a limitation as documented in my earlier comment. What is wrong is that the invariant simply isn't true. I create a default value of your struct and clearly the invariant does not hold, no matter what any static checker would do. So it isn't an invariant. – Manuel Fahndrich Apr 26 '13 at 22:07
  • The static checker attempts to prove invariants hold. If it can't, then I must state some pre/postconditions or to explicitly force an assumption. In this case, merely stating the invariant makes the static checks succeed erroneously, eg. take the above code, switch it to a class and add a default constructor: the static checker emits a warning that the invariant is false. Value types always have such a default constructor, but the above invariant holds. Anyway, the fail-safe approach is an error when invariants are declared on structs, not to succeed silently. Seems wrong either way. – naasking Apr 26 '13 at 22:44
  • Sure, the static checker could warn about the invariant not being true for the implicit default constructor. Assume it does, how would that help you with your example? It would mean you'd have to remove the invariant, so the invariant is wrong. – Manuel Fahndrich Apr 26 '13 at 22:48
  • Yes, the invariant is wrong. I was hoping either for the checker to catch that, or to hold the invariant true and propagate that requirement to all clients thus preventing use of default struct instances, like I did with the recursive precondition. Remember, the ultimate goal was always to prevent use of default struct instances so all these propositions were specifically designed to push that boundary. – naasking Apr 26 '13 at 23:13