Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Code Contracts can't invert conditionals?

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

like image 211
p.s.w.g Avatar asked Jul 10 '14 15:07

p.s.w.g


1 Answers

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.

like image 184
fourpastmidnight Avatar answered Oct 16 '22 17:10

fourpastmidnight