Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CLI Option for Disabling Support for Global Variables #541

Merged
merged 4 commits into from
Oct 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion src/main/scala/viper/gobra/frontend/Config.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import viper.gobra.util.{TypeBounds, Violation}
import viper.silver.ast.SourcePosition

import scala.concurrent.duration.Duration
import scala.util.matching.Regex

object LoggerDefaults {
val DefaultLevel: Level = Level.INFO
Expand Down Expand Up @@ -62,6 +63,7 @@ object ConfigDefaults {
lazy val DefaultAssumeInjectivityOnInhale: Boolean = true
lazy val DefaultParallelizeBranches: Boolean = false
lazy val DefaultDisableMoreCompleteExhale: Boolean = false
lazy val DefaultEnableLazyImports: Boolean = false
}

case class Config(
Expand Down Expand Up @@ -109,6 +111,7 @@ case class Config(
// branches will be verified in parallel
parallelizeBranches: Boolean = ConfigDefaults.DefaultParallelizeBranches,
disableMoreCompleteExhale: Boolean = ConfigDefaults.DefaultDisableMoreCompleteExhale,
enableLazyImports: Boolean = ConfigDefaults.DefaultEnableLazyImports,
) {

def merge(other: Config): Config = {
Expand Down Expand Up @@ -150,6 +153,7 @@ case class Config(
assumeInjectivityOnInhale = assumeInjectivityOnInhale || other.assumeInjectivityOnInhale,
parallelizeBranches = parallelizeBranches,
disableMoreCompleteExhale = disableMoreCompleteExhale,
enableLazyImports = enableLazyImports || other.enableLazyImports,
)
}

Expand All @@ -163,10 +167,13 @@ case class Config(

object Config {
// the header signals that a file should be considered when running on "header-only" mode
val header = """\/\/\s*\+gobra""".r
val header: Regex = """\/\/\s*\+gobra""".r
val prettyPrintedHeader = "// +gobra"
require(header.matches(prettyPrintedHeader))
def sourceHasHeader(s: Source): Boolean = header.findFirstIn(s.content).nonEmpty

val enableLazyImportOptionName = "enableLazyImport"
val enableLazyImportOptionPrettyPrinted = s"--$enableLazyImportOptionName"
}

// have a look at `Config` to see an inline description of some of these parameters
Expand All @@ -192,6 +199,7 @@ case class BaseConfig(gobraDirectory: Path = ConfigDefaults.DefaultGobraDirector
assumeInjectivityOnInhale: Boolean = ConfigDefaults.DefaultAssumeInjectivityOnInhale,
parallelizeBranches: Boolean = ConfigDefaults.DefaultParallelizeBranches,
disableMoreCompleteExhale: Boolean = ConfigDefaults.DefaultDisableMoreCompleteExhale,
enableLazyImports: Boolean = ConfigDefaults.DefaultEnableLazyImports,
) {
def shouldParse: Boolean = true
def shouldTypeCheck: Boolean = !shouldParseOnly
Expand Down Expand Up @@ -242,6 +250,7 @@ trait RawConfig {
assumeInjectivityOnInhale = baseConfig.assumeInjectivityOnInhale,
parallelizeBranches = baseConfig.parallelizeBranches,
disableMoreCompleteExhale = baseConfig.disableMoreCompleteExhale,
enableLazyImports = baseConfig.enableLazyImports,
)
}

Expand Down Expand Up @@ -583,6 +592,13 @@ class ScallopGobraConfig(arguments: Seq[String], isInputOptional: Boolean = fals
noshort = true,
)

val enableLazyImports: ScallopOption[Boolean] = opt[Boolean](
name = Config.enableLazyImportOptionName,
descr = s"Enforces that ${GoVerifier.name} parses depending packages only when necessary. Note that this disables certain language features such as global variables.",
default = Some(ConfigDefaults.DefaultEnableLazyImports),
noshort = true,
)

/**
* Exception handling
*/
Expand Down Expand Up @@ -715,5 +731,6 @@ class ScallopGobraConfig(arguments: Seq[String], isInputOptional: Boolean = fals
assumeInjectivityOnInhale = assumeInjectivityOnInhale(),
parallelizeBranches = parallelizeBranches(),
disableMoreCompleteExhale = disableMoreCompleteExhale(),
enableLazyImports = enableLazyImports(),
)
}
15 changes: 13 additions & 2 deletions src/main/scala/viper/gobra/frontend/Desugar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3063,7 +3063,17 @@ object Desugar {
// As for imported methods, imported packages are not checked to satisfy their specification. In particular,
// Gobra does not check that the package postconditions are established by the initialization code. Instead,
// this is assumed. We only generate the proof obligations for the initialization code in the main package.
registerPackage(mainPkg, specCollector, generateInitProof = true)(config)
// Note that `config.enableLazyImports` disables init blocks, global variables, import preconditions, and init
// postconditions in the type checker, which means that we do not have to generate an init proof.
registerPackage(mainPkg, specCollector, generateInitProof = !config.enableLazyImports)(config)

if (config.enableLazyImports) {
// early return to not add any proof obligations because we have made sure in the type checker that
// there aren't any global variables, init blocks, init postconditions, and import preconditions (in the
// packages that have been parsed). Note that packages that have been skipped because of the laziness might
// still contain such features.
return
}

// check that the postconditions of every package are enough to satisfy all of their imports' preconditions
val src = meta(mainPkg, info)
Expand Down Expand Up @@ -3121,8 +3131,9 @@ object Desugar {
info.context.getTypeInfo(RegularImport(imp.importPath))(config) match {
case Some(Right(tI)) =>
val desugaredPre = imp.importPres.map(specificationD(FunctionContext.empty(), info))
Violation.violation(!config.enableLazyImports || desugaredPre.isEmpty, s"Import precondition found despite running with ${Config.enableLazyImportOptionPrettyPrinted}")
specCollector.addImportPres(tI.getTypeInfo.tree.originalRoot, desugaredPre)
case e => Violation.violation(s"Unexpected value found $e while importing ${imp.importPath}")
case e => Violation.violation(config.enableLazyImports, s"Unexpected value found $e while importing ${imp.importPath} - type information is assumed to be available for all packages when Gobra is executed with lazy imports disabled")
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,26 @@
package viper.gobra.frontend.info.implementation.typing

import org.bitbucket.inkytonik.kiama.util.Messaging.{message, noMessages}
import viper.gobra.GoVerifier
import viper.gobra.ast.frontend.{PExplicitQualifiedImport, PImplicitQualifiedImport, PImport, PNode, PUnqualifiedImport}
import viper.gobra.frontend.Config
import viper.gobra.frontend.PackageResolver.RegularImport
import viper.gobra.frontend.info.implementation.TypeInfoImpl

trait ImportTyping extends BaseTyping { this: TypeInfoImpl =>

lazy val wellDefImport: WellDefinedness[PImport] = createWellDef { imp =>
forceNonLazyImport(imp.importPath, imp)
imp match {
(if (config.enableLazyImports) {
imp.importPres.flatMap(importPre => message(importPre, s"Import preconditions are not allowed when executing ${GoVerifier.name} with ${Config.enableLazyImportOptionPrettyPrinted}"))
} else {
forceNonLazyImport(imp.importPath, imp)
noMessages
}) ++ (imp match {
case _: PExplicitQualifiedImport => noMessages
case _: PUnqualifiedImport => noMessages
// this case should never occur as these nodes should get converted in the parse postprocessing step
case n: PImplicitQualifiedImport => message(n, s"Explicit qualifier could not be derived")
}
})
}

// This method forces a package to be processed non-lazily - every import can cause side effects,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
package viper.gobra.frontend.info.implementation.typing

import org.bitbucket.inkytonik.kiama.util.Messaging.{Messages, error, noMessages}
import viper.gobra.GoVerifier
import viper.gobra.ast.frontend._
import viper.gobra.frontend.Config
import viper.gobra.frontend.info.implementation.TypeInfoImpl
import viper.gobra.util.Constants

Expand All @@ -26,19 +28,23 @@ trait MemberTyping extends BaseTyping { this: TypeInfoImpl =>
case b: PConstDecl =>
b.specs.flatMap(wellDefConstSpec)
case g: PVarDecl if isGlobalVarDeclaration(g) =>
// HACK: without this explicit check, Gobra does not find repeated declarations
// of global variables. This has to do with the changes introduced in PR #186.
// We need this check nonetheless because the checks performed in the "true" branch
// assume that the ids are well-defined.
val idsOkMsgs = g.left.flatMap(l => wellDefID(l).out)
if (idsOkMsgs.isEmpty) {
val isGhost = isEnclosingGhost(g)
g.right.flatMap(isExpr(_).out) ++
declarableTo.errors(g.right map exprType, g.typ map typeSymbType, g.left map idType)(g) ++
error(g, s"Currently, global variables cannot be made ghost", isGhost) ++
acyclicGlobalDeclaration.errors(g)(g)
if (config.enableLazyImports) {
error(g, s"Global variables are not allowed when executing ${GoVerifier.name} with ${Config.enableLazyImportOptionPrettyPrinted}")
} else {
idsOkMsgs
// HACK: without this explicit check, Gobra does not find repeated declarations
// of global variables. This has to do with the changes introduced in PR #186.
// We need this check nonetheless because the checks performed in the "true" branch
// assume that the ids are well-defined.
val idsOkMsgs = g.left.flatMap(l => wellDefID(l).out)
if (idsOkMsgs.isEmpty) {
val isGhost = isEnclosingGhost(g)
g.right.flatMap(isExpr(_).out) ++
declarableTo.errors(g.right map exprType, g.typ map typeSymbType, g.left map idType)(g) ++
error(g, s"Currently, global variables cannot be made ghost", isGhost) ++
acyclicGlobalDeclaration.errors(g)(g)
} else {
idsOkMsgs
}
}
case s: PActualStatement =>
wellDefStmt(s).out
Expand Down Expand Up @@ -68,7 +74,9 @@ trait MemberTyping extends BaseTyping { this: TypeInfoImpl =>

private def wellDefIfInitBlock(n: PFunctionDecl): Messages = {
val isInitFunc = n.id.name == Constants.INIT_FUNC_NAME
if (isInitFunc) {
if (isInitFunc && config.enableLazyImports) {
error(n, s"Init functions are not supported when executing ${GoVerifier.name} with ${Config.enableLazyImportOptionPrettyPrinted}")
} else if (isInitFunc) {
val errorMsgEmptySpec =
s"Currently, ${Constants.INIT_FUNC_NAME} blocks cannot have specification. Instead, use package postconditions and import preconditions."
val errorMsgNoInOut = s"func ${Constants.INIT_FUNC_NAME} must have no arguments and no return values"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@
package viper.gobra.frontend.info.implementation.typing

import org.bitbucket.inkytonik.kiama.util.Entity
import org.bitbucket.inkytonik.kiama.util.Messaging.{Messages, error}
import org.bitbucket.inkytonik.kiama.util.Messaging.{Messages, error, message}
import viper.gobra.GoVerifier
import viper.gobra.ast.frontend.{PExpression, POld, PPackage, PProgram, PVarDecl}
import viper.gobra.frontend.Config
import viper.gobra.frontend.info.base.{SymbolTable => st}
import viper.gobra.frontend.info.implementation.TypeInfoImpl
import viper.gobra.frontend.info.implementation.property.{AssignMode, StrictAssignMode}
Expand All @@ -18,30 +20,34 @@ trait ProgramTyping extends BaseTyping { this: TypeInfoImpl =>

lazy val wellDefProgram: WellDefinedness[PProgram] = createWellDef {
case PProgram(_, posts, imports, members) =>
// Obtains global variable declarations sorted by the order in which they appear in the file
val sortedByPosDecls: Vector[PVarDecl] = {
val unsortedDecls: Vector[PVarDecl] = members.collect{ case d: PVarDecl => d }
// we require a package to be able to obtain position information
val pkgOpt: Option[PPackage] = unsortedDecls.headOption.flatMap(tryEnclosingPackage)
// sort declarations by the order in which they appear in the program
unsortedDecls.sortBy{ decl =>
pkgOpt.get.positions.positions.getStart(decl) match {
case Some(pos) => (pos.line, pos.column)
case _ => Violation.violation(s"Could not find position information of $decl.")
if (config.enableLazyImports) {
posts.flatMap(post => message(post, s"Init postconditions are not allowed when executing ${GoVerifier.name} with ${Config.enableLazyImportOptionPrettyPrinted}"))
} else {
// Obtains global variable declarations sorted by the order in which they appear in the file
val sortedByPosDecls: Vector[PVarDecl] = {
val unsortedDecls: Vector[PVarDecl] = members.collect{ case d: PVarDecl => d }
// we require a package to be able to obtain position information
val pkgOpt: Option[PPackage] = unsortedDecls.headOption.flatMap(tryEnclosingPackage)
// sort declarations by the order in which they appear in the program
unsortedDecls.sortBy{ decl =>
pkgOpt.get.positions.positions.getStart(decl) match {
case Some(pos) => (pos.line, pos.column)
case _ => Violation.violation(s"Could not find position information of $decl.")
}
}
}
}
// HACK: without this explicit check, Gobra does not find repeated declarations
// of global variables. This has to do with the changes introduced in PR #186.
// We need this check nonetheless because the checks performed in the "true" branch
// assume that the ids are well-defined.
val idsOkMsgs = sortedByPosDecls.flatMap(d => d.left).flatMap(l => wellDefID(l).out)
if (idsOkMsgs.isEmpty) {
globalDeclSatisfiesDepOrder(sortedByPosDecls) ++
hasOldExpression(posts) ++
hasOldExpression(imports.flatMap(_.importPres))
} else {
idsOkMsgs
// HACK: without this explicit check, Gobra does not find repeated declarations
// of global variables. This has to do with the changes introduced in PR #186.
// We need this check nonetheless because the checks performed in the "true" branch
// assume that the ids are well-defined.
val idsOkMsgs = sortedByPosDecls.flatMap(d => d.left).flatMap(l => wellDefID(l).out)
if (idsOkMsgs.isEmpty) {
globalDeclSatisfiesDepOrder(sortedByPosDecls) ++
hasOldExpression(posts) ++
hasOldExpression(imports.flatMap(_.importPres))
} else {
idsOkMsgs
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Any copyright is dedicated to the Public Domain.
// http://creativecommons.org/publicdomain/zero/1.0/

// this is the same file as `globals-simple01.gobra` but additionally provides the `--disableGlobalVars` flag to Gobra.
// ##(--enableLazyImport)

//:: ExpectedOutput(type_error)
initEnsures acc(&A) && acc(&B) && acc(&C, 1/2)
package pkg

//:: ExpectedOutput(type_error)
importRequires true
import "fmt"

import (
//:: ExpectedOutput(type_error)
importRequires true
//:: ExpectedOutput(type_error)
importRequires true
"bytes"
//:: ExpectedOutput(type_error)
importRequires true
"sync"
)


//:: ExpectedOutput(type_error)
var A int = 0
//:: ExpectedOutput(type_error)
var B, C = f()
//:: ExpectedOutput(type_error)
var _ = g()
//:: ExpectedOutput(type_error)
var D struct{}

decreases
func f() (int, bool)

decreases
func g() interface{}

requires acc(&A, 1/2) && A == 1
ensures acc(&A, 1/2)
func testRead() {
var v1 int = A
assert v1 == 1
}

requires acc(&C) && C
func testWrite() {
C = false
}

// Variable hiding works correctly, as in Go
func tesVarHiding() {
var A int = 0
assert A == 0
}