Skip to content

Commit 939805b

Browse files
DspilFelix A. Wolfjcp19Felalolf
authored
Embedded interfaces (#492)
* stuff * test case works, everything else not * temp * refactoring * bugfix * added the unit tests * bug fix * bug-fix * added Felix's test case * implemented my own feedback * removed commeted out code * more cleanup * fixed bug * added test * cycle detected * added a comment * cgedges * Generate correct implementation proof between interface types * Fix problems with termination edges transform * updated todo * Revert changes that are not supposed to be in this PR * Revert some changes from a previous commit * Add extra type-checks for better error messages * fix line of error * Update src/main/scala/viper/gobra/ast/internal/transform/CGEdgesTerminationTransform.scala Co-authored-by: Felix Wolf <60103963+Felalolf@users.noreply.github.com> * Incorporate Felix's feedback Co-authored-by: Felix A. Wolf <felix.wolf@inf.ethz.ch> Co-authored-by: João Pereira <joaopereira.19@gmail.com> Co-authored-by: Felix Wolf <60103963+Felalolf@users.noreply.github.com>
1 parent 93346d6 commit 939805b

31 files changed

+732
-116
lines changed

src/main/scala/viper/gobra/ast/frontend/Ast.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -690,7 +690,7 @@ case class PPredType(args: Vector[PType]) extends PTypeLit
690690
case class PInterfaceType(
691691
embedded: Vector[PInterfaceName],
692692
methSpecs: Vector[PMethodSig],
693-
predSpec: Vector[PMPredicateSig]
693+
predSpecs: Vector[PMPredicateSig]
694694
) extends PTypeLit with PUnorderedScope
695695

696696
sealed trait PInterfaceClause extends PNode

src/main/scala/viper/gobra/ast/internal/PrettyPrinter.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -591,7 +591,7 @@ class DefaultPrettyPrinter extends PrettyPrinter with kiama.output.PrettyPrinter
591591
case TupleT(ts, _) => parens(showTypeList(ts))
592592
case PredT(args, _) => "pred" <> parens(showTypeList(args))
593593
case struct: StructT => emptyDoc <> block(hcat(struct.fields map showField))
594-
case _: InterfaceT => "interface" <> parens("...")
594+
case i: InterfaceT => "interface" <> parens("name is " <> i.name)
595595
case _: DomainT => "domain" <> parens("...")
596596
case ChannelT(elem, _) => "chan" <+> showType(elem)
597597
case SortT => "sort"

src/main/scala/viper/gobra/ast/internal/Program.scala

Lines changed: 66 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -33,22 +33,22 @@ case class Program(
3333
}
3434

3535
class LookupTable(
36-
definedTypes: Map[(String, Addressability), Type],
37-
definedMethods: Map[MethodProxy, MethodLikeMember],
38-
definedFunctions: Map[FunctionProxy, FunctionLikeMember],
39-
definedMPredicates: Map[MPredicateProxy, MPredicateLikeMember],
40-
definedFPredicates: Map[FPredicateProxy, FPredicateLikeMember],
36+
private val definedTypes: Map[(String, Addressability), Type] = Map.empty,
37+
private val definedMethods: Map[MethodProxy, MethodLikeMember] = Map.empty,
38+
private val definedFunctions: Map[FunctionProxy, FunctionLikeMember] = Map.empty,
39+
private val definedMPredicates: Map[MPredicateProxy, MPredicateLikeMember] = Map.empty,
40+
private val definedFPredicates: Map[FPredicateProxy, FPredicateLikeMember] = Map.empty,
4141
/**
4242
* only has to be defined on types that implement an interface // might change depending on how embedding support changes
4343
* SortedSet is used to achieve a consistent ordering of members across runs of Gobra
4444
*/
45-
val memberProxies: Map[Type, SortedSet[MemberProxy]],
45+
private val directMemberProxies: Map[Type, SortedSet[MemberProxy]] = Map.empty,
4646
/**
4747
* empty interface does not have to be included
4848
* SortedSet is used to achieve a consistent ordering of members across runs of Gobra
4949
*/
50-
val interfaceImplementations: Map[InterfaceT, SortedSet[Type]],
51-
implementationProofPredicateAliases: Map[(Type, InterfaceT, String), FPredicateProxy]
50+
private val directInterfaceImplementations: Map[InterfaceT, SortedSet[Type]] = Map.empty,
51+
private val implementationProofPredicateAliases: Map[(Type, InterfaceT, String), FPredicateProxy] = Map.empty,
5252
) {
5353
def lookup(t: DefinedT): Type = definedTypes(t.name, t.addressability)
5454
def lookup(m: MethodProxy): MethodLikeMember = definedMethods(m)
@@ -61,22 +61,69 @@ class LookupTable(
6161
def getMPredicates: Iterable[MPredicateLikeMember] = definedMPredicates.values
6262
def getFPredicates: Iterable[FPredicateLikeMember] = definedFPredicates.values
6363

64-
def getDefinedTypes: Map[(String, Addressability), Type] = definedTypes
65-
def getDefinedMethods: Map[MethodProxy, MethodLikeMember] = definedMethods
66-
def getDefinedFunctions: Map[FunctionProxy, FunctionLikeMember] = definedFunctions
67-
def getDefinedMPredicates: Map[MPredicateProxy, MPredicateLikeMember] = definedMPredicates
68-
def getDefinedFPredicates: Map[FPredicateProxy, FPredicateLikeMember] = definedFPredicates
69-
def getImplementationProofPredicateAliases: Map[(Type, InterfaceT, String), FPredicateProxy] = implementationProofPredicateAliases
70-
71-
def implementations(t: InterfaceT): SortedSet[Type] = interfaceImplementations.getOrElse(t.withAddressability(Addressability.Exclusive), SortedSet.empty)
72-
def members(t: Type): SortedSet[MemberProxy] = memberProxies.getOrElse(t.withAddressability(Addressability.Exclusive), SortedSet.empty)
73-
def lookup(t: Type, name: String): Option[MemberProxy] = members(t).find(_.name == name)
74-
64+
def lookupImplementations(t: InterfaceT): SortedSet[Type] = getImplementations.getOrElse(t.withAddressability(Addressability.Exclusive), SortedSet.empty)
65+
def lookupNonInterfaceImplementations(t: InterfaceT): SortedSet[Type] = lookupImplementations(t).filterNot(_.isInstanceOf[InterfaceT])
66+
def lookupMembers(t: Type): SortedSet[MemberProxy] = getMembers.getOrElse(t.withAddressability(Addressability.Exclusive), SortedSet.empty)
67+
def lookup(t: Type, name: String): Option[MemberProxy] = lookupMembers(t).find(_.name == name)
7568
def lookupImplementationPredicate(impl: Type, itf: InterfaceT, name: String): Option[PredicateProxy] = {
7669
lookup(impl, name).collect{ case m: MPredicateProxy => m }.orElse{
7770
implementationProofPredicateAliases.get(impl, itf, name)
7871
}
7972
}
73+
74+
def getImplementations: Map[InterfaceT, SortedSet[Type]] = transitiveInterfaceImplementations
75+
def getMembers: Map[Type, SortedSet[MemberProxy]] = transitiveMemberProxies
76+
77+
def merge(other: LookupTable): LookupTable = new LookupTable(
78+
definedTypes ++ other.definedTypes,
79+
definedMethods ++ other.definedMethods,
80+
definedFunctions ++ other.definedFunctions,
81+
definedMPredicates ++ other.definedMPredicates,
82+
definedFPredicates ++ other.definedFPredicates,
83+
directMemberProxies ++ other.directMemberProxies,
84+
directInterfaceImplementations ++ other.directInterfaceImplementations,
85+
implementationProofPredicateAliases ++ other.implementationProofPredicateAliases,
86+
)
87+
88+
private lazy val (transitiveInterfaceImplementations, transitiveMemberProxies) = {
89+
var res = directInterfaceImplementations
90+
var resMemberProxies = directMemberProxies
91+
92+
for ((_, values) <- res; t <- values) t match {
93+
case t: InterfaceT if !res.contains(t) => res += (t -> SortedSet.empty)
94+
case _ =>
95+
}
96+
97+
var change = false
98+
var temp = res
99+
100+
def mergeProxies(l: Option[SortedSet[MemberProxy]], r: Option[SortedSet[MemberProxy]]): SortedSet[MemberProxy] = {
101+
(l.getOrElse(SortedSet.empty[MemberProxy]) ++ r.getOrElse(SortedSet.empty[MemberProxy])).foldLeft((SortedSet.empty[MemberProxy], SortedSet.empty[String])){
102+
case ((res, set), x) if set.contains(x.name) =>
103+
// method redeclarations are currently rejected
104+
Violation.violation(x.isInstanceOf[PredicateProxy], s"Found re-declaration or override of $x, which is currently not supported")
105+
(res, set) // always take first in sorted set
106+
case ((res, set), x) => (res ++ SortedSet(x), set ++ SortedSet(x.name))
107+
}._1
108+
}
109+
val mapsTo = temp.compose[Type]{ case t: InterfaceT => t }
110+
def trans(key: InterfaceT, t: Type): SortedSet[Type] = t match {
111+
case mapsTo(set) =>
112+
change = true
113+
res += (key -> (res(key) ++ set))
114+
resMemberProxies += (t -> mergeProxies(resMemberProxies.get(t), resMemberProxies.get(key)))
115+
set
116+
117+
case _ => SortedSet.empty
118+
}
119+
120+
do {
121+
change = false
122+
temp = temp.map{ case (key, values) => (key, values.flatMap(trans(key, _))) }
123+
} while (change)
124+
125+
(res, resMemberProxies)
126+
}
80127
}
81128

82129
sealed trait Member extends Node

src/main/scala/viper/gobra/ast/internal/transform/CGEdgesTerminationTransform.scala

Lines changed: 85 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,15 @@ object CGEdgesTerminationTransform extends InternalTransform {
2525
var methodsToAdd: Set[in.Member] = Set.empty
2626
var definedMethodsDelta: Map[in.MethodProxy, in.MethodLikeMember] = Map.empty
2727

28-
table.memberProxies.foreach {
28+
def isEmbeddedMethod(subTProxy: in.MethodProxy, superTProxy: in.MethodProxy): Boolean = {
29+
// The proxies for embedded methods defined in interface type superT are the same
30+
// as the method proxies for the corresponding method in an embedding interface type subT
31+
subTProxy == superTProxy
32+
}
33+
34+
table.getMembers.foreach {
2935
case (t: in.InterfaceT, proxies) =>
30-
val implementations = table.interfaceImplementations(t)
36+
val implementations = table.lookupImplementations(t)
3137
proxies.foreach {
3238
case proxy: in.MethodProxy =>
3339
table.lookup(proxy) match {
@@ -48,38 +54,65 @@ object CGEdgesTerminationTransform extends InternalTransform {
4854
* }
4955
* }
5056
*/
51-
case m: in.Method if m.terminationMeasures.nonEmpty =>
57+
case m: in.Method if m.terminationMeasures.nonEmpty && m.receiver.typ == t =>
58+
// The restriction `m.receiver.typ` ensures that the member with the addtional call-graph edges
59+
// is only generated once, when looking at the original definition of the method (and not, for
60+
// example, when looking at an embedding of the method).
61+
5262
// only performs transformation if method has termination measures
5363
val src = m.getMeta
5464
val assumeFalse = in.Assume(in.ExprAssertion(in.BoolLit(b = false)(src))(src))(src)
65+
val optCallsToImpls = implementations.toVector.flatMap { subT: in.Type =>
66+
table.lookup(subT, proxy.name).toVector.map {
67+
68+
case implProxy: in.MethodProxy if !subT.isInstanceOf[in.InterfaceT] =>
69+
// looking at a concrete implementation of the method
70+
in.If(
71+
in.EqCmp(in.TypeOf(m.receiver)(src), typeAsExpr(subT)(src))(src),
72+
in.Seqn(Vector(
73+
in.MethodCall(
74+
m.results map parameterAsLocalValVar,
75+
in.TypeAssertion(m.receiver, subT)(src),
76+
implProxy, m.args
77+
)(src),
78+
in.Return()(src)
79+
))(src),
80+
in.Seqn(Vector())(src)
81+
)(src)
82+
83+
case implProxy: in.MethodProxy if subT.isInstanceOf[in.InterfaceT]
84+
&& isEmbeddedMethod(implProxy, proxy) =>
85+
// If the subtype (subT) is an interface type and the method is defined in subT
86+
// via an interface embedding, then the contract of the method is the same and
87+
// there is no need to generate extra proof obligations.
88+
// The soundness of this argument critically relies on the fact that if a type T implements
89+
// an interface B and B has interface A embedded, then T must implement A too.
90+
in.Seqn(Vector())(src)
91+
92+
case _: in.MethodProxy if subT.isInstanceOf[in.InterfaceT] =>
93+
Violation.violation(s"Type $subT contains a re-definition of method ${proxy.name}. This is still not supported.")
94+
95+
case v => Violation.violation(s"Expected a MethodProxy but got $v instead.")
96+
97+
}
98+
}
5599
val newBody = {
56100
in.Block(
57101
decls = Vector.empty,
58-
stmts = assumeFalse +: implementations.toVector.flatMap { t: in.Type =>
59-
table.lookup(t, proxy.name).map {
60-
case implProxy: in.MethodProxy =>
61-
in.If(
62-
in.EqCmp(in.TypeOf(m.receiver)(src), typeAsExpr(t)(src))(src),
63-
in.Seqn(Vector(
64-
in.MethodCall(
65-
m.results map parameterAsLocalValVar,
66-
in.TypeAssertion(m.receiver, t)(src),
67-
implProxy, m.args
68-
)(src),
69-
in.Return()(src)
70-
))(src),
71-
in.Seqn(Vector())(src)
72-
)(src)
73-
case v => Violation.violation(s"Expected a MethodProxy but got $v instead.")
74-
}
75-
}
102+
stmts = assumeFalse +: optCallsToImpls
76103
)(src)
77104
}
78105
val newMember = in.Method(m.receiver, m.name, m.args, m.results, m.pres, m.posts, m.terminationMeasures, Some(newBody.toMethodBody))(src)
79106
methodsToRemove += m
80107
methodsToAdd += newMember
81108
definedMethodsDelta += proxy -> newMember
82109

110+
case m: in.Method if m.terminationMeasures.nonEmpty && m.receiver.typ != t =>
111+
val recvT = m.receiver.typ.asInstanceOf[in.InterfaceT]
112+
// Sanity check: no method is ignored by this case analysis
113+
Violation.violation(table.lookupImplementations(recvT).contains(t),
114+
s"Method ${m.name} found for type $t even though its receiver is not $t or one of its supertypes.")
115+
83116
/**
84117
* Transforms the abstract pure methods from interface declarations into non-abstract pure methods containing calls
85118
* to all implementations' corresponding methods. The new body has the form
@@ -109,7 +142,7 @@ object CGEdgesTerminationTransform extends InternalTransform {
109142
* that are not easily reproducible via a transformation at the level of the internal code.
110143
*
111144
*/
112-
case m: in.PureMethod if m.terminationMeasures.nonEmpty =>
145+
case m: in.PureMethod if m.terminationMeasures.nonEmpty && m.receiver.typ == t =>
113146
Violation.violation(m.results.length == 1, "Expected one and only one out-parameter.")
114147
Violation.violation(m.posts.isEmpty, s"Expected no postcondition, but got ${m.posts}.")
115148
// only performs transformation if method has termination measures
@@ -124,17 +157,32 @@ object CGEdgesTerminationTransform extends InternalTransform {
124157
val terminationCheckBody = {
125158
val returnType = m.results.head.typ
126159
val fallbackProxyCall = in.PureMethodCall(m.receiver, fallbackProxy, m.args, returnType)(src)
127-
val bodyFalseBranch = implementations.toVector.foldLeft[in.Expr](fallbackProxyCall) {
128-
case (accum: in.Expr, impl: in.Type) =>
129-
table.lookup(impl, proxy.name) match {
130-
case Some(implProxy: in.MethodProxy) =>
160+
val implProxies: Vector[(in.Type, in.MemberProxy)] = implementations.toVector.flatMap{ impl =>
161+
table.lookup(impl, proxy.name).map(implProxy => (impl, implProxy))
162+
}
163+
val bodyFalseBranch = implProxies.foldLeft[in.Expr](fallbackProxyCall) {
164+
case (accum: in.Expr, (subT: in.Type, implMemberProxy: in.MemberProxy)) =>
165+
implMemberProxy match {
166+
case implProxy: in.MethodProxy if !subT.isInstanceOf[in.InterfaceT] =>
131167
in.Conditional(
132-
in.EqCmp(in.TypeOf(m.receiver)(src), typeAsExpr(impl)(src))(src),
133-
in.PureMethodCall(in.TypeAssertion(m.receiver, impl)(src), implProxy, m.args, returnType)(src),
168+
in.EqCmp(in.TypeOf(m.receiver)(src), typeAsExpr(subT)(src))(src),
169+
in.PureMethodCall(in.TypeAssertion(m.receiver, subT)(src), implProxy, m.args, returnType)(src),
134170
accum,
135171
returnType
136172
)(src)
137-
case None => accum
173+
174+
case implProxy: in.MethodProxy if subT.isInstanceOf[in.InterfaceT]
175+
&& isEmbeddedMethod(implProxy, proxy) =>
176+
// If the subtype (subT) is an interface type and the method is defined in subT
177+
// via an interface embedding, then the contract of the method is the same and
178+
// there is no need to generate extra proof obligations.
179+
// The soundness of this argument critically relies on the fact that if a type T implements
180+
// and interface B and B has interface A embedded, then T must implement A too.
181+
accum
182+
183+
case _: in.MethodProxy if subT.isInstanceOf[in.InterfaceT] =>
184+
Violation.violation(s"Type $subT contains a re-definition of method ${proxy.name}. This is still not supported.")
185+
138186
case v => Violation.violation(s"Expected a MethodProxy but got $v instead.")
139187
}
140188
}
@@ -148,6 +196,13 @@ object CGEdgesTerminationTransform extends InternalTransform {
148196
definedMethodsDelta += fallbackProxy -> fallbackFunction
149197
definedMethodsDelta += proxy -> transformedM
150198

199+
200+
case m: in.PureMethod if m.terminationMeasures.nonEmpty && m.receiver.typ != t =>
201+
val recvT = m.receiver.typ.asInstanceOf[in.InterfaceT]
202+
// Sanity check: no method is ignored by this case analysis
203+
Violation.violation(table.lookupImplementations(recvT).contains(t),
204+
s"Pure method ${m.name} found for type $t even though its receiver is not $t or one of its supertypes.")
205+
151206
case _ =>
152207
}
153208
case _ =>
@@ -160,16 +215,7 @@ object CGEdgesTerminationTransform extends InternalTransform {
160215
in.Program(
161216
types = p.types,
162217
members = p.members.diff(methodsToRemove.toSeq).appendedAll(methodsToAdd),
163-
table = new in.LookupTable(
164-
definedTypes = table.getDefinedTypes,
165-
definedMethods = table.getDefinedMethods ++ definedMethodsDelta,
166-
definedFunctions = table.getDefinedFunctions,
167-
definedMPredicates = table.getDefinedMPredicates,
168-
definedFPredicates = table.getDefinedFPredicates,
169-
memberProxies = table.memberProxies,
170-
interfaceImplementations = table.interfaceImplementations,
171-
implementationProofPredicateAliases = table.getImplementationProofPredicateAliases
172-
)
218+
table = p.table.merge(new in.LookupTable(definedMethods = definedMethodsDelta)),
173219
)(p.info)
174220
}
175221

0 commit comments

Comments
 (0)