Skip to content
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

ring PR #5

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open

ring PR #5

wants to merge 14 commits into from

Conversation

witemap
Copy link
Collaborator

@witemap witemap commented Jan 6, 2023

No description provided.

invariant forall e *Ring :: e in elems ==> ((e != r && e != p) ==> (e.next != nil && e.prev != nil))
invariant forall e1, e2 *Ring :: (e1 in elems && e1.next == e2 && e1 != p && e2 != r) ==> (e2 in elems && e2.prev == e1 && e1.next.prev == e1 && e2.prev.next == e2)
//invariant forall e1, e2 *Ring :: (e1 in elems && e1.prev == e2 && e1 != r && e2 != p) ==> (e2 in elems && e2.next == e1 && e1.prev.next == e1 && e2.next.prev == e2)
//invariant forall e1, e2 *Ring :: (e1 in elems && e1.prev == e2 && e1 != r && e2 != p) ==> (e2 in elems && e2.next == e1) //# Why does this not work?
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With these invariants I try to build up the required facts to fold the predicate r.Mem(elems, true) in the end. This line (239) does not verify. It is a shortened version of the line above which of course also does not work. It complains the loop invariant cannot be preserved. Interestingly, it verifies successfully with the invariant in line 237. This quantified expression is a modified version of a similar expression stated in the predicate. It aims to establish how neighboring ring elements are connected. Because the end-points of the ring (r at the beginning and p at the end) are not already connected in this original implementation I exclude them in the LHS of the implication. The idea is if we have e1.prev == e2 and e2 != p (i.e. e2 is not the "last" element in the chain) then we should have e2.next == e1. The implication is successful if we only had e2 in elems on the RHS. The code suggests the whole implication should hold true and the vice-versa case holds on line 237.

Even if this invariant were to hold, I am very unsure whether I could successfully fold the predicate at the very end.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I spent some time debug but I did not find any obvious mistake. Maybe one way to move forward is to generate the viper file for this specific method (using --printVpr and the @ notation). With this file, you can then try to run silicon with the options --moreCompleteExhale --counterexample variables which produces a counterexample for the failing assertion at the viper level. With this, you can try to infer a counterexample at the Gobra level for the invariant

src/container/ring/ring.gobra Outdated Show resolved Hide resolved
src/container/ring/ring.gobra Outdated Show resolved Hide resolved
src/container/ring/ring.gobra Outdated Show resolved Hide resolved
src/container/ring/ring.gobra Outdated Show resolved Hide resolved
@viperproject viperproject deleted a comment from jcp19 Jan 12, 2023
}

//# Functional properties of this method have NOT yet been verified.
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Functional properties (e.g. r.Move(3) produces r.next.next.next) could not be verified.
The idea would be to use something like moveNext from the ring_spec.gobra file in the invariant and postcondition. However, this did not terminate even after a day when used in the invariant. Testing moveNext by itself works just fine.
The same applies of course to cases where n<0 and movePrev.

func New(n int) *Ring {
//@ ensures n <= 0 ==> res == nil
//@ ensures n > 0 ==> (len(elems) == n && res.Mem(elems, true))
func New(n int) (res *Ring /*@, ghost elems set[*Ring] @*/) {
Copy link
Collaborator Author

@witemap witemap Jan 12, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not find the time to continue the investigation as to why the invariants for the original implementation of New did not verify. (see https://github.com/jcp19/verified_go_stdlib/pull/5#discussion_r1065748315)
Therefore, for this version I included my adapted implementation with an explanation and will point this out in the report.

//# ensures (s != nil && s in elemsR && r == s && len(elemsR) > 1) ==> res.Mem((elemsR setminus (set[*Ring]{r})), true) CANNOT FOLD
//# ensures (s != nil && s in elemsR && r != s && len(elemsR) == 1) ==> UNIMPLEMENTED
//@ decreases
func (r *Ring) Link(s *Ring /*@, ghost elemsR set[*Ring], ghost elemsS set[*Ring] @*/) (res *Ring) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only a subset of the behaviours are dealt with here. Corresponding comments have been included.
Sometimes it is necessary to assume forall i *Ring :: i in (elemsR union elemsS) ==> (i.next != i && i.prev != i) since Gobra cannot establish that by itself. The problem is likely that the predicate by itself does not guarantee that all the elements form a single ring structure. If multiple smaller ring structures were possible then this implication could be false. However, the behaviour of the methods effectively preserves that it will always be a single ring structure once initialized. I will point this out in the report.

//@ requires r != nil
//@ requires r.Mem(elems, isInit)
//@ ensures n <= 0 ==> res == nil
func (r *Ring) Unlink(n int /*@, ghost elems set[*Ring], ghost owner *Ring, ghost isInit bool @*/) (res *Ring) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unlink could not be specified even for the subset of cases we handled in Link. The reasons are:

  • We don't get any information from Move about at what position the element pointed to by r is in the end
  • Move changes r itself and we loose the reference to the Mem predicate for the call to Link. This could possibly be fixed by carrying the owner through all calls everywhere.
  • We only call Link if n>0, thus even if we would get the information about the new position of r from Move we would always enter a case in Link that we could not handle unless n==len(elems

//@ ensures r == nil ==> res == 0
//@ ensures (r != nil && !isInit) ==> (r.Mem(set[*Ring]{r}, true) && res == 1)
//@ ensures (r != nil && isInit) ==> (r.Mem(elems, true) && res == len(elems))
func (r *Ring) Len(/*@ ghost elems set[*Ring], ghost isInit bool @*/) (res int) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The loop could not be verified since we could not establish that after len(elems) many iterations (i.e. p=p.next len(elems) many times) we get r==p.

//@ requires false //# UNIMPLEMENTED
//@ requires r != nil ==> r.Mem(elems, isInit)
//@ trusted //# required because of error with 'f'
func (r *Ring) Do(f func(any) /*@, ghost elems set[*Ring], ghost isInit bool @*/) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We made no attempt at Do

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants