13

I have this struct (simplified for brevity):

public struct Period
{
    public Period(DateTime? start, DateTime? end) : this()
    {
        if (end.HasValue && start.HasValue && end.Value < start.Value)
        {
            throw new ArgumentOutOfRangeException("end", "...");
        }
        Contract.EndContractBlock();

        this.start = start;
        this.end = end;
    }

    private readonly DateTime? start;
    private readonly DateTime? end;

    public static Period operator +(Period p, TimeSpan t)
    {
        Contract.Assume(!p.start.HasValue || !p.end.HasValue || p.start.Value <= p.end.Value);
        return new Period(
            p.start.HasValue ? p.start.Value + t : (DateTime?) null,
            p.end.HasValue ? p.end.Value + t : (DateTime?) null);
    }
}

But the static checker is giving me this warning:

CodeContracts: requires unproven: end.HasValue && start.HasValue && end.Value >= start.Value

This requirement that it inferred from the custom parameter validation is just plain wrong. I want to allow null values for start or end, and only require start <= end if both are provided. However, if I change the constructor to this:

public Period(DateTime? start, DateTime? end) : this()
{
    Contract.Requires(!start.HasValue || !end.HasValue || start.Value <= end.Value);
    this.start = start;
    this.end = end;
}

I get this warning, which looks more correct, but it's hard to see why the requires can't be proven:

CodeContracts: requires unproven: !start.HasValue || !end.HasValue || start.Value <= end.Value

I thought it might be having trouble with the ?:, but this warning is still there when I change the operator to:

public static Period operator +(Period p, TimeSpan t)
{
    var start = p.start.HasValue ? p.start.Value + t : (DateTime?) null;
    var end = p.end.HasValue ? p.end.Value + t : (DateTime?) null;

    Contract.Assume(!start.HasValue || !end.HasValue || start.Value <= end.Value);
    return new Period(start, end);
}

And of course, if I change that .Requires to .Assume, the warning goes away completely, but that's not an acceptable solution.

So it appears that the static checker in Code Contracts is not able to invert the condition correctly. Rather than simply inverting the condition by wrapping it with !(…) or applying De Morgan's law (as shown above), it appears to be inverting just the last component of the condition. Is the static checker unable to correctly interpret complex conditionals when using custom parameter validation?

Interestingly, I tried this, thinking the static checker would just strip the ! off the front, but no:

if (!(!start.HasValue || !end.HasValue || start.Value <= end.Value))
{
    throw new ArgumentOutOfRangeException("end", "...");
}
Contract.EndContractBlock();

CodeContracts: requires unproven: !(!(!start.HasValue || !end.HasValue || start.Value <= end.Value))

In this case, it did just wrap the whole condition with !(…), even though it didn't have to.

Also, if I change the nullable DateTime's to just plain non-nullable DateTime's and rewrite the contracts like this, it works as expected without any warnings:

public struct Period
{
    public Period(DateTime start, DateTime end) : this()
    {
        Contract.Requires(start <= end);
        this.start = start;
        this.end = end;
    }

    private readonly DateTime start;
    private readonly DateTime end;

    public static Period operator +(Period p, TimeSpan t)
    {
        Contract.Assume(p.start + t <= p.end + t);   // or use temp variables
        return new Period(p.start + t <= p.end + t);
    }
}

But simply using Contract.Assume(p.start <= p.end) won't work.

CodeContracts: requires unproven: start <= end

