Skip to content

Commit 7f1385b

Browse files
authored
Merge pull request #710 from viperproject/meilers_decreases_autoimport
Auto-including well-foundedness functions and axioms when they are used
2 parents cafd011 + 0e1f857 commit 7f1385b

12 files changed

Lines changed: 263 additions & 36 deletions

File tree

src/main/resources/import/decreases/all.vpr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import "bool.vpr"
55
import "int.vpr"
66
import "multiset.vpr"
77
import "predicate_instance.vpr"
8-
import "rational.vpr"
8+
import "perm.vpr"
99
import "ref.vpr"
1010
import "seq.vpr"
1111
import "set.vpr"

src/main/resources/import/decreases/multiset.vpr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
import "declaration.vpr"
55

6-
domain MuliSetWellFoundedOrder[S]{
6+
domain MultiSetWellFoundedOrder[S]{
77
//MultiSet
88
axiom multiset_ax_dec{
99
forall mSet1: Multiset[S], mSet2: Multiset[S] :: {decreasing(mSet1, mSet2)}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Any copyright is dedicated to the Public Domain.
2+
// http://creativecommons.org/publicdomain/zero/1.0/
3+
4+
import "declaration.vpr"
5+
6+
domain PermWellFoundedOrder{
7+
//Rationals
8+
axiom rational_ax_dec{
9+
forall int1: Perm, int2: Perm :: {decreasing(int1, int2)}
10+
(int1 <= int2 - 1/1) ==> decreasing(int1, int2)
11+
}
12+
axiom rational_ax_bound{
13+
forall int1: Perm :: {bounded(int1)}
14+
int1 >= 0/1 ==> bounded(int1)
15+
}
16+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
// This Source Code Form is subject to the terms of the Mozilla Public
2+
// License, v. 2.0. If a copy of the MPL was not distributed with this
3+
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
4+
//
5+
// Copyright (c) 2011-2021 ETH Zurich.
6+
7+
package viper.silver.frontend
8+
9+
10+
import ch.qos.logback.classic.Logger
11+
import viper.silver.ast.Program
12+
import viper.silver.logger.ViperStdOutLogger
13+
import viper.silver.plugin.SilverPluginManager
14+
import viper.silver.reporter.{AstConstructionFailureMessage, AstConstructionSuccessMessage, Reporter}
15+
import viper.silver.verifier.{Failure, Success, VerificationResult, Verifier}
16+
17+
import java.nio.file.Path
18+
19+
20+
class ViperPAstProvider(override val reporter: Reporter,
21+
override implicit val logger: Logger = ViperStdOutLogger("ViperPAstProvider", "INFO").get,
22+
private val disablePlugins: Boolean = false) extends SilFrontend {
23+
24+
class Config(args: Seq[String]) extends SilFrontendConfig(args, "ViperPAstProviderConfig") {
25+
verify()
26+
}
27+
28+
class PAstProvidingVerifier(rep: Reporter) extends Verifier {
29+
private var _config: Config = _
30+
31+
def config: Config = _config
32+
33+
override def name = "Viper PAST Constructor"
34+
35+
override def version = ""
36+
37+
override def buildVersion = ""
38+
39+
override def copyright: String = "(c) Copyright ETH Zurich 2012 - 2023"
40+
41+
override def signature: String = name
42+
43+
override def debugInfo(info: Seq[(String, Any)]): Unit = {}
44+
45+
override def dependencies: Seq[Nothing] = Nil
46+
47+
override def start(): Unit = ()
48+
49+
override def stop(): Unit = {}
50+
51+
override def verify(program: Program): VerificationResult = Success
52+
53+
override def parseCommandLine(args: Seq[String]): Unit = {
54+
_config = new Config(args)
55+
}
56+
57+
override def reporter: Reporter = rep
58+
}
59+
60+
// All phases after semantic analysis omitted
61+
override val phases: Seq[Phase] = Seq(Parsing, SemanticAnalysis)
62+
63+
override def result: VerificationResult = {
64+
65+
if (_errors.isEmpty) {
66+
require(state >= DefaultStates.SemanticAnalysis)
67+
Success
68+
}
69+
else {
70+
Failure(_errors)
71+
}
72+
}
73+
74+
protected var instance: PAstProvidingVerifier = _
75+
76+
override def createVerifier(fullCmd: String): Verifier = {
77+
instance = new PAstProvidingVerifier(reporter)
78+
instance
79+
}
80+
81+
override def configureVerifier(args: Seq[String]): SilFrontendConfig = {
82+
instance.parseCommandLine(args)
83+
instance.config
84+
}
85+
86+
override def reset(input: Path): Unit = {
87+
super.reset(input)
88+
if (_config != null) {
89+
noPluginsManager = SilverPluginManager(None)(reporter, logger, _config, fp)
90+
}
91+
}
92+
93+
private var noPluginsManager = SilverPluginManager(None)(reporter, logger, _config, fp)
94+
override def plugins: SilverPluginManager = {
95+
if (disablePlugins) noPluginsManager
96+
else {
97+
super.plugins
98+
}
99+
}
100+
}

src/main/scala/viper/silver/plugin/standard/termination/TerminationPlugin.scala

Lines changed: 131 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@ import viper.silver.plugin.{ParserPluginTemplate, SilverPlugin}
1616
import viper.silver.verifier.errors.AssertFailed
1717
import viper.silver.verifier._
1818
import fastparse._
19+
import viper.silver.frontend.{DefaultStates, ViperPAstProvider}
20+
import viper.silver.logger.SilentLogger
1921
import viper.silver.parser.FastParserCompanion.whitespace
20-
import viper.silver.reporter.Entity
22+
import viper.silver.reporter.{Entity, NoopReporter, WarningsDuringTypechecking}
2123

2224
import 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

src/test/resources/termination/errorMessages/multipleErrors.vpr

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
// failure filtering by Silicon should not filter out one of the failures but correctly return both errors
55

66
import <decreases/int.vpr>
7-
import <decreases/predicate_instance.vpr>
87

98
function factorialPure(n: Int): Int
109
decreases // wrong termination measure

src/test/resources/termination/errorMessages/notDefined.vpr

Lines changed: 0 additions & 27 deletions
This file was deleted.

src/test/resources/termination/functions/basic/adt.vpr

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// Any copyright is dedicated to the Public Domain.
22
// http://creativecommons.org/publicdomain/zero/1.0/
33

4-
import <decreases/int.vpr>
54

65
domain list {
76

src/test/resources/termination/functions/basic/allTypes.vpr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Any copyright is dedicated to the Public Domain.
22
// http://creativecommons.org/publicdomain/zero/1.0/
33

4-
import <decreases/all.vpr>
4+
import <decreases/multiset.vpr>
55

66
//Example decreasing Int
77
function fact(x:Int): Int
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Any copyright is dedicated to the Public Domain.
2+
// http://creativecommons.org/publicdomain/zero/1.0/
3+
4+
5+
domain Huh {}
6+
7+
function fac(i: Int, h: Huh): Int
8+
requires i >= 0
9+
decreases h
10+
{
11+
//:: ExpectedOutput(termination.failed:tuple.false)
12+
i == 0 ? 1 : i * fac(i - 1, h)
13+
}

0 commit comments

Comments
 (0)