0

I'm playing around with code contracts and got a simple method inserting a given count of space characters between each character.

e.g.

Hello -> H e l l o
World -> W   o   r   l   d

The method InsertSpaceBetweenLetters is implemented within a class that offers some string-property S, the string that should be returned after modification. Here's the code

public string InsertSpaceBetweenLetters(int spaceWidth)
{
    Contract.Requires(spaceWidth > 0);
    Contract.Requires(S.Length > 0);
    Contract.Ensures(Contract.Result<string>() != null);
    Contract.Ensures(Contract.Result<string>().Length == S.Length + (S.Length - 1) * spaceWidth);

    string result = String.Empty;

    Contract.Assume(S.Length >= 0);
    for(int i = 0; i < S.Length; i++)
    {
        result += S[i];
        if (i < S.Length - 1)
            result += new String(' ', spaceWidth);
    }
    return result;
}

The static checker gives me the following warning:

ensures unproven: Contract.Result<string>().Length == S.Length + (S.Length - 1) * spaceWidth

I thought I could get rid of this warning with the assumption I'm doing before the loop:

Contract.Assume(S.Length >= 0);

But the warning is still there. What assumptions have to be made to get rid of the warning?

Thank you in advance.

mbue
  • 1,591
  • 2
  • 14
  • 34

3 Answers3

3

Fundamentally, I think you're asking too much of the static checker in this case. It would be pretty impressive if it really could work that out. In this case I think you'll just have to live with it being an execution-time check rather than a compile-time check.

I don't know if there's a specific way of saying "please ensure this as a post-condition, but don't try to prove it" - calling Contract.Assume with the post-condition at the end of the method may do it, but it would probably mean evaluating it twice :(

(As an aside, your implementation is currently really inefficient. That's not the topic of this question, but is still worth looking at.)

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • I know it is inefficient, it was a quick'n'dirty test. The string instantiation should be outside the loop. :-) I know the static checker cannot prove algorithms, that should violate some statements of theoretical cs. But I wanted to know: how many things I have to assume, to get the static checker working in this case? I just want to get a feeling about its limits – mbue Nov 21 '12 at 11:08
  • 1
    @mbue: That's for starters... then there's avoiding repeated string concatenation... – Jon Skeet Nov 21 '12 at 11:08
  • @mbue: I don't know how much you'll need to assume, and it will change over time. You *could* try extracting the string constructor call, making an assumption on the length of the string, and see whether it "knows" enough about the rest... – Jon Skeet Nov 21 '12 at 11:25
1

I think your assumption is wrong. It doesn't hold for S.Length == 0: in the second ensures you would get a negativ value.

Second, it's not trivial to check your ensures statement on compile time. Probably just not implemented in the checker..

Maybe you could implement your loop differently. Remove your if statement and loop from 0 to S.Length-2. But I'm not sure..

duedl0r
  • 9,289
  • 3
  • 30
  • 45
  • The S.Length == 0 case cannot occure, because of Contract.Requires(S.Length > 0); – mbue Nov 21 '12 at 11:11
  • Can you explain this? The PRE-condition tells: S.Length > 0. That means, if we reach the assumption, S.Length is greather than 0. This implies that S.Length is >= 0. – mbue Nov 21 '12 at 11:14
  • 1
    Ah I see, I interpret it like this: you can rewrite it as `Assert(S.Length >=0);` this is certainly true. But I don't think it helps the checker if he can assume the length is 0. Because in this case the ensures statement is wrong. – duedl0r Nov 21 '12 at 11:26
0

Try this

        public string InsertSpaceBetweenLetters(string S, int spaceWidth) {

        Contract.Requires(spaceWidth > 0);
        Contract.Requires(S != null);
        Contract.Requires(S.Length > 0);
        Contract.Ensures(Contract.Result<string>() != null);
        Contract.Ensures(Contract.Result<string>().Length == S.Length + (S.Length - 1) * spaceWidth);

        StringBuilder result = new StringBuilder(String.Empty);

        int count = 0;
        int final = S.Length - 1;
        foreach ( char c in S )
        {
            result.Append(c, 1);
            if ( count < final )
            {
                result.Append(' ', spaceWidth);
            }
            ++count;
        }
        return result.ToString();
    }
ddur
  • 75
  • 3