p.s.w.g
  • 146,324
  • 30
  • 291
  • 331
  • This could be a bug in the way CC parses the IL of the && operator. AFAIK this operator is implemented by branching over the if body for each "arm" of the && chain. Maybe CC parsed this wrongly. Try using Contract.Requires. – usr Jul 10 '14 at 23:40
  • Version of the static checker, and was it bundled with a larger suite? – Ben Voigt Jul 10 '14 at 23:51
  • @usr Whoops! That's what I meant to do with the rewrite of the constructor (an assume like that wouldn't make any sense on a public constructor). However when I change it to `.Requires` it still generates an warning, it uses the exact condition text that I provided. This also introduces the possibility that it *might* not be a bug in the type checker, but just a bug in how it creates the warning text. – p.s.w.g Jul 11 '14 at 00:07
  • @BenVoigt v1.6.60505.10 from http://visualstudiogallery.msdn.microsoft.com/1ec7db13-3363-46c9-851f-1ce455f66970 – p.s.w.g Jul 11 '14 at 00:08
  • I spent some more time thinking about exactly the behavior you're seeing, and I think there's more to it than what I was originally talking about in my answer - for now I've retracted my answer, but I'm going to keep thinking about it. I do think operator overloading is part of it, especially since DateTime has so little contract added to it. See for your self: http://referencesource.microsoft.com/mscorlib/system/datetime.cs.html#mscorlib/system/datetime.cs – antiduh Jul 11 '14 at 16:57
  • @antiduh Actually, I think it might an issue with the `+` operator. Looking more closely at the code I was trying, I realized the only way I could get it tow work nicely with non-nullables was `var start = …; var end = …; Contracts.Assume(start <= end)` (in my previous test, I set up two classes to test and my non-nullable one was accidentally calling the constructor for my nullable one–I still don't know why the contracts were happy in that case, maybe because it could ensure they were not null?). I think CC is missing here is the ability to infer that `A <= B` implies `A + t <= B + t`. – p.s.w.g Jul 11 '14 at 18:11
  • As a side note, it is **much** easier to implement the rest of that type if you don't allow null values, in particular a workaround to this would be to use min/max values for the corresponding endpoints. In particular, what does it mean to add a timespan value to a period that does not have a start nor an end? – Lasse V. Karlsen Jul 11 '14 at 18:47
  • @LasseV.Karlsen In my case, that's referred to as an 'unbounded' period. Also, if start == end and they're not null, it's referred to as a 'moment'. These cases are required by the model that I'm coding against. I considered using min/max for unbounded endpoints but then a *lot* of other operations become more difficult. – p.s.w.g Jul 11 '14 at 19:14
  • OK. Does the contract parsing system react more nicely to something like `(both are set and start <= end) or (either are null)`? – Lasse V. Karlsen Jul 11 '14 at 19:17
  • @LasseV.Karlsen Hmmm, it actually seems to behave even worse! I replaced the original condition with `(start.HasValue && end.HasValue && end.Value < start.Value) || (!start.HasValue && !end.HasValue)` it inverts that to produce the requirement `(start.HasValue && end.HasValue && end.Value >= start.Value) && (start.HasValue && !end.HasValue)` -- it's inverting the last condition of the first group and the first condition of the second group, and changing the `||` to `&&`. – p.s.w.g Jul 11 '14 at 19:28
  • Have you had any luck on this? I'm interested in finding out how to make this work. It's easy enough when specifying `Contracts.Requires()`, but i'm curious why the translation from custom parameter checking to contract acts so weirdly. It's very possible the IL inspector in the contracts tools has a bug. – antiduh Jul 14 '14 at 15:43
  • @antiduh No luck so far. I'm still looking for a solution, but yeah I'm beginning to think it's just bugged. I am considering refactoring the class to have static factory methods like `Period.Before()`, `Period.After()`, and `Period.Between()` rather than accepting nullables (`default(Period)` would be equivalent to passing in null for both). That may lead to more insight on this issue. – p.s.w.g Jul 15 '14 at 19:00
  • @antiduh So it works when I converted it to 3 factory methods (with `Contract.Requires(start <= end)` in just one). Unfortunately, it makes my operators 6 times longer because I've got to write all a bunch of conditions, and I still need an `Assume` in one of those conditions. I could easily create use a private constructor with no contracts to get around that, but that kinda defeats the purpose. – p.s.w.g Jul 16 '14 at 18:58
  • @p.s.w.g - When I was fiddling with this a few days ago, I had good luck with the following: http://pastebin.com/8Ed5DzuA – antiduh Jul 16 '14 at 19:07
  • @antiduh Well, still no luck. I still get *requires unproven* with your code. I have also tried splitting the `DateTime?` parameters into separate `bool` and `DateTime` parameters and removing the `+ t` entirely to eliminate see if it had to do with the addition; but neither attempt seemed able to satisfy the requires. I wonder if it has to do with mixing Booleans (`.HasValue`) with comparisons (`start <= end`)? – p.s.w.g Jul 16 '14 at 21:44
  • 1
    @antiduh So it turns out that refactoring it to use 3 factory methods worked when I had `.Requires(start <= end)`. When I changed it to use custom parameter validation with `if (start > end) …` it gave me `requires unproved: start <= end`, but then I tried `if (!(start <= end)) …` and it worked perfectly. I think your assessment about `<=` vs. `>` is correct; the code that generates the message appears to assume `!(x > y)` → `x <= y`, but the actual checker does not. Although there's also probably something else going on here related to the AND's and OR's. – p.s.w.g Jul 16 '14 at 22:05
  • Also, about my pasted code I see that I was able to get warning-free code because I had the warning level set to low (default). – antiduh Jul 16 '14 at 22:09

1 Answers1

3

I think part of the problem may be your conditional that you're using in the Contract.Requires call.

Take the example of your one constructor:

public Period(DateTime? start, DateTime? end) : this()
{
    Contract.Requires(!start.HasValue || !end.HasValue || start.Value <= end.Value);
    this.start = start;
    this.end = end;
}

What if start.HasValue is false (meanining !start.HasValue is true), but end does have a value. What does start.value <= end.Value evaluate to in this situation, since one is null and the other has a value?

It seems instead, that your Contract.Requires condition should be stated as follows:

Contract.Requires(!(start.HasValue && end.HasValue) || start.Value <= end.Value);

If either one of start or end don't have a value, then the conditional will return true (and the OR condition short circuits, never evaluating whether or not start.Value <= end.Value). However, if both start and end have a value assigned, the first part of the conditional returns false, at which point then, start.Value must be less than or equal to end.Value in order for the conditional to evaluate to true overall. This is what you're after.

Here's a question for you: is it true that any instance of Period requires that start.Value <= end.Value or one or the other (or both) of them are null? If so, you could specify this as an Invariant, instead. That means that at any method entry or exit, !(start.HasValue && end.HasValue) || start.Value <= end.Value must hold true. This can simplify your contracts quite a bit when it works out.

UPDATE

Reviewing my blog article I posted in the comments (TDD and Code Contracts), you can safely annotate your operator +(Period p, TimeSpan t) implementation with the Code Contracts PureAttribute attribute. This attribute tells the Code Contracts static analyzer that the method does not alter any internal state of the object on which the method is called, and is therefore side-effect free:

[Pure]
public static Period operator +(Period p, TimeSpan t)
{
    Contract.Requires(!(p.start.HasValue && p.end.HasValue) || p.start.Value <= p.end.Value)

    return new Period(
        p.start.HasValue ? p.start.Value + t : (DateTime?) null,
        p.end.HasValue ? p.end.Value + t : (DateTime?) null);
}

UPDATE

OK, I thought about this some more, and I think I now understand the issue that Code Contracts is having with your contracts. I think you further need to add a Contract.Ensures contract (i.e. a post condition contract) to your constructor:

public Period(DateTime? start, DateTime? end) : this()
{
    Contract.Requires(!(start.HasValue && end.HasValue) || start.Value <= end.Value);
    Contract.Ensures(!(this.start.HasValue && this.end.HasValue) || this.start.Value <= this.end.Value);
    this.start = start;
    this.end = end;
}

This tells Code Contracts that when your constructor exits, the object's start and end fields, if they both have a value, must satisfy the condition that start.Value <= end.Value. If that condition is not satisfied, (potentially) an exception will be thrown by Code Contracts. This should also help out the static analyzer.

UPDATE (again, and mostly for completeness)

I did some more sleuthing on the "unproven" warning. This can occur both for Requires and Ensures. Here's another example of someone having a similar problem: http://www.go4answers.com/Example/ensures-unproven-contractresult-79084.aspx.

Adding a contract invariant can be done as follows (for the code in question by the OP):

[ContractInvariantMethod]
protected void PeriodInvariants()
{
    Contract.Invariant(!(start.HasValue && end.HasValue) || start.Value <= end.Value);
}

This method will be called upon every entry/exit into a method on the object to ensure that this condition holds true.

Another blog entry that should prove interesting

I found another blog entry by someone else that may prove interesting: http://www.rareese.com/blog/net-code-contracts

In this case, I disagree with the author's "solution" to getting rid of the requires unproven warning. Here's the author's code:

public static void TestCodeContract(int value)
{
    if(value > 100 && value < 110)
        TestLimits(value); 
}

public static void TestLimits(int i)
{
    Contract.Requires(i > 100);
    Contract.Requires(i < 110);

    //Do Something
}

Here, the real solution to the problem should be the following:

public static void TestCodeContract(int value)
{
    Contract.Requires(value > 100 && value < 110);
    // You could alternatively write two Contract.Requires statements as the blog
    // author originally did.
}

This should also get rid of the warning since the static analyzer now knows that value must be in the range of 101 to 109, which also happens to satisfy the contract criteria of the TestLimits method.

My suggestion to you, therefore, is to check wherever the Period constructor is called, and/or the Period.operator +(...) method to ensure that the calling method also has the necessary Contract.Requires statements (or, alternatively, Contract.Assume, which tells the static analyzer to just assume that the conditional provided is true).

When using Code Contracts, you need to instrument all of your code. You normally can't "pick and choose" which part to specify contracts for, as the static analyzer most likely won't have enough information to complete its analysis (and therefore, ensure the contracts are being guaranteed) and you will receive many warnings.

fourpastmidnight
  • 4,032
  • 1
  • 35
  • 48
  • Thanks for the response. I tried using the requires you suggested, but it doesn't change the behavior. I've also tried using invariants in several different forms, but it doesn't really change anything. Basically, you still need a requires in the constructor or you get an 'invariant unproven', then you need a requires or assume in the operator or you get a 'requires unproven'. – p.s.w.g Jul 22 '14 at 22:10
  • In case it helps you are anyone else, I wrote a blog article about [TDD and Microsoft Code Contracts](http://discoveringcode.blogspot.com/2014/03/code-contracts-and-test-driven.html). Especially pay attention to `Contract.Ensures` in conjunction with `Requires` and `Contract.Invariant` and how I used it in my examlpe in the blog. – fourpastmidnight Jul 23 '14 at 14:03
  • 2
    Your restatement of the condition after your What If is functionally identical to the original condition. You then swap the `&&` back for `||` in later versions ofthe condition without removing the `!(...)` you added. – Rawling Jul 23 '14 at 15:04
  • Yes, this is true; but the `Requires` call is checking the incoming parameters while the `Ensures` call is ensuring that the object's state, after performing some logic, satisfies a certain condition. In this case, the condition is the same for both the incoming parameters and for what the final state of the object should be when the constructor exits. – fourpastmidnight Jul 23 '14 at 15:08
  • @Rawling sorry, I think I see what you mean. The first "example" is a re-statement of the OP's original code. The succeeding examples are what I believe the conditional should be in order to accurately capture/convey the condition(s) that need to be met. – fourpastmidnight Jul 23 '14 at 15:09
  • Doesn't your restatement lead to the case you worried about, where exactly one of `start` or `end` has a value and then it tries to evaluate the inequality? – Rawling Jul 23 '14 at 15:14
  • I've tried many different variants (1) using `Pure` on the operator, the constructor, and / or the type itself (2) using ensures in the constructor (3) using invariants (4) using `Assume` and / or `Requires` in the operator (5) using a class or a struct (6) separating this out to a separate non-static method (7) using the original `!A || !B || A <= B` or your `!(A && B) || A <= B)` ― None of these worked. In every case, I'm still getting a 'requires unproven'. – p.s.w.g Jul 23 '14 at 15:52
  • Also note, since this is a struct, the ensures has to be written as `Contract.Ensures(!(Contract.ValueAtReturn(out this.start).HasValue && Contract.ValueAtReturn(out this.end).HasValue) || Contract.ValueAtReturn(out this.start).Value <= Contract.ValueAtReturn(out this.end).Value)` – p.s.w.g Jul 23 '14 at 15:55
  • @p.s.w.g Please make sure the conditionals are correct. I messed them up later in my updates. Sorry about that. – fourpastmidnight Jul 23 '14 at 16:37
  • @p.s.w.g Re: your comment about `struct`s: hmm, that's interesting. I didn't know that. Thanks for that! – fourpastmidnight Jul 23 '14 at 16:38
  • @p.s.w.g I don't believe you should use `Pure` on a constructor since you are changing the state of the object. Constructors, by their very nature, have side-effects. (Or am I misunderstanding side-effects in relation to object construction?) – fourpastmidnight Jul 23 '14 at 16:39
  • Yes, I noticed the typo with the conditionals and took care to write it correctly in my tests. As for `Pure`, I'm not entirely sure if it applies here (though I think it does), see my related [Programmers.SE question](http://programmers.stackexchange.com/questions/246573/when-to-use-pure-on-a-constructor); neither answer really seems definitive and I'm still trying to understand the proper use of `Pure` constructors. – p.s.w.g Jul 23 '14 at 16:42