@@ -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