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
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With