-
Notifications
You must be signed in to change notification settings - Fork 29
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
When calling a node with a contract, guarantee is not always provable #736
Comments
Actually, I wonder whether this could be related to the new bounds checking: this particular example works fine with Could it be that the new bounds checking is encoding the guarantee as something like |
Hi Amos. Thank you for reporting this issue. It is very helpful for understanding the limitations of Kind 2.
I think that is the reason. We use Let's keep this issue open as a reminder that we need to study how to make the satisfaction of properties like these more obvious for Kind 2. |
Hi Amos, Here is a remark for the particular example provided, just in case it helps for proving properties of your original models. The main reason why Kind 2 has troubles proving the properties is due to the presence of the integer division in
Then you can use it in this way:
I hope this is helpful. |
Hi Daniel, Thanks for the trick about integer division. Unfortunately the original program doesn't have integer division for filter_input – I just used it as something complex enough for the example – but maybe I can add a simplified contract there as well. At a higher level, do you think it would make sense to have some sort of preprocessing phase that marks as solved any local propositions that are (alpha-)equivalent to the instantiated guarantees of calls to nodes with contracts? I believe this is sound, since the use of the contract will still require the assumptions to be proved separately. It should also be predictable and well-defined (but quite weak). This sort of 'solve by substitution' would help for our original case, as Kind2 can prove the assumptions of the call with contract fine, but when I add a local property that's the substitution of the guarantee that fails. Do you think this preprocessing would be a reasonable approach? Do you think it would be very hard to implement? I'm happy to have a go if you think it's worth trying. |
Hi Amos, I created a pull request (#739) that fixes this issue. When a call with a contract is abstracted with an implication like Please feel free to reopen the issue if you still experience problems with other models. Thanks, |
Thanks Daniel! Sorry I haven't had a chance to properly try this out yet, but it looks promising |
Hi Daniel, I hope you don't mind me coming back to this issue after so long. We've recently been having issues with contracts again. They usually work for smaller examples, but we're having trouble composing them together into larger models. We can usually prove the precondition OK at the use-site, but the postcondition will time out. I've been going through a few artificial examples to better understand what's happening. I would consider the following one to be "definitionally true". Kind2 proves it fine if you have invariant generation enabled, but it won't go through if invariant generation is disabled. I think that if it were possible to prove contracts like these without invariant generation it'd be much more predictable and scalable - when the nodes get a bit bigger it's impossible to know whether the right invariants will be generated in time. (That's my impression as a user at least, not knowing the details of invariant generation.)
Looking at the generated SMT problem, the main issue seems to be that the two different calls to lastn are completely separate instances. The call in the contract of with_contract is one instance, and the property of top is another instance with different state. There's no invariant linking the states of the two, and I can't write one manually because I can't refer to the internal state of lastn without changing the return types of both lastn and with_contract. I was thinking that one option would be to inline the contract into the calling node as a property for the assumes and an unchecked assertion for the guarantees. That way the two calls to This inlining and common subexpression elimination would be a bit surprising though, because the exact same contract and its definition would have slightly different behaviour. The CSE would also change the semantics if a contract referred to any non-deterministic functions, though that seems surprising to begin with. What do you think? Thanks for your time, |
Hi Amos, Thank you for sharing your experience with us and raising this issue. We are considering applying a more general solution to this problem. Namely, guide Kind 2 to discover behavioral equivalences between calls to a same node. We would like to try this before applying a more specific solution. I'll keep you posted here on any progress. Best regards, |
Hi Amos, PR #877 provides a tentative solution to this issue. I would appreciate it if you could test this change with your models, and let me know how well it works. Thanks, |
Hi Daniel, Thanks for the quick solution. I just had a quick look and it sounds promising but I'll try it out properly sometime this weekend. Amos |
Hi Daniel, When trying this out I did have an issue: I had a contract guarantee R transitively depends on a pure function with a contract. When I don't mention R in a property the occurrence of that function can be removed by slicing, so perhaps that's why it works without these properties. For some reason I can get it to go through by removing the contract from the function, even though the contract is simpler than the implementation. Interestingly, if I replace the contract with a guarantee that states the body of the function it still will not go through, which suggests to me that something is strange with contracts on functions. This isn't related to your PR #877 - I have the same issue with an older version of Kind2. Thanks for all your help. I will keep trying to create a smallish example and let you know how I go. |
Hi Amos, Thank you for the update. When a component is declared as a function (as opposed to a node), Kind 2 makes the commitment of treating the component as a true mathematical function. That is, given the same input(s) the function provides the same output(s) at all times. This is similar to what PR #877 is aiming to achieve for nodes, but the approach used for functions is different and the provided guarantees are stronger. The consequence of this additional constraint is that reasoning about a model in presence of imported functions or functions abstracted by their contracts is a lot harder for Kind 2 (this is not the case when a full implementation for a function is available and the current analysis uses it because the definition of the function ensures the satisfaction of the requirement). Moreover, the current IC3 engine cannot work with these systems and is automatically disabled. We have plans to improve the support of abstracted functions in the future, but until that happens there exists a workaround you can try. You can declare the function as a node which will avoid adding the additional constraint. If the contract of the function (together the rest of guarantees) is strong enough to prove the properties of the caller, the verification will go through and you do not have to worry about anything else. If the contract of the function is not strong enough, you may get a spurious counterexample that does not respect the functional constraint. However, when compositional and modular analyses are enabled, Kind 2 will refine the function and use the implementation of the function in a new analysis to confirm or discard the counterexample. I hope this helps. Daniel |
That's very helpful - thanks Daniel. With this information about function contracts and PR #877 I think we will have a predictable way to compose proofs together. I will keep trying with our models but unfortunately I might not get a chance until this coming weekend. I'll let you know how we go. |
Hi Amos, Thank you for your feedback. FYI, we decided to make the functional constraint optional. PR #881 makes Kind 2 only enforce the constraint on abstract functions when a new flag, Hope this helps. Daniel |
Hi Daniel,
I'm running into some trouble using contracts. Sometimes when I have a node with a guarantee like:
for some predicate
P
, then, when I call this node from another node, I expectP(the_input, contracted(the_input))
to be trivially provable:but it seems like when the caller gets complicated, Kind2 can't always prove P.
I actually expected the caller of the contract to behave as if the guarantees of the contracts it calls were asserted (something like
always(assume) => always(guarantee)
), that is, I expected the contract caller above to desugar to something like:I've found some cases, however, where I can't prove some facts that rely on
P
, but assertingP
in the caller lets me prove what I want.Is there some way that we could automatically instantiate
assert P
in the callers? Is there some subtle reason that this isn't always valid, or why they behave differently?I've attached a concrete example below. Unfortunately it's quite complicated. I spent some time trying to reduce it but I couldn't simplify it more than this. The complexity is kind of inherent because it only starts to show up for non-trivial programs that Kind2 can't prove immediately.
Thanks for any advice or clues,
Amos
The text was updated successfully, but these errors were encountered: