@@ -16,8 +16,10 @@ import viper.silver.plugin.{ParserPluginTemplate, SilverPlugin}
1616import viper .silver .verifier .errors .AssertFailed
1717import viper .silver .verifier ._
1818import fastparse ._
19+ import viper .silver .frontend .{DefaultStates , ViperPAstProvider }
20+ import viper .silver .logger .SilentLogger
1921import viper .silver .parser .FastParserCompanion .whitespace
20- import viper .silver .reporter .Entity
22+ import viper .silver .reporter .{ Entity , NoopReporter , WarningsDuringTypechecking }
2123
2224import scala .annotation .unused
2325
@@ -29,6 +31,8 @@ class TerminationPlugin(@unused reporter: viper.silver.reporter.Reporter,
2931
3032 private def deactivated : Boolean = config != null && config.terminationPlugin.toOption.getOrElse(false )
3133
34+ private var decreasesClauses : Seq [PDecreasesClause ] = Seq .empty
35+
3236 /**
3337 * Keyword used to define decreases clauses
3438 */
@@ -93,7 +97,12 @@ class TerminationPlugin(@unused reporter: viper.silver.reporter.Reporter,
9397
9498 // Apply the predicate access to instance transformation only to decreases clauses.
9599 val newProgram : PProgram = StrategyBuilder .Slim [PNode ]({
96- case dt : PDecreasesTuple => transformPredicateInstances.execute(dt): PDecreasesTuple
100+ case dt : PDecreasesTuple =>
101+ decreasesClauses = decreasesClauses :+ dt
102+ transformPredicateInstances.execute(dt): PDecreasesTuple
103+ case dc : PDecreasesClause =>
104+ decreasesClauses = decreasesClauses :+ dc
105+ dc
97106 case d => d
98107 }).recurseFunc({ // decreases clauses can only appear in functions/methods pres and methods bodies
99108 case PProgram (_, _, _, _, functions, _, methods, _, _) => Seq (functions, methods)
@@ -104,6 +113,126 @@ class TerminationPlugin(@unused reporter: viper.silver.reporter.Reporter,
104113 newProgram
105114 }
106115
116+ private def constrainsWellfoundednessUnexpectedly (ax : PAxiom , wfTypeName : Option [String ]): Seq [PType ] = {
117+
118+ def isWellFoundedFunctionCall (c : PCall ): Boolean = {
119+ if (! c.isDomainFunction)
120+ return false
121+ if (! (c.idnuse.name == " decreases" || c.idnuse.name == " bounded" ))
122+ return false
123+ c.function match {
124+ case df : PDomainFunction => df.domainName.name == " WellFoundedOrder"
125+ case _ => false
126+ }
127+ }
128+
129+ def isNotExpectedConstrainedType (t : PType ): Boolean = {
130+ if (! t.isValidOrUndeclared)
131+ return false
132+ if (wfTypeName.isEmpty)
133+ return true
134+ val typeNames = t match {
135+ case PPrimitiv (" Perm" ) => Seq (" Rational" , " Perm" )
136+ case PPrimitiv (n) => Seq (n)
137+ case PSeqType (_) => Seq (" Seq" )
138+ case PSetType (_) => Seq (" Set" )
139+ case PMultisetType (_) => Seq (" MultiSet" )
140+ case PMapType (_, _) => Seq (" Map" )
141+ case PDomainType (d, _) if d.name == " PredicateInstance" => Seq (" PredicateInstances" )
142+ case PDomainType (d, _) => Seq (d.name)
143+ }
144+ ! typeNames.exists(tn => wfTypeName.contains(tn))
145+ }
146+
147+ ax.exp.shallowCollect{
148+ case c : PCall if isWellFoundedFunctionCall(c) && c.domainSubstitution.isDefined &&
149+ c.domainSubstitution.get.contains(" T" ) &&
150+ isNotExpectedConstrainedType(c.domainSubstitution.get.get(" T" ).get) =>
151+ c.domainSubstitution.get.get(" T" ).get
152+ }
153+ }
154+
155+ override def beforeTranslate (input : PProgram ): PProgram = {
156+ val allClauseTypes : Set [Any ] = decreasesClauses.flatMap{
157+ case PDecreasesTuple (Seq (), _) => Seq (())
158+ case PDecreasesTuple (exps, _) => exps.map(e => e.typ match {
159+ case PUnknown () if e.isInstanceOf [PCall ] => e.asInstanceOf [PCall ].idnuse.typ
160+ case _ => e.typ
161+ })
162+ case _ => Seq ()
163+ }.toSet
164+ val presentDomains = input.domains.map(_.idndef.name).toSet
165+
166+ // Check if the program contains any domains that define decreasing and bounded functions that do *not* have the expected names.
167+ for (d <- input.domains) {
168+ val name = d.idndef.name
169+ val typeName = if (name.endsWith(" WellFoundedOrder" ))
170+ Some (name.substring(0 , name.length - 16 ))
171+ else
172+ None
173+ val wronglyConstrainedTypes = d.axioms.flatMap(a => constrainsWellfoundednessUnexpectedly(a, typeName))
174+ reporter.report(WarningsDuringTypechecking (wronglyConstrainedTypes.map(t =>
175+ TypecheckerWarning (s " Domain ${d.idndef.name} constrains well-foundedness functions for type ${t} and should be named <Type>WellFoundedOrder instead. " , d.pos._1))))
176+ }
177+
178+ val importStmts = allClauseTypes flatMap {
179+ case TypeHelper .Int if ! presentDomains.contains(" IntWellFoundedOrder" ) => Some (" import <decreases/int.vpr>" )
180+ case TypeHelper .Ref if ! presentDomains.contains(" RefWellFoundedOrder" ) => Some (" import <decreases/ref.vpr>" )
181+ case TypeHelper .Bool if ! presentDomains.contains(" BoolWellFoundedOrder" ) => Some (" import <decreases/bool.vpr>" )
182+ case TypeHelper .Perm if ! presentDomains.contains(" RationalWellFoundedOrder" ) && ! presentDomains.contains(" PermWellFoundedOrder" ) => Some (" import <decreases/perm.vpr>" )
183+ case PMultisetType (_) if ! presentDomains.contains(" MultiSetWellFoundedOrder" ) => Some (" import <decreases/multiset.vpr>" )
184+ case PSeqType (_) if ! presentDomains.contains(" SeqWellFoundedOrder" ) => Some (" import <decreases/seq.vpr>" )
185+ case PSetType (_) if ! presentDomains.contains(" SetWellFoundedOrder" ) => Some (" import <decreases/set.vpr>" )
186+ case PPredicateType () if ! presentDomains.contains(" PredicateInstancesWellFoundedOrder" ) =>
187+ Some (" import <decreases/predicate_instance.vpr>" )
188+ case _ if ! presentDomains.contains(" WellFoundedOrder" ) => Some (" import <decreases/declaration.vpr>" )
189+ case _ => None
190+ }
191+ if (importStmts.nonEmpty) {
192+ val importOnlyProgram = importStmts.mkString(" \n " )
193+ val importPProgram = PAstProvider .generateViperPAst(importOnlyProgram)
194+ val mergedDomains = input.domains.filter(_.idndef.name != " WellFoundedOrder" ) ++ importPProgram.get.domains
195+
196+ val mergedProgram = input.copy(domains = mergedDomains)(input.pos)
197+ super .beforeTranslate(mergedProgram)
198+ } else {
199+ super .beforeTranslate(input)
200+ }
201+ }
202+
203+ object PAstProvider extends ViperPAstProvider (NoopReporter , SilentLogger ().get) {
204+ def generateViperPAst (code : String ): Option [PProgram ] = {
205+ val code_id = code.hashCode.asInstanceOf [Short ].toString
206+ _input = Some (code)
207+ execute(Array (" --ignoreFile" , code_id))
208+
209+ if (errors.isEmpty) {
210+ Some (semanticAnalysisResult)
211+ } else {
212+ None
213+ }
214+ }
215+
216+ def setCode (code : String ): Unit = {
217+ _input = Some (code)
218+ }
219+
220+ override def reset (input : java.nio.file.Path ): Unit = {
221+ if (state < DefaultStates .Initialized ) sys.error(" The translator has not been initialized." )
222+ _state = DefaultStates .InputSet
223+ _inputFile = Some (input)
224+
225+ /** must be set by [[setCode ]] */
226+ // _input = None
227+ _errors = Seq ()
228+ _parsingResult = None
229+ _semanticAnalysisResult = None
230+ _verificationResult = None
231+ _program = None
232+ resetMessages()
233+ }
234+ }
235+
107236
108237 /**
109238 * Remove decreases clauses from the AST and add them as information to the AST nodes
0 commit comments