Open davidcok opened 2 years ago
Fundamentally, the read effect of a method should be handled like write effects, everything that a method's execution may read should be included in the accessible clause for that method, just like everything that a method may write must be included in the method's assignable clause. This kind of specification is modular, because the contract takes into account all the possible effects, the client does not need to know how the effect might be achieved in the code. This enables the client of a method to reason about a call to that method without having to look at the method's code or the specification of methods that it calls.
The use of data groups, pure methods, or location sets for tracking what a method is allowed to read can help with making the specification less burdensome to write and easier to maintain. I don't think that the specification of a method should refer to another method that the method in question calls, to me that is not modular, so I dislike the proposed syntax for designating the contents of another method's reads clause. Instead, I prefer the first solution that you say is not very maintainable, but which I think has no more maintenance problems than what we have for assignable (writes) clauses.
Gary - I consider my first solution totally unworkable. One would need to list locations readable by a method own through all its call graph — which locations may not be known or even nameable by a method author.
So that leads to with defining a datagroup for every method that reads anything, so that datagroup can be used in the reads clause of each of its callers.
But that is a maintenance nuisance — we might as well implicitly define such a datagroup and have syntax for naming it.
If you work through an example on paper, I think you will see what I mean.
On Feb 10, 2022, at 11:44 AM, Gary T. Leavens @.***> wrote:
Fundamentally, the read effect of a method should be handled like write effects, everything that a method's execution may read should be included in the accessible clause for that method, just like everything that a method may write must be included in the method's assignable clause. This kind of specification is modular, because the contract takes into account all the possible effects, the client does not need to know how the effect might be achieved in the code. This enables the client of a method to reason about a call to that method without having to look at the method's code or the specification of methods that it calls.
The use of data groups, pure methods, or location sets for tracking what a method is allowed to read can help with making the specification less burdensome to write and easier to maintain. I don't think that the specification of a method should refer to another method that the method in question calls, to me that is not modular, so I dislike the proposed syntax for designating the contents of another method's reads clause. Instead, I prefer the first solution that you say is not very maintainable, but which I think has no more maintenance problems than what we have for assignable (writes) clauses.
— Reply to this email directly, view it on GitHub https://github.com/JavaModelingLanguage/RefMan/issues/48#issuecomment-1035147135, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABITDQBODBHHM3ECXRWIQVTU2PTPRANCNFSM5OBHZQAA. Triage notifications on the go with GitHub Mobile for iOS https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675 or Android https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub. You are receiving this because you authored the thread.
I don't see how doing that is any different than what we already do for assignable (writes) clauses. Are you talking about being able to have a method (say one far down the call chain) read some hidden cache or other private state?
I can see the requirement to access elements in contracts, and other JML elements.
My thoughts are:
obj::foo.ensures
seems strange. obj::foo <: Supplier<T>
and Supplier<T>
does not have further member variables. \invariant_for
and \static_invariant_for
. obj::foo.\contracts.<name>.ensures.\all
to retrieve an expression (conjunction) of all ensures clauses in the contract <name>
, and obj::foo.\contracts.<name>.ensures.myclause
to retrieve a specific clause. \spec
namespace. For example, something like this: Here is the uml code to play around:
@startuml
class java.Class {
}
class java.Method {}
class jml.Expression {}
class jml.Formula {}
class jml.Method {
+ visibility : VisibilityModifier
+ kind : {GHOST, MODEL}
+ name : String}
+ params : List<Parameters>
+ body : jml.BlockStmt
}
class jml.Field {
+ visibility : VisibilityModifier
+ kind : {GHOST, MODEL}
+ name : String
+ params : List<Parameters>
}
class jml.Class {
+\inv : Formula
+\initially : Formula
}
class jml.Contract {
+ name : String
+ assignable : Expression
}
jml.Contract -- "1..*" jml.FClause : +requires
jml.Contract -- "1..*" jml.FClause : +ensures
jml.Contract -- "1..*" jml.EClause : +diverges
jml.Contract -- "1..*" jml.EClause : +assignable
class jml.FClause {}
class jml.EClause {}
jml.EClause o-- "1..*" jml.Expression
java.Class --> "0..*" jml.Field
jml.FClause --> jml.Formula : +/all
jml.FClause o-- "1..*" jml.Formula
java.Class --> jml.Class
java.Class --> "0..*" jml.Method
java.Method --> "0..*" jml.Contract
jml.Field --> "0..1" jml.Expression : "representsBy"
@enduml
Adding capabilities to refer to other specifications is a huge jump in expressiveness, and to my mind would make the specification language quite different than current JML. I am not sure of the properties of such a language and how verification tools could use it.
In addition, I worry about making a specification depend on the form of another specification, in ways that may break when the second specification is rewritten. For example, a JML specification like:
//@ requires P; //@ ensures Q; public void m();
could equivalently be expressed as:
//@ ensures P ==> Q;
public void m();
but this would have quite different interactions with the proposed constructs. Similarly using multiple specification cases is equivalent to a single specification case after some translation. So, it would make more sense if all specifications were transformed into some kind of normal form before access. Is that part of your proposal?
I have two points:
Is there not a wellfoundedness problem:
class X {
X x;
//@ ensures next.foo::ensures
void foo() { ... next.foo(); ... }
}
Probably we need something like a least/greatest fixpoint semantics here. I am confident that this can be chosen consistent, but it looks weird.
I agree with Gary that we should abstract from the inner workings of methods in modular verification.
I have observed that in most cases it is the footprint of an object that one modifies. So having a ghost field "footprint" that lists all locations that belong to an object seems very sensible. So in most cases I would have: accessible / assignable this.footprint;
Rarely I encounter arg.footprint
for a method argument.
Sometimes one explicitly lists all fields, ok, but this is not really modular verification then.
Actually, there is a way to abstract away from predicates / expressions canonically using model methods. Wojtek Mostowski and I have shown that at the Modularity 15 conference in Fort Collins, CO. (Gary was there :smile:) https://link.springer.com/chapter/10.1007/978-3-319-46969-0_7
This can in particular be used in the face of subtyping. One can hand around a predicate o.preForMethodX()
indicating that o satisfies the precondition for MethodX. In a modular context, one does not know how the predicate is defined, but one knows that it holds. And one can reason with it using dependency analyses.
No syntactical extensions needed. (We added "two_state" to abstract from post conditions and "\covariant" and "\contravariant" to ensure that predicates are reimplemented correctly.
I like the idea of using model methods to abstract from the specifications of other methods. (I will have to reread that paper...) And I like the idea that no syntactical extensions are needed.
JML has accessible (reads) clauses for methods. These mostly use the same syntax as assignable clauses do.
//@ reads a, bar; public int foo() { return a + bar; }
But if foo() calls a method, say bar(), then what?
//@ model int _bar;
//@ reads _bar; or //@ reads b1, b2, b3; where b1 b2 b3 area all 'in' _bar public int bar() { ... } //@ reads _bar; public int foo() { return bar(); }
---- also not very maintainable
//@ reads a, A::bar.reads; public int foo() { return a + bar(); }
}
We could then also have A::bar.requires, A::bar.ensures, A::bar.assigns. (Dafny defines reads and requires like this)
Or a different syntax is possible: e.g. \requires(A::bar) We'd want these to be applicable to lambda expressions also.
Thoughts?