diff --git a/src/main/scala/viper/gobra/ast/internal/PrettyPrinter.scala b/src/main/scala/viper/gobra/ast/internal/PrettyPrinter.scala index b1cca4748..57ce6406e 100644 --- a/src/main/scala/viper/gobra/ast/internal/PrettyPrinter.scala +++ b/src/main/scala/viper/gobra/ast/internal/PrettyPrinter.scala @@ -594,6 +594,7 @@ class DefaultPrettyPrinter extends PrettyPrinter with kiama.output.PrettyPrinter case DfltVal(typ) => "dflt" <> brackets(showType(typ)) case Tuple(args) => parens(showExprList(args)) case Deref(exp, _) => "*" <> showExpr(exp) + case UncheckedRef(ref, _) => "&" <> showAddressable(ref) case Ref(ref, _) => "&" <> showAddressable(ref) case FieldRef(recv, field) => showExpr(recv) <> "." <> field.name case StructUpdate(base, field, newVal) => showExpr(base) <> brackets(showField(field) <+> ":=" <+> showExpr(newVal)) diff --git a/src/main/scala/viper/gobra/ast/internal/Program.scala b/src/main/scala/viper/gobra/ast/internal/Program.scala index 1f6494c67..361a61e81 100644 --- a/src/main/scala/viper/gobra/ast/internal/Program.scala +++ b/src/main/scala/viper/gobra/ast/internal/Program.scala @@ -1001,7 +1001,18 @@ case class Deref(exp: Expr, underlyingTypeExpr: Type)(val info: Source.Parser.In override val typ: Type = underlyingTypeExpr.asInstanceOf[PointerT].t } -case class Ref(ref: Addressable, typ: PointerT)(val info: Source.Parser.Info) extends Expr with Location +/** Only used internally to separate the type encodings. Should not be created by the desugarer. */ +case class UncheckedRef(ref: Addressable, typ: PointerT)(val info: Source.Parser.Info) extends Expr { + def checked: Ref = Ref(ref, typ)(info) +} + +object UncheckedRef { + def apply(exp: Expr)(info: Source.Parser.Info): UncheckedRef = Ref(exp)(info).unchecked +} + +case class Ref(ref: Addressable, typ: PointerT)(val info: Source.Parser.Info) extends Expr { + def unchecked: UncheckedRef = UncheckedRef(ref, typ)(info) +} object Ref { def apply(ref: Expr)(info: Source.Parser.Info): Ref = { diff --git a/src/main/scala/viper/gobra/reporting/DefaultErrorBackTranslator.scala b/src/main/scala/viper/gobra/reporting/DefaultErrorBackTranslator.scala index cd144f934..75bfe1bb1 100644 --- a/src/main/scala/viper/gobra/reporting/DefaultErrorBackTranslator.scala +++ b/src/main/scala/viper/gobra/reporting/DefaultErrorBackTranslator.scala @@ -95,7 +95,7 @@ object DefaultErrorBackTranslator { val transformVerificationErrorReason: VerificationErrorReason => VerificationErrorReason = { case AssertionFalseError(info / OverflowCheckAnnotation) => OverflowErrorReason(info) - case AssertionFalseError(info / ReceiverNotNilCheckAnnotation) => InterfaceReceiverIsNilReason(info) + case AssertionFalseError(info / ReceiverNotNilCheckAnnotation) => ReceiverIsNilReason(info) case x => x } diff --git a/src/main/scala/viper/gobra/reporting/VerifierError.scala b/src/main/scala/viper/gobra/reporting/VerifierError.scala index 963bd624c..72c9401ea 100644 --- a/src/main/scala/viper/gobra/reporting/VerifierError.scala +++ b/src/main/scala/viper/gobra/reporting/VerifierError.scala @@ -483,9 +483,9 @@ case class OverflowErrorReason(node: Source.Verifier.Info) extends VerificationE override def message: String = s"Expression ${node.origin.tag.trim} might cause integer overflow." } -case class InterfaceReceiverIsNilReason(node: Source.Verifier.Info) extends VerificationErrorReason { +case class ReceiverIsNilReason(node: Source.Verifier.Info) extends VerificationErrorReason { override def id: String = "receiver_is_nil_error" - override def message: String = s"The receiver might be nil" + override def message: String = s"The receiver ${node.origin.tag.trim} might be nil" } case class DynamicValueNotASubtypeReason(node: Source.Verifier.Info) extends VerificationErrorReason { diff --git a/src/main/scala/viper/gobra/translator/context/Context.scala b/src/main/scala/viper/gobra/translator/context/Context.scala index 224a5c613..0203bf6f0 100644 --- a/src/main/scala/viper/gobra/translator/context/Context.scala +++ b/src/main/scala/viper/gobra/translator/context/Context.scala @@ -99,6 +99,8 @@ trait Context { def reference(x: in.Location): CodeWriter[vpr.Exp] = typeEncoding.reference(this)(x) + def safeReference(x: in.Location): CodeWriter[vpr.Exp] = typeEncoding.safeReference(this)(x) + def value(x: in.Expr): CodeWriter[vpr.Exp] = typeEncoding.value(this)(x) def footprint(x: in.Location, perm: in.Expr): CodeWriter[vpr.Exp] = typeEncoding.addressFootprint(this)(x, perm) @@ -138,7 +140,6 @@ trait Context { } // mapping - def addVars(vars: vpr.LocalVarDecl*): Context // fresh variable counter @@ -146,7 +147,7 @@ trait Context { def freshNames: Iterator[String] = internalFreshNames /** internal fresh name iterator that additionally provides a getter function for its counter value */ - protected def internalFreshNames: FreshNameIterator + protected def internalFreshNames: Context.FreshNameIterator /** copy constructor */ def :=( @@ -165,7 +166,28 @@ trait Context { unknownValueN: UnknownValues = unknownValue, typeEncodingN: TypeEncoding = typeEncoding, defaultEncodingN: DefaultEncoding = defaultEncoding, - initialFreshCounterValueN: Int = internalFreshNames.getValue + initialFreshCounterValueN: Option[Int] = None, + ): Context = { + update(fieldN, arrayN, seqToSetN, seqToMultisetN, seqMultiplicityN, optionN, optionToSeqN, sliceN, fixpointN, tupleN, equalityN, conditionN, unknownValueN, typeEncodingN, defaultEncodingN, initialFreshCounterValueN) + } + + protected def update( + fieldN: Fields, + arrayN: Arrays, + seqToSetN: SeqToSet, + seqToMultisetN: SeqToMultiset, + seqMultiplicityN: SeqMultiplicity, + optionN: Options, + optionToSeqN: OptionToSeq, + sliceN: Slices, + fixpointN: Fixpoint, + tupleN: Tuples, + equalityN: Equality, + conditionN: Conditions, + unknownValueN: UnknownValues, + typeEncodingN: TypeEncoding, + defaultEncodingN: DefaultEncoding, + initialFreshCounterValueN: Option[Int], ): Context @@ -189,8 +211,10 @@ trait Context { col.finalize(typeEncoding) } +} + +object Context { trait FreshNameIterator extends Iterator[String] { def getValue: Int } - } diff --git a/src/main/scala/viper/gobra/translator/context/ContextImpl.scala b/src/main/scala/viper/gobra/translator/context/ContextImpl.scala index 1bfa6abc0..32c6b4b70 100644 --- a/src/main/scala/viper/gobra/translator/context/ContextImpl.scala +++ b/src/main/scala/viper/gobra/translator/context/ContextImpl.scala @@ -22,25 +22,25 @@ import viper.gobra.translator.library.tuples.Tuples import viper.gobra.translator.library.unknowns.UnknownValues import viper.silver.ast.LocalVarDecl -case class ContextImpl( - field: Fields, - array: Arrays, - seqToSet: SeqToSet, - seqToMultiset: SeqToMultiset, - seqMultiplicity: SeqMultiplicity, - option: Options, - optionToSeq: OptionToSeq, - slice: Slices, - fixpoint: Fixpoint, - tuple: Tuples, - equality: Equality, - condition: Conditions, - unknownValue: UnknownValues, - typeEncoding: TypeEncoding, - defaultEncoding: DefaultEncoding, - table: LookupTable, - initialFreshCounterValue: Int = 0 - ) extends Context { +class ContextImpl( + override val field: Fields, + override val array: Arrays, + override val seqToSet: SeqToSet, + override val seqToMultiset: SeqToMultiset, + override val seqMultiplicity: SeqMultiplicity, + override val option: Options, + override val optionToSeq: OptionToSeq, + override val slice: Slices, + override val fixpoint: Fixpoint, + override val tuple: Tuples, + override val equality: Equality, + override val condition: Conditions, + override val unknownValue: UnknownValues, + override val typeEncoding: TypeEncoding, + override val defaultEncoding: DefaultEncoding, + override val table: LookupTable, + override val internalFreshNames: Context.FreshNameIterator = ContextImpl.FreshNameIteratorImpl(0), + ) extends Context { def this(conf: TranslatorConfig, table: LookupTable) = { this( @@ -64,24 +64,24 @@ case class ContextImpl( } /** copy constructor */ - override def :=( - fieldN: Fields = field, - arrayN: Arrays = array, - seqToSetN: SeqToSet = seqToSet, - seqToMultisetN: SeqToMultiset = seqToMultiset, - seqMultiplicityN: SeqMultiplicity = seqMultiplicity, - optionN: Options = option, - optionToSeqN: OptionToSeq = optionToSeq, - sliceN: Slices = slice, - fixpointN: Fixpoint = fixpoint, - tupleN: Tuples = tuple, - equalityN: Equality = equality, - conditionN: Conditions = condition, - unknownValueN: UnknownValues = unknownValue, - typeEncodingN: TypeEncoding = typeEncoding, - defaultEncodingN: DefaultEncoding = defaultEncoding, - initialFreshCounterValueN: Int = internalFreshNames.getValue - ): Context = copy( + override def update( + fieldN: Fields, + arrayN: Arrays, + seqToSetN: SeqToSet, + seqToMultisetN: SeqToMultiset, + seqMultiplicityN: SeqMultiplicity, + optionN: Options, + optionToSeqN: OptionToSeq, + sliceN: Slices, + fixpointN: Fixpoint, + tupleN: Tuples, + equalityN: Equality, + conditionN: Conditions, + unknownValueN: UnknownValues, + typeEncodingN: TypeEncoding, + defaultEncodingN: DefaultEncoding, + initialFreshCounterValueN: Option[Int], + ): Context = new ContextImpl( fieldN, arrayN, seqToSetN, @@ -97,14 +97,18 @@ case class ContextImpl( unknownValueN, typeEncodingN, defaultEncodingN, - initialFreshCounterValue = initialFreshCounterValueN + table, + initialFreshCounterValueN match { + case None => internalFreshNames + case Some(n) => ContextImpl.FreshNameIteratorImpl(n) + }, ) override def addVars(vars: LocalVarDecl*): Context = this +} - override val internalFreshNames: FreshNameIteratorImpl = FreshNameIteratorImpl(initialFreshCounterValue) - - case class FreshNameIteratorImpl(private val initialValue: Int) extends FreshNameIterator { +object ContextImpl { + case class FreshNameIteratorImpl(private val initialValue: Int) extends Context.FreshNameIterator { private var currentValue: Int = initialValue override def hasNext: Boolean = true @@ -117,5 +121,4 @@ case class ContextImpl( override def getValue: Int = currentValue } - } diff --git a/src/main/scala/viper/gobra/translator/encodings/PointerEncoding.scala b/src/main/scala/viper/gobra/translator/encodings/PointerEncoding.scala index 9b94bafae..a2a9d5be8 100644 --- a/src/main/scala/viper/gobra/translator/encodings/PointerEncoding.scala +++ b/src/main/scala/viper/gobra/translator/encodings/PointerEncoding.scala @@ -72,7 +72,6 @@ class PointerEncoding extends LeafTypeEncoding { * Ref[*e] -> [e] */ override def reference(ctx: Context): in.Location ==> CodeWriter[vpr.Exp] = default(super.reference(ctx)){ - case (loc: in.Deref) :: _ / Shared => - ctx.expression(loc.exp) + case (loc: in.Deref) :: _ / Shared => ctx.expression(loc.exp) } } diff --git a/src/main/scala/viper/gobra/translator/encodings/arrays/ArrayEncoding.scala b/src/main/scala/viper/gobra/translator/encodings/arrays/ArrayEncoding.scala index 2ca272282..945b896cc 100644 --- a/src/main/scala/viper/gobra/translator/encodings/arrays/ArrayEncoding.scala +++ b/src/main/scala/viper/gobra/translator/encodings/arrays/ArrayEncoding.scala @@ -145,7 +145,7 @@ class ArrayEncoding extends TypeEncoding with SharedArrayEmbedding { * R[ mset(e: [n]T) ] -> seqToMultiset(ex_array_toSeq([e])) * R[ x in (e: [n]T) ] -> [x] in ex_array_toSeq([e]) * R[ x # (e: [n]T) ] -> [x] # ex_array_toSeq([e]) - * R[ loc: ([n]T)@ ] -> arrayConversion(L[loc]) + * R[ loc: ([n]T)@ ] -> arrayConversion(Ref[loc]) // assert [&loc != nil] if [n]T has size zero */ override def expression(ctx: Context): in.Expr ==> CodeWriter[vpr.Exp] = default(super.expression(ctx)){ case (loc@ in.IndexedExp(base :: ctx.Array(len, t), idx, _)) :: _ / Exclusive => @@ -175,7 +175,7 @@ class ArrayEncoding extends TypeEncoding with SharedArrayEmbedding { case n@ in.Length(e :: ctx.Array(len, t) / m) => m match { case Exclusive => ctx.expression(e).map(ex.length(_, cptParam(len, t)(ctx))(n)(ctx)) - case Shared => ctx.reference(e.asInstanceOf[in.Location]).map(sh.length(_, cptParam(len, t)(ctx))(n)(ctx)) + case Shared => ctx.safeReference(e.asInstanceOf[in.Location]).map(sh.length(_, cptParam(len, t)(ctx))(n)(ctx)) } case in.Length(exp :: ctx.*(t: in.ArrayT)) => @@ -221,7 +221,7 @@ class ArrayEncoding extends TypeEncoding with SharedArrayEmbedding { vE <- ctx.expression(e) } yield ctx.seqMultiplicity.create(vX, ex.toSeq(vE, cptParam(len, t)(ctx))(n)(ctx))(pos, info, errT) - case (loc: in.Location) :: ctx.Array(len, t) / Shared => + case (loc: in.Location) :: ctx.NoZeroSize(ctx.Array(len, t)) / Shared => val (pos, info, errT) = loc.vprMeta for { arg <- ctx.reference(loc) @@ -260,12 +260,15 @@ class ArrayEncoding extends TypeEncoding with SharedArrayEmbedding { * i.e. all permissions involved in converting the shared location to an exclusive r-value. * An encoding for type T should be defined at all shared locations of type T. * + * The default implements: + * Footprint[loc: T@ if sizeOf(T) == 0] -> [&loc != nil: *T°] + * * Footprint[loc: [n]T] -> forall idx :: {trigger} 0 <= idx < n ==> Footprint[ loc[idx] ] * where trigger = sh_array_get(Ref[loc], idx, n) * * We do not use let because (at the moment) Viper does not accept quantified permissions with let expressions. */ - override def addressFootprint(ctx: Context): (in.Location, in.Expr) ==> CodeWriter[vpr.Exp] = { + override def addressFootprint(ctx: Context): (in.Location, in.Expr) ==> CodeWriter[vpr.Exp] = super.addressFootprint(ctx).orElse { case (loc :: ctx.Array(len, t) / Shared, perm) => val (pos, info, errT) = loc.vprMeta val typ = underlyingType(loc.typ)(ctx) @@ -311,13 +314,16 @@ class ArrayEncoding extends TypeEncoding with SharedArrayEmbedding { * */ private val conversionFunc: FunctionGenerator[ComponentParameter] = new FunctionGenerator[ComponentParameter]{ def genFunction(t: ComponentParameter)(ctx: Context): vpr.Function = { + val info = Source.Parser.Internal + val argType = t.arrayT(Shared) // variable name does not matter because it is the only argument - val x = in.LocalVar("x", argType)(Source.Parser.Internal) + val x = in.LocalVar("x", argType)(info) + val resultType = argType.withAddressability(Exclusive) val vResultType = typ(ctx)(resultType) // variable name does not matter because it is turned into a vpr.Result - val resultVar = in.LocalVar("res", resultType)(Source.Parser.Internal) + val resultVar = in.LocalVar("res", resultType)(info) val post = pure(equal(ctx)(x, resultVar, x))(ctx).res // replace resultVar with vpr.Result .transform{ case v: vpr.LocalVar if v.name == resultVar.id => vpr.Result(vResultType)() } @@ -327,7 +333,7 @@ class ArrayEncoding extends TypeEncoding with SharedArrayEmbedding { formalArgs = Vector(variable(ctx)(x)), typ = vResultType, pres = Vector( - pure(addressFootprint(ctx)(x, in.WildcardPerm(Source.Parser.Internal)))(ctx).res, + pure(addressFootprint(ctx)(x, in.WildcardPerm(info)))(ctx).res, synthesized(termination.DecreasesWildcard(None))("This function is assumed to terminate") ), posts = Vector(post), diff --git a/src/main/scala/viper/gobra/translator/encodings/closures/MethodObjectEncoder.scala b/src/main/scala/viper/gobra/translator/encodings/closures/MethodObjectEncoder.scala index a4a0e7d9b..a830f5baf 100644 --- a/src/main/scala/viper/gobra/translator/encodings/closures/MethodObjectEncoder.scala +++ b/src/main/scala/viper/gobra/translator/encodings/closures/MethodObjectEncoder.scala @@ -8,7 +8,7 @@ package viper.gobra.translator.encodings.closures import viper.gobra.ast.{internal => in} import viper.gobra.reporting.BackTranslator.{ErrorTransformer, RichErrorMessage} -import viper.gobra.reporting.{InterfaceReceiverIsNilReason, MethodObjectGetterPreconditionError, Source} +import viper.gobra.reporting.{ReceiverIsNilReason, MethodObjectGetterPreconditionError, Source} import viper.gobra.translator.Names import viper.gobra.translator.context.Context import viper.gobra.translator.encodings.interfaces.{InterfaceComponent, InterfaceComponentImpl, InterfaceUtils, PolymorphValueComponent, PolymorphValueComponentImpl, TypeComponent, TypeComponentImpl} @@ -105,6 +105,6 @@ class MethodObjectEncoder(domain: ClosureDomainEncoder) { case e@vprerr.PreconditionInAppFalse(_, _, _) if e causedBy call=> val info = m.vprMeta._2.asInstanceOf[Source.Verifier.Info] val recvInfo = m.recv.vprMeta._2.asInstanceOf[Source.Verifier.Info] - MethodObjectGetterPreconditionError(info).dueTo(InterfaceReceiverIsNilReason(recvInfo)) + MethodObjectGetterPreconditionError(info).dueTo(ReceiverIsNilReason(recvInfo)) } } \ No newline at end of file diff --git a/src/main/scala/viper/gobra/translator/encodings/combinators/TypeEncoding.scala b/src/main/scala/viper/gobra/translator/encodings/combinators/TypeEncoding.scala index b17392453..6d92d79a1 100644 --- a/src/main/scala/viper/gobra/translator/encodings/combinators/TypeEncoding.scala +++ b/src/main/scala/viper/gobra/translator/encodings/combinators/TypeEncoding.scala @@ -10,7 +10,8 @@ import org.bitbucket.inkytonik.kiama.==> import viper.gobra.ast.{internal => in} import viper.gobra.ast.internal.theory.Comparability import viper.gobra.reporting.BackTranslator.{ErrorTransformer, RichErrorMessage} -import viper.gobra.reporting.{AssignmentError, DefaultErrorBackTranslator, LoopInvariantNotWellFormedError, MethodContractNotWellFormedError, NoPermissionToRangeExpressionError, Source} +// import viper.gobra.reporting.{AssignmentError, DefaultErrorBackTranslator, DerefError, LoopInvariantNotWellFormedError, MethodContractNotWellFormedError, NoPermissionToRangeExpressionError, Source} +import viper.gobra.reporting.{AssignmentError, DefaultErrorBackTranslator, LoadError, LoopInvariantNotWellFormedError, MethodContractNotWellFormedError, NoPermissionToRangeExpressionError, Source} import viper.gobra.theory.Addressability.{Exclusive, Shared} import viper.gobra.translator.library.Generator import viper.gobra.translator.context.Context @@ -18,10 +19,11 @@ import viper.gobra.translator.util.ViperWriter.{CodeWriter, MemberWriter} import viper.silver.verifier.{errors => vprerr} import viper.silver.{ast => vpr} -import scala.annotation.unused +import scala.annotation.{tailrec, unused} trait TypeEncoding extends Generator { + import viper.gobra.translator.encodings.combinators.TypeEncoding._ import viper.gobra.translator.util.TypePatterns._ import viper.gobra.translator.util.ViperWriter.CodeLevel._ import viper.gobra.translator.util.ViperWriter.{MemberLevel => ml} @@ -121,7 +123,7 @@ trait TypeEncoding extends Generator { for { footprint <- addressFootprint(ctx)(loc, in.FullPerm(loc.info)) eq1 <- ctx.equal(loc, in.DfltVal(t.withAddressability(Exclusive))(loc.info))(loc) - eq2 <- ctx.equal(in.Ref(loc)(loc.info), in.NilLit(in.PointerT(t, Exclusive))(loc.info))(loc) + eq2 <- ctx.equal(in.UncheckedRef(loc)(loc.info), in.NilLit(in.PointerT(t, Exclusive))(loc.info))(loc) } yield vpr.Inhale(vpr.And(footprint, vpr.And(eq1, vpr.Not(eq2)(pos, info, errT))(pos, info, errT))(pos, info, errT))(pos, info, errT) } @@ -237,7 +239,9 @@ trait TypeEncoding extends Generator { * (1) exclusive operations on T, which includes literals and default values * (2) shared expression of type T * The default implements exclusive local variables and constants with [[variable]] and [[Fixpoint::get]], respectively. - * Furthermore, the default implements global exclusive variables and conversions as [T(e: T)] -> [e]. + * Furthermore, the default implements + * [T(e: T)] -> [e] + * [loc: T@ if sizeOf(T) == 0] -> assert [&loc != nil: *T°]; [dflt(T°)] */ def expression(ctx: Context): in.Expr ==> CodeWriter[vpr.Exp] = { case v: in.GlobalConst if typ(ctx) isDefinedAt v.typ => unit(ctx.fixpoint.get(v)(ctx)) @@ -251,6 +255,11 @@ trait TypeEncoding extends Generator { )(pos, info, typ, errT) unit(vprExpr) case in.Conversion(t2, expr :: t) if typ(ctx).isDefinedAt(t) && typ(ctx).isDefinedAt(t2) => ctx.expression(expr) + case (loc: in.Location) :: (t@ctx.ZeroSize()) / Shared if typ(ctx).isDefinedAt(t) => + for { + dflt <- ctx.expression(in.DfltVal(t.withAddressability(Exclusive))(loc.info)) + checked <- checkNotNil(loc, dflt)(ctx) + } yield checked } /** @@ -328,6 +337,22 @@ trait TypeEncoding extends Generator { unit(vprExpr) } + /** + * Checks whether an L-value is safe, i.e. does not cause a runtime panic due to dereferencing nil. + * Instead of checking that a dereference is safe immediately, the encoding checks that usages of l-values are safe. + * Usages of L-values are: (1) taking a reference, (2) taking a slice, (3) converting to R-value + * + * SafeRef[loc: T@] => assert [&loc != nil: *T°]; Ref[loc] + */ + final def safeReference(ctx: Context): in.Location ==> CodeWriter[vpr.Exp] = { + val r = reference(ctx); { case loc@r(w) => + for { + vprLoc <- w + checked <- checkNotNil(loc, vprLoc)(ctx) + } yield checked + } + } + /** * Encodes an expression such that substitution is possible. * For an expression `e` and a context `K`, the encoding of K[e] is semantically equal to K[x] with x := Value[e]. @@ -348,8 +373,13 @@ trait TypeEncoding extends Generator { * Encodes the permissions for all addresses of a shared type, * i.e. all permissions involved in converting the shared location to an exclusive r-value. * An encoding for type T should be defined at all shared locations of type T. + * + * The default implements: + * Footprint[loc: T@ if sizeOf(T) == 0] -> [&loc != nil: *T°] */ - def addressFootprint(@unused ctx: Context): (in.Location, in.Expr) ==> CodeWriter[vpr.Exp] = PartialFunction.empty + def addressFootprint(ctx: Context): (in.Location, in.Expr) ==> CodeWriter[vpr.Exp] = { + case (loc :: (t@ctx.ZeroSize()) / Shared, _) if typ(ctx).isDefinedAt(t) => checkNotNil(loc)(ctx) + } /** * Encodes whether a value is comparable or not. @@ -450,7 +480,6 @@ trait TypeEncoding extends Generator { } } - /** * Alternative version of `orElse` to simplify delegations to super implementations. * @@ -471,3 +500,52 @@ trait TypeEncoding extends Generator { node(pos, info, errT)(ctx) } } + +object TypeEncoding { + + import viper.gobra.translator.util.ViperWriter.{CodeLevel => cl} + + /** + * Checks whether an L-value is safe, i.e. does not cause a runtime panic due to dereferencing nil. + * + * assert [&loc != nil: *T°]; res + * + */ + final def checkNotNil(loc: in.Location, res: vpr.Exp)(ctx: Context): CodeWriter[vpr.Exp] = { + if (cannotBeNil(loc)) cl.unit(res) + else { + for { + cond <- checkNotNil(loc)(ctx) + checked <- cl.assertWithDefaultReason(cond, res, LoadError)(ctx) + } yield checked + } + } + + /** + * Checks whether an L-value is safe, i.e. does not cause a runtime panic due to dereferencing nil. + * + * [&loc != nil: *T°] + * + */ + final def checkNotNil(loc: in.Location)(ctx: Context): CodeWriter[vpr.Exp] = { + + val annotatedInfo = loc.info.asInstanceOf[Source.Parser.Single].createAnnotatedInfo(Source.ReceiverNotNilCheckAnnotation) + val (pos, info, errT) = loc.vprMeta + + if (cannotBeNil(loc)) cl.unit(vpr.TrueLit()(pos, info, errT)) + else { + ctx.expression(in.UneqCmp( + in.UncheckedRef(loc)(annotatedInfo), + in.NilLit(in.PointerT(loc.typ, Exclusive))(annotatedInfo) + )(annotatedInfo)) + } + } + + @tailrec + private def cannotBeNil(l: in.Expr): Boolean = l match { + case _: in.Var => true + case l: in.FieldRef => cannotBeNil(l.recv) + case l: in.IndexedExp => cannotBeNil(l.base) + case _ => false + } +} diff --git a/src/main/scala/viper/gobra/translator/encodings/programs/ProgramsImpl.scala b/src/main/scala/viper/gobra/translator/encodings/programs/ProgramsImpl.scala index b47a3aa74..4d7ab1581 100644 --- a/src/main/scala/viper/gobra/translator/encodings/programs/ProgramsImpl.scala +++ b/src/main/scala/viper/gobra/translator/encodings/programs/ProgramsImpl.scala @@ -25,7 +25,7 @@ class ProgramsImpl extends Programs { def goM(member: in.Member): MemberWriter[(Vector[vpr.Member], Context)] = { /** we use a separate context for each member in order to reset the fresh counter */ - val ctx = mainCtx := (initialFreshCounterValueN = 0) + val ctx = mainCtx := (initialFreshCounterValueN = Some(0)) ctx.member(member).map(m => (m, ctx)) } diff --git a/src/main/scala/viper/gobra/translator/encodings/slices/SliceEncoding.scala b/src/main/scala/viper/gobra/translator/encodings/slices/SliceEncoding.scala index f2fd03fdb..26780ea6a 100644 --- a/src/main/scala/viper/gobra/translator/encodings/slices/SliceEncoding.scala +++ b/src/main/scala/viper/gobra/translator/encodings/slices/SliceEncoding.scala @@ -70,10 +70,10 @@ class SliceEncoding(arrayEmb : SharedArrayEmbedding) extends LeafTypeEncoding { * R[ nil: []T° ] -> nilSlice() * R[ len(e: []T) ] -> slen([e]) * R[ cap(e: []T) ] -> scap([e]) - * R[ (e: [n]T@)[e1:e2] ] -> sliceFromArray([e], [e1], [e2]) - * R[ (e: *[n]T)[e1:e2] ] -> sliceFromArray([*e], [e1], [e2]) - * R[ (e: [n]T@)[e1:e2:e3] ] -> fullSliceFromArray([e], [e1], [e2], [e3]) - * R[ (e: *[n]T)[e1:e2:e3] ] -> fullSliceFromArray([*e], [e1], [e2], [e3]) + * R[ (e: [n]T@)[e1:e2] ] -> sliceFromArray(SafeRef[e], [e1], [e2]) + * R[ (e: *[n]T)[e1:e2] ] -> sliceFromArray([*e], [e1], [e2]) // no SafeRef needed as we just dereferenced `e` + * R[ (e: [n]T@)[e1:e2:e3] ] -> fullSliceFromArray(SafeRef[e], [e1], [e2], [e3]) + * R[ (e: *[n]T)[e1:e2:e3] ] -> fullSliceFromArray([*e], [e1], [e2], [e3]) // no SafeRef needed as we just dereferenced `e` * R[ (e: []T)[e1:e2] ] -> sliceFromSlice([e], [e1], [e2]) * R[ (e: []T)[e1:e2:e3] ] -> fullSliceFromSlice([e], [e1], [e2], [e3]) * @@ -99,7 +99,7 @@ class SliceEncoding(arrayEmb : SharedArrayEmbedding) extends LeafTypeEncoding { } yield withSrc(ctx.slice.cap(expT), exp) case exp @ in.Slice((base : in.Location) :: ctx.Array(_, _) / Shared, low, high, max, _) => for { - baseT <- ctx.reference(base) + baseT <- ctx.safeReference(base) baseType = base.typ.asInstanceOf[in.ArrayT] unboxedBaseT = arrayEmb.unbox(baseT, baseType)(base)(ctx) lowT <- goE(low) @@ -109,6 +109,7 @@ class SliceEncoding(arrayEmb : SharedArrayEmbedding) extends LeafTypeEncoding { case None => withSrc(sliceFromArray(unboxedBaseT, lowT, highT)(ctx), exp) case Some(maxT) => withSrc(fullSliceFromArray(unboxedBaseT, lowT, highT, maxT)(ctx), exp) } + case n@in.Slice(base :: ctx.*(t: in.ArrayT), low, high, max, _) => val baseInfo = base.info val derefBase = in.Deref(base, in.PointerT(t, t.addressability))(baseInfo) diff --git a/src/main/scala/viper/gobra/translator/encodings/structs/SharedStructComponent.scala b/src/main/scala/viper/gobra/translator/encodings/structs/SharedStructComponent.scala index 7d7e9a7ea..229bfa2d1 100644 --- a/src/main/scala/viper/gobra/translator/encodings/structs/SharedStructComponent.scala +++ b/src/main/scala/viper/gobra/translator/encodings/structs/SharedStructComponent.scala @@ -31,7 +31,7 @@ trait SharedStructComponent extends Generator { * All permissions involved in the conversion should be returned by [[addressFootprint]]. * * The default implementation is: - * Convert[loc: Struct{F}@] -> create_ex_struct( R[loc.f] | f in F ) + * Convert[loc: Struct{F}@] -> create_ex_struct( [loc.f] | f in F ) */ def convertToExclusive(loc: in.Location)(ctx: Context, ex: ExclusiveStructComponent): CodeWriter[vpr.Exp] = { loc match { @@ -54,7 +54,6 @@ trait SharedStructComponent extends Generator { * i.e. all permissions involved in converting the shared location to an exclusive value ([[convertToExclusive]]). * An encoding for type T should be defined at all shared locations of type T. * - * The default implementation is: * Footprint[loc: Struct{F}@] -> AND f in F: Footprint[loc.f] */ def addressFootprint(loc: in.Location, perm: in.Expr)(ctx: Context): CodeWriter[vpr.Exp] = { diff --git a/src/main/scala/viper/gobra/translator/encodings/structs/StructEncoding.scala b/src/main/scala/viper/gobra/translator/encodings/structs/StructEncoding.scala index 35017308b..fb1f938b5 100644 --- a/src/main/scala/viper/gobra/translator/encodings/structs/StructEncoding.scala +++ b/src/main/scala/viper/gobra/translator/encodings/structs/StructEncoding.scala @@ -99,13 +99,13 @@ class StructEncoding extends TypeEncoding { * [loc: T@ = rhs] -> exhale Footprint[loc]; inhale Footprint[loc] && [loc == rhs] * * [e.f: Struct{F}° = rhs] -> [ e = e[f := rhs] ] - * [lhs: Struct{F}@ = rhs] -> FOREACH f in F: [lhs.f = rhs.f] + * [lhs: Struct{F}@ = rhs if size != 0] -> FOREACH f in F: [lhs.f = rhs.f] */ override def assignment(ctx: Context): (in.Assignee, in.Expr, in.Node) ==> CodeWriter[vpr.Stmt] = default(super.assignment(ctx)){ case (in.Assignee((fa: in.FieldRef) :: _ / Exclusive), rhs, src) => ctx.assignment(in.Assignee(fa.recv), in.StructUpdate(fa.recv, fa.field, rhs)(src.info))(src) - case (in.Assignee(lhs :: ctx.Struct(lhsFs) / Shared), rhs :: ctx.Struct(rhsFs), src) => + case (in.Assignee(lhs :: ctx.NoZeroSize(ctx.Struct(lhsFs)) / Shared), rhs :: ctx.Struct(rhsFs), src) => for { x <- bind(lhs)(ctx) y <- bind(rhs)(ctx) @@ -186,7 +186,7 @@ class StructEncoding extends TypeEncoding { * R[ (base: Struct{F})[f = e] ] -> ex_struct_upd([base], f, [e], F) * R[ dflt(Struct{F}) ] -> create_ex_struct( [T] | (f: T) in F ) * R[ structLit(E) ] -> create_ex_struct( [e] | e in E ) - * R[ loc: Struct{F}@ ] -> convert_to_exclusive( Ref[loc] ) + * R[ loc: Struct{F}@ ] -> convert_to_exclusive( Ref[loc] ) // assert [&loc != nil] if Struct{F} has size zero */ override def expression(ctx: Context): in.Expr ==> CodeWriter[vpr.Exp] = default(super.expression(ctx)){ case (loc@ in.FieldRef(recv :: ctx.Struct(fs), field)) :: _ / Exclusive => @@ -214,7 +214,7 @@ class StructEncoding extends TypeEncoding { val fieldExprs = lit.args.map(arg => ctx.expression(arg)) sequence(fieldExprs).map(ex.create(_, cptParam(fs)(ctx))(lit)(ctx)) - case (loc: in.Location) :: ctx.Struct(_) / Shared => + case (loc: in.Location) :: ctx.NoZeroSize(ctx.Struct(_)) / Shared => sh.convertToExclusive(loc)(ctx, ex) } @@ -238,8 +238,13 @@ class StructEncoding extends TypeEncoding { * Encodes the permissions for all addresses of a shared type, * i.e. all permissions involved in converting the shared location to an exclusive r-value. * An encoding for type T should be defined at all shared locations of type T. + * + * The default implements: + * Footprint[loc: T@ if sizeOf(T) == 0] -> [&loc != nil: *T°] + * + * Footprint[loc: Struct{F}@] -> AND f in F: Footprint[loc.f] */ - override def addressFootprint(ctx: Context): (in.Location, in.Expr) ==> CodeWriter[vpr.Exp] = { + override def addressFootprint(ctx: Context): (in.Location, in.Expr) ==> CodeWriter[vpr.Exp] = super.addressFootprint(ctx).orElse { case (loc :: ctx.Struct(_) / Shared, perm) => sh.addressFootprint(loc, perm)(ctx) } @@ -290,7 +295,7 @@ class StructEncoding extends TypeEncoding { val src = in.DfltVal(resType)(Source.Parser.Internal) // variable name does not matter because it is turned into a vpr.Result val resDummy = in.LocalVar("res", resType)(src.info) - val resFAccs = fs.map(f => in.Ref(in.FieldRef(resDummy, f)(src.info))(src.info)) + val resFAccs = fs.map(f => in.UncheckedRef(in.FieldRef(resDummy, f)(src.info))(src.info)) val fieldEq = resFAccs map (f => ctx.equal(f, in.DfltVal(f.typ)(src.info))(src)) // termination measure val pre = synthesized(termination.DecreasesWildcard(None))("This function is assumed to terminate") diff --git a/src/main/scala/viper/gobra/translator/encodings/typeless/MemoryEncoding.scala b/src/main/scala/viper/gobra/translator/encodings/typeless/MemoryEncoding.scala index b5149518a..4a4b1a558 100644 --- a/src/main/scala/viper/gobra/translator/encodings/typeless/MemoryEncoding.scala +++ b/src/main/scala/viper/gobra/translator/encodings/typeless/MemoryEncoding.scala @@ -16,7 +16,8 @@ import viper.silver.{ast => vpr} class MemoryEncoding extends Encoding { override def expression(ctx: Context): in.Expr ==> CodeWriter[vpr.Exp] = { - case r: in.Ref => ctx.reference(r.ref.op) + case r: in.UncheckedRef => ctx.reference(r.ref.op) + case r: in.Ref => ctx.safeReference(r.ref.op) case x@ in.EqCmp(l, r) => ctx.goEqual(l, r)(x) case x@ in.UneqCmp(l, r) => ctx.goEqual(l, r)(x).map(v => withSrc(vpr.Not(v), x)) case x@ in.GhostEqCmp(l, r) => ctx.equal(l, r)(x) diff --git a/src/main/scala/viper/gobra/translator/util/TypePatterns.scala b/src/main/scala/viper/gobra/translator/util/TypePatterns.scala index c2c2813bd..71bc6446c 100644 --- a/src/main/scala/viper/gobra/translator/util/TypePatterns.scala +++ b/src/main/scala/viper/gobra/translator/util/TypePatterns.scala @@ -244,6 +244,13 @@ object TypePatterns { def unapply(arg: in.Type): Boolean = isZeroSize(underlyingType(arg)(ctx))(ctx.table) } + + object NoZeroSize { + def unapply(arg: in.Type): Option[in.Type] = underlyingType(arg)(ctx) match { + case ctx.ZeroSize() => None + case t => Some(t) + } + } } diff --git a/src/main/scala/viper/gobra/translator/util/ViperWriter.scala b/src/main/scala/viper/gobra/translator/util/ViperWriter.scala index c5387b74b..50496afcd 100644 --- a/src/main/scala/viper/gobra/translator/util/ViperWriter.scala +++ b/src/main/scala/viper/gobra/translator/util/ViperWriter.scala @@ -418,6 +418,13 @@ object ViperWriter { } yield call } + /* Can be used in expressions. */ + def assertWithDefaultReason(cond: vpr.Exp, exp: vpr.Exp, error: Source.Verifier.Info => VerificationError)(ctx: Context): Writer[vpr.Exp] = { + // In the future, this might do something more sophisticated + val (res, errT) = ctx.condition.assert(cond, exp, (info, reason) => error(info) dueTo DefaultErrorBackTranslator.defaultTranslate(reason)) + errorT(errT).map(_ => res) + } + /* Emits Viper statements. */ def assert(cond: vpr.Exp, reasonT: (Source.Verifier.Info, ErrorReason) => VerificationError): Writer[Unit] = { val res = vpr.Assert(cond)(cond.pos, cond.info, cond.errT) diff --git a/src/test/resources/regressions/features/closures/closures-recursion2-mutual.gobra b/src/test/resources/regressions/features/closures/closures-recursion2-mutual.gobra index 6e4ce0e07..c186bdb57 100644 --- a/src/test/resources/regressions/features/closures/closures-recursion2-mutual.gobra +++ b/src/test/resources/regressions/features/closures/closures-recursion2-mutual.gobra @@ -1,3 +1,5 @@ +//:: IgnoreFile(/gobra/issue/532/) + // Any copyright is dedicated to the Public Domain. // http://creativecommons.org/publicdomain/zero/1.0/ diff --git a/src/test/resources/regressions/issues/000491-2.gobra b/src/test/resources/regressions/issues/000491-2.gobra new file mode 100644 index 000000000..34828beda --- /dev/null +++ b/src/test/resources/regressions/issues/000491-2.gobra @@ -0,0 +1,16 @@ +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/publicdomain/zero/1.0/ + +package main + +type Pair struct { + x int + y int +} + +func f(p *Pair) (r *int) { + //:: ExpectedOutput(load_error:receiver_is_nil_error) + r = &p.x + return +} + diff --git a/src/test/resources/regressions/issues/000491-3.gobra b/src/test/resources/regressions/issues/000491-3.gobra new file mode 100644 index 000000000..76267421f --- /dev/null +++ b/src/test/resources/regressions/issues/000491-3.gobra @@ -0,0 +1,19 @@ +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/publicdomain/zero/1.0/ + +package issue491_3 + +// tests that slicing an array pointer correctly results in a non-nilness proof obligation as the pointer is dereferenced. +// inspired by #805 + +preserves p != nil ==> acc(p) +func shorthand(p *[10]int) { + //:: ExpectedOutput(load_error:receiver_is_nil_error) + _ = p[0:5] +} + +func caller() { + arr@ := [10]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} + shorthand(&arr) + shorthand(nil) +} diff --git a/src/test/resources/regressions/issues/000491.gobra b/src/test/resources/regressions/issues/000491.gobra new file mode 100644 index 000000000..7b3300c38 --- /dev/null +++ b/src/test/resources/regressions/issues/000491.gobra @@ -0,0 +1,73 @@ +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/publicdomain/zero/1.0/ + +package main + +type A struct { + x int +} + +func test1() { + var x *A + //:: ExpectedOutput(load_error:receiver_is_nil_error) + var y *A = &(*x) +} + +func test2() { + var x *[3]int + //:: ExpectedOutput(load_error:receiver_is_nil_error) + var y []int = (*x)[:] +} + +requires x != nil +func test3(x *struct{}) { + v := *x + + //:: ExpectedOutput(assert_error:assertion_error) + assert false +} + +requires x != nil +func test4(x *[0]int) { + v := *x + + //:: ExpectedOutput(assert_error:assertion_error) + assert false +} + +func test5(x *struct{}) { + //:: ExpectedOutput(load_error:receiver_is_nil_error) + v := *x +} + +func test6(x *[0]int) { + //:: ExpectedOutput(load_error:receiver_is_nil_error) + v := *x +} + +func test7(x, y *struct{}) { + //:: ExpectedOutput(assert_error:assertion_error) + assert x == y +} + +func test8(x, y *[0]int) { + //:: ExpectedOutput(assert_error:assertion_error) + assert x == y +} + +func test9(x *struct{}) { + //:: ExpectedOutput(assignment_error:receiver_is_nil_error) + *x = struct{}{} +} + +func test10(x *[0]int) { + //:: ExpectedOutput(assignment_error:receiver_is_nil_error) + *x = [0]int{} +} + +func test11() { + var x *[10]int + + //:: ExpectedOutput(load_error:receiver_is_nil_error) + assert len(*x) == 10 +} \ No newline at end of file