From b2455e3606311f05088e4ddfb8f517b2a9d7af43 Mon Sep 17 00:00:00 2001 From: Pieter Bos Date: Wed, 25 Oct 2023 12:51:49 +0200 Subject: [PATCH] make systemerrors use context by default, and turn non-fatal exceptions into systemerrors --- src/col/vct/col/ast/node/NodeImpl.scala | 2 +- src/col/vct/col/ast/node/ProgramImpl.scala | 4 +- .../vct/col/ast/statement/StatementImpl.scala | 4 +- .../ast/statement/composite/BlockImpl.scala | 3 +- .../ast/statement/composite/LoopImpl.scala | 29 +++++++++------ .../ast/statement/composite/ScopeImpl.scala | 12 +++--- .../ast/statement/composite/SwitchImpl.scala | 26 +------------ .../statement/terminal/FramedProofImpl.scala | 2 +- .../ast/statement/terminal/LabelImpl.scala | 19 +++++----- .../vct/col/rewrite/NonLatchingRewriter.scala | 4 +- .../util/ConstructingSuccessorOfContext.scala | 10 ++++- .../col/util/CurrentCheckNodeContext.scala | 10 ++++- .../col/util/CurrentCheckProgramContext.scala | 12 ++++++ .../col/util/CurrentProgramCheckContext.scala | 6 --- .../util/CurrentProgramRewriteContext.scala | 6 --- .../col/util/CurrentRewriteNodeContext.scala | 14 +++++++ .../util/CurrentRewriteProgramContext.scala | 12 ++++++ src/col/vct/col/util/FrozenScopes.scala | 2 +- src/col/vct/col/util/Scopes.scala | 30 ++++++--------- src/colhelper/ColDefs.scala | 2 + src/colhelper/ColHelperRewriteHelpers.scala | 2 +- src/hre/hre/io/CollectString.scala | 13 +++++++ src/hre/vct/result/VerificationError.scala | 37 ++++++++++++++++--- src/main/vct/main/modes/Verify.scala | 7 +++- 24 files changed, 166 insertions(+), 102 deletions(-) create mode 100644 src/col/vct/col/util/CurrentCheckProgramContext.scala delete mode 100644 src/col/vct/col/util/CurrentProgramCheckContext.scala delete mode 100644 src/col/vct/col/util/CurrentProgramRewriteContext.scala create mode 100644 src/col/vct/col/util/CurrentRewriteNodeContext.scala create mode 100644 src/col/vct/col/util/CurrentRewriteProgramContext.scala create mode 100644 src/hre/hre/io/CollectString.scala diff --git a/src/col/vct/col/ast/node/NodeImpl.scala b/src/col/vct/col/ast/node/NodeImpl.scala index e156a69c34..9a67d8ed65 100644 --- a/src/col/vct/col/ast/node/NodeImpl.scala +++ b/src/col/vct/col/ast/node/NodeImpl.scala @@ -52,7 +52,7 @@ trait NodeImpl[G] extends Show { this: Node[G] => if(childrenErrors.nonEmpty) { childrenErrors } else { - VerificationError.context(CurrentCheckNodeContext(this)) { + VerificationError.withContext(CurrentCheckNodeContext(this)) { check(context) } } diff --git a/src/col/vct/col/ast/node/ProgramImpl.scala b/src/col/vct/col/ast/node/ProgramImpl.scala index 124dbf87e7..4ff48978af 100644 --- a/src/col/vct/col/ast/node/ProgramImpl.scala +++ b/src/col/vct/col/ast/node/ProgramImpl.scala @@ -4,14 +4,14 @@ import vct.col.ast.{Node, Program} import vct.col.ast.util.Declarator import vct.col.check.{CheckContext, CheckError} import vct.col.print.{Ctx, Doc} -import vct.col.util.CurrentProgramCheckContext +import vct.col.util.CurrentCheckProgramContext import vct.result.VerificationError trait ProgramImpl[G] extends Declarator[G] { this: Program[G] => def check: Seq[CheckError] = checkTrans(CheckContext()) override def checkContextRecursor[T](context: CheckContext[G], f: (CheckContext[G], Node[G]) => T): Seq[T] = - VerificationError.context(CurrentProgramCheckContext(this)) { + VerificationError.withContext(CurrentCheckProgramContext(this)) { super.checkContextRecursor(context, f) } diff --git a/src/col/vct/col/ast/statement/StatementImpl.scala b/src/col/vct/col/ast/statement/StatementImpl.scala index a332b02628..3a5caf423d 100644 --- a/src/col/vct/col/ast/statement/StatementImpl.scala +++ b/src/col/vct/col/ast/statement/StatementImpl.scala @@ -6,7 +6,7 @@ import vct.col.print._ trait StatementImpl[G] extends NodeFamilyImpl[G] { this: Statement[G] => def layoutAsBlock(implicit ctx: Ctx): Doc = - Text("{") <>> Doc.stack(blockElementsForLayout) <+/> "}" + Text("{") <>> foldBlock(_ <+/> _) <+/> "}" - def blockElementsForLayout(implicit ctx: Ctx): Seq[Show] = Seq(this) + def foldBlock(f: (Doc, Doc) => Doc)(implicit ctx: Ctx): Doc = show } diff --git a/src/col/vct/col/ast/statement/composite/BlockImpl.scala b/src/col/vct/col/ast/statement/composite/BlockImpl.scala index c31e80c986..1c9da4595d 100644 --- a/src/col/vct/col/ast/statement/composite/BlockImpl.scala +++ b/src/col/vct/col/ast/statement/composite/BlockImpl.scala @@ -5,6 +5,5 @@ import vct.col.print._ trait BlockImpl[G] { this: Block[G] => override def layout(implicit ctx: Ctx): Doc = layoutAsBlock - override def blockElementsForLayout(implicit ctx: Ctx): Seq[Show] = - statements.flatMap(_.blockElementsForLayout) + override def foldBlock(f: (Doc, Doc) => Doc)(implicit ctx: Ctx): Doc = NodeDoc(this, Doc.fold(statements.map(_.foldBlock(f)))(f)) } \ No newline at end of file diff --git a/src/col/vct/col/ast/statement/composite/LoopImpl.scala b/src/col/vct/col/ast/statement/composite/LoopImpl.scala index 882e621ccd..a49b404417 100644 --- a/src/col/vct/col/ast/statement/composite/LoopImpl.scala +++ b/src/col/vct/col/ast/statement/composite/LoopImpl.scala @@ -77,19 +77,26 @@ trait LoopImpl[G] { this: Loop[G] => Group(Text("while") <+> "(" <> Doc.arg(cond) <> ")") <+> body.layoutAsBlock )) - def layoutControlElement(e: Show)(implicit ctx: Ctx): Option[Show] = Some(e match { - case Eval(e) => e.show - case a: Assign[G] => a.layoutAsExpr - case e: VeyMontAssignExpression[G] => return layoutControlElement(e.assign) - case e: VeyMontCommExpression[G] => return layoutControlElement(e.assign) - case LocalDecl(local) => local.show - case JavaLocalDeclarationStatement(local) => local.show - - case _ => return None - }) + def simpleControlElements(stat: Statement[G])(implicit ctx: Ctx): Option[Doc] = stat match { + case Eval(e) => Some(e.show) + case a: Assign[G] => Some(a.layoutAsExpr) + case e: VeyMontAssignExpression[G] => simpleControlElements(e.assign) + case e: VeyMontCommExpression[G] => simpleControlElements(e.assign) + case LocalDecl(local) => Some(local.show) + case JavaLocalDeclarationStatement(local) => Some(local.show) + + case Block(stats) => + stats + .map(simpleControlElements(_)) + .foldLeft[Option[Seq[Doc]]](Some(Nil)) { + case (Some(acc), Some(more)) => Some(acc :+ more) + case (_, _) => None + } + .map(elems => NodeDoc(stat, Doc.fold(elems)(_ <> "," <+/> _))) + } def layoutControl(stat: Statement[G])(implicit ctx: Ctx): Doc = - Group(Doc.args(stat.blockElementsForLayout.map(layoutControlElement(_).getOrElse(return stat.show)))) + simpleControlElements(stat).map(doc => Group(Doc.arg(doc))).getOrElse(stat.show) def layoutGenericFor(implicit ctx: Ctx): Doc = Doc.stack(Seq( diff --git a/src/col/vct/col/ast/statement/composite/ScopeImpl.scala b/src/col/vct/col/ast/statement/composite/ScopeImpl.scala index 4b317a91ca..05e00f5546 100644 --- a/src/col/vct/col/ast/statement/composite/ScopeImpl.scala +++ b/src/col/vct/col/ast/statement/composite/ScopeImpl.scala @@ -11,9 +11,11 @@ trait ScopeImpl[G] { context.withScope(locals, toScan = Seq(body)) override def layout(implicit ctx: Ctx): Doc = layoutAsBlock - override def blockElementsForLayout(implicit ctx: Ctx): Seq[Show] = - locals.map(local => ctx.syntax match { - case Ctx.Silver => Text("var") <+> local - case _ => local.show <> ";" - }) ++ body.blockElementsForLayout + override def foldBlock(f: (Doc, Doc) => Doc)(implicit ctx: Ctx): Doc = + NodeDoc(this, + Doc.fold(locals.map(local => ctx.syntax match { + case Ctx.Silver => Text("var") <+> local + case _ => local.show <> ";" + }) :+ body.foldBlock(f))(f) + ) } \ No newline at end of file diff --git a/src/col/vct/col/ast/statement/composite/SwitchImpl.scala b/src/col/vct/col/ast/statement/composite/SwitchImpl.scala index 8c30881664..29b51dca28 100644 --- a/src/col/vct/col/ast/statement/composite/SwitchImpl.scala +++ b/src/col/vct/col/ast/statement/composite/SwitchImpl.scala @@ -13,30 +13,6 @@ trait SwitchImpl[G] { this: Switch[G] => def isNotCase(s: Show): Boolean = !isCase(s) - @tailrec - private def layoutContentWithCaseLabel(content: Seq[Show], acc: Show)(implicit ctx: Ctx): Show = { - if(content.isEmpty) acc - else { - val label = content.head - val more = content.tail - val (uncased, casePrefix) = more.span(isNotCase) - val newAcc = acc.show <+/> label <> Nest(Line <> Doc.stack(uncased)) - layoutContentWithCaseLabel(casePrefix, newAcc) - } - } - - def layoutContent(implicit ctx: Ctx): Doc = { - val elements = body.blockElementsForLayout - val (uncased, casePrefix) = elements.span(isNotCase) - - if(casePrefix.isEmpty) { - // PB: very weird switch, or we just can't peek through blocks - Line <> Doc.stack(uncased) - } else { - Nest(Line <> Doc.stack(uncased)) <> layoutContentWithCaseLabel(casePrefix, Empty) - } - } - override def layout(implicit ctx: Ctx): Doc = - Group(Text("switch") <+> "(" <> Doc.arg(expr) <> ")") <+> "{" <> Nest(layoutContent) <+/> "}" + Group(Text("switch") <+> "(" <> Doc.arg(expr) <> ")") <+> "{" <>> body.foldBlock(_ <+/> _) <+/> "}" } \ No newline at end of file diff --git a/src/col/vct/col/ast/statement/terminal/FramedProofImpl.scala b/src/col/vct/col/ast/statement/terminal/FramedProofImpl.scala index aacaa4f048..8473c908e4 100644 --- a/src/col/vct/col/ast/statement/terminal/FramedProofImpl.scala +++ b/src/col/vct/col/ast/statement/terminal/FramedProofImpl.scala @@ -13,7 +13,7 @@ trait FramedProofImpl[G] { this: FramedProof[G] => )) def layoutWithSpec(implicit ctx: Ctx): Doc = - Doc.spec(Show.lazily(frameHeader(_))) <+/> Doc.stack(body.blockElementsForLayout) <+/> Doc.inlineSpec(Text("}")) + Doc.spec(Show.lazily(frameHeader(_))) <+/> body.foldBlock(_ <+/> _) <+/> Doc.inlineSpec(Text("}")) def layoutNative(implicit ctx: Ctx): Doc = Doc.stack(Seq( diff --git a/src/col/vct/col/ast/statement/terminal/LabelImpl.scala b/src/col/vct/col/ast/statement/terminal/LabelImpl.scala index 2bb7699518..8d9b8b27a4 100644 --- a/src/col/vct/col/ast/statement/terminal/LabelImpl.scala +++ b/src/col/vct/col/ast/statement/terminal/LabelImpl.scala @@ -1,23 +1,22 @@ package vct.col.ast.statement.terminal import vct.col.ast.Label -import vct.col.print.{Ctx, Doc, Show, Text} +import vct.col.print.{Ctx, Doc, NodeDoc, Show, Text} trait LabelImpl[G] { this: Label[G] => - override def blockElementsForLayout(implicit ctx: Ctx): Seq[Show] = ctx.syntax match { - case Ctx.PVL => Seq(layoutLabel) ++ stat.blockElementsForLayout - case Ctx.Silver => Seq(layoutLabel) ++ stat.blockElementsForLayout - case Ctx.Java => Seq(this) - case Ctx.C | Ctx.Cuda | Ctx.OpenCL | Ctx.CPP => Seq(layoutLabel) ++ stat.blockElementsForLayout + override def foldBlock(f: (Doc, Doc) => Doc)(implicit ctx: Ctx): Doc = ctx.syntax match { + case Ctx.PVL => f(layoutLabel, stat.foldBlock(f)) + case Ctx.Silver => f(layoutLabel, stat.foldBlock(f)) + case Ctx.Java => layoutLabel <+> stat.show + case Ctx.C | Ctx.Cuda | Ctx.OpenCL | Ctx.CPP => f(layoutLabel, stat.foldBlock(f)) } - def layoutLabel(implicit ctx: Ctx): Doc = ctx.syntax match { + def layoutLabel(implicit ctx: Ctx): Doc = NodeDoc(this, ctx.syntax match { case Ctx.PVL => Text("label") <+> ctx.name(decl) <> ";" case Ctx.Silver => Text("label") <+> ctx.name(decl) case Ctx.Java => Text(ctx.name(decl)) <> ":" case Ctx.C | Ctx.Cuda | Ctx.OpenCL | Ctx.CPP => Text(ctx.name(decl)) <> ":" - } + }) - override def layout(implicit ctx: Ctx): Doc = - Doc.stack(blockElementsForLayout) + override def layout(implicit ctx: Ctx): Doc = foldBlock(_ <+/> _) } \ No newline at end of file diff --git a/src/col/vct/col/rewrite/NonLatchingRewriter.scala b/src/col/vct/col/rewrite/NonLatchingRewriter.scala index 33c1f7d3d9..21c7484073 100644 --- a/src/col/vct/col/rewrite/NonLatchingRewriter.scala +++ b/src/col/vct/col/rewrite/NonLatchingRewriter.scala @@ -1,14 +1,14 @@ package vct.col.rewrite import vct.col.ast._ -import vct.col.util.CurrentProgramRewriteContext +import vct.col.util.CurrentRewriteProgramContext import vct.result.VerificationError class NonLatchingRewriter[Pre, Post]() extends AbstractRewriter[Pre, Post] { override def dispatch(context: Verification[Pre]): Verification[Post] = rewriteDefault(context) override def dispatch(context: VerificationContext[Pre]): VerificationContext[Post] = rewriteDefault(context) override def dispatch(program: Program[Pre]): Program[Post] = - VerificationError.context(CurrentProgramRewriteContext(program)) { + VerificationError.withContext(CurrentRewriteProgramContext(program)) { rewriteDefault(program) } diff --git a/src/col/vct/col/util/ConstructingSuccessorOfContext.scala b/src/col/vct/col/util/ConstructingSuccessorOfContext.scala index 17882491a0..f3367061a4 100644 --- a/src/col/vct/col/util/ConstructingSuccessorOfContext.scala +++ b/src/col/vct/col/util/ConstructingSuccessorOfContext.scala @@ -1,6 +1,14 @@ package vct.col.util import vct.col.ast.Declaration +import vct.col.print.Doc import vct.result.VerificationError -case class ConstructingSuccessorOfContext(decl: Declaration[_]) extends VerificationError.Context +case class ConstructingSuccessorOfContext(decl: Declaration[_]) extends VerificationError.Context { + override def tryMessageContext(message: String, err: VerificationError): Option[String] = + err.context[CurrentRewriteProgramContext].map { ctx => + Doc.messagesInContext(Seq( + (ctx.program, decl, message) + )) + } +} diff --git a/src/col/vct/col/util/CurrentCheckNodeContext.scala b/src/col/vct/col/util/CurrentCheckNodeContext.scala index ffa6eeafa3..a9ccff344b 100644 --- a/src/col/vct/col/util/CurrentCheckNodeContext.scala +++ b/src/col/vct/col/util/CurrentCheckNodeContext.scala @@ -1,6 +1,14 @@ package vct.col.util import vct.col.ast.Node +import vct.col.print.Doc import vct.result.VerificationError -case class CurrentCheckNodeContext(node: Node[_]) extends VerificationError.Context +case class CurrentCheckNodeContext(node: Node[_]) extends VerificationError.Context { + override def tryMessageContext(message: String, err: VerificationError): Option[String] = + err.context[CurrentCheckProgramContext].map { ctx => + Doc.messagesInContext(Seq( + (ctx.program, node, message) + )) + } +} diff --git a/src/col/vct/col/util/CurrentCheckProgramContext.scala b/src/col/vct/col/util/CurrentCheckProgramContext.scala new file mode 100644 index 0000000000..8f2107f084 --- /dev/null +++ b/src/col/vct/col/util/CurrentCheckProgramContext.scala @@ -0,0 +1,12 @@ +package vct.col.util + +import vct.col.ast.Program +import vct.col.print.Doc +import vct.result.VerificationError + +case class CurrentCheckProgramContext(program: Program[_]) extends VerificationError.Context { + override def tryMessageContext(message: String, err: VerificationError): Option[String] = + Some(Doc.messagesInContext(Seq( + (program, program, message) + ))) +} diff --git a/src/col/vct/col/util/CurrentProgramCheckContext.scala b/src/col/vct/col/util/CurrentProgramCheckContext.scala deleted file mode 100644 index 06d0c1c8c4..0000000000 --- a/src/col/vct/col/util/CurrentProgramCheckContext.scala +++ /dev/null @@ -1,6 +0,0 @@ -package vct.col.util - -import vct.col.ast.Node -import vct.result.VerificationError - -case class CurrentProgramCheckContext(node: Node[_]) extends VerificationError.Context diff --git a/src/col/vct/col/util/CurrentProgramRewriteContext.scala b/src/col/vct/col/util/CurrentProgramRewriteContext.scala deleted file mode 100644 index 83c31da05d..0000000000 --- a/src/col/vct/col/util/CurrentProgramRewriteContext.scala +++ /dev/null @@ -1,6 +0,0 @@ -package vct.col.util - -import vct.col.ast.Program -import vct.result.VerificationError - -case class CurrentProgramRewriteContext(program: Program[_]) extends VerificationError.Context diff --git a/src/col/vct/col/util/CurrentRewriteNodeContext.scala b/src/col/vct/col/util/CurrentRewriteNodeContext.scala new file mode 100644 index 0000000000..ac830a0bdd --- /dev/null +++ b/src/col/vct/col/util/CurrentRewriteNodeContext.scala @@ -0,0 +1,14 @@ +package vct.col.util + +import vct.col.ast.Node +import vct.col.print.Doc +import vct.result.VerificationError + +case class CurrentRewriteNodeContext(node: Node[_]) extends VerificationError.Context { + override def tryMessageContext(message: String, err: VerificationError): Option[String] = + err.context[CurrentRewriteProgramContext].map { ctx => + Doc.messagesInContext(Seq( + (ctx.program, node, message) + )) + } +} diff --git a/src/col/vct/col/util/CurrentRewriteProgramContext.scala b/src/col/vct/col/util/CurrentRewriteProgramContext.scala new file mode 100644 index 0000000000..25be84f4ca --- /dev/null +++ b/src/col/vct/col/util/CurrentRewriteProgramContext.scala @@ -0,0 +1,12 @@ +package vct.col.util + +import vct.col.ast.Program +import vct.col.print.Doc +import vct.result.VerificationError + +case class CurrentRewriteProgramContext(program: Program[_]) extends VerificationError.Context { + override def tryMessageContext(message: String, err: VerificationError): Option[String] = + Some(Doc.messagesInContext(Seq( + (program, program, message) + ))) +} diff --git a/src/col/vct/col/util/FrozenScopes.scala b/src/col/vct/col/util/FrozenScopes.scala index 95e1a9abf6..8f99752558 100644 --- a/src/col/vct/col/util/FrozenScopes.scala +++ b/src/col/vct/col/util/FrozenScopes.scala @@ -22,7 +22,7 @@ class FrozenScopes[Pre, Post, PreDecl <: Declaration[Pre], PostDecl <: Declarati override def computeSucc(decl: PreDecl): Option[PostDecl] = scopes.collectFirst { case m if m.contains(decl) => m(decl) } override def succ[RefDecl <: Declaration[Post]](decl: PreDecl)(implicit tag: ClassTag[RefDecl]): Ref[Post, RefDecl] = - VerificationError.context(ConstructingSuccessorOfContext(decl)) { + VerificationError.withContext(ConstructingSuccessorOfContext(decl)) { TimeTravel.cause { causeIdx => new LazyRef(computeSucc(decl).getOrElse(TimeTravel.badEffect(causeIdx, throw Scopes.NoSuccessor(decl))), Some(EqualityMeasure(this, decl))) } diff --git a/src/col/vct/col/util/Scopes.scala b/src/col/vct/col/util/Scopes.scala index 35f074ddde..8467c5ef6c 100644 --- a/src/col/vct/col/util/Scopes.scala +++ b/src/col/vct/col/util/Scopes.scala @@ -13,6 +13,7 @@ import vct.result.VerificationError.SystemError import scala.collection.mutable import scala.collection.mutable.ArrayBuffer import scala.reflect.ClassTag +import scala.util.Try object Scopes { case class WrongDeclarationCount(kind: ClassTag[_], count: Int) extends SystemError { @@ -36,25 +37,18 @@ object Scopes { case class NoSuccessor(pre: Declaration[_]) extends SystemError { override def text: String = { - ( - getContext[CurrentProgramCheckContext], - getContext[CurrentCheckNodeContext], - getContext[CurrentProgramRewriteContext], - getContext[ConstructingSuccessorOfContext], - ) match { - case ( - Some(CurrentProgramCheckContext(useProgram)), - Some(CurrentCheckNodeContext(useNode)), - Some(CurrentProgramRewriteContext(predProgram)), - Some(ConstructingSuccessorOfContext(predDecl)), - ) => - Doc.messagesInContext(Seq( - (predProgram, predDecl, "A reference to the successor of this declaration was made, ..."), - (useProgram, useNode, "... but it has no successor in this position.") - )) - case _ => pre.o.messageInContext("A reference to the successor of this declaration was made, but it has no successor.") + Try { + val useProgram = getContext[CurrentCheckProgramContext].program + val useNode = getContext[CurrentCheckNodeContext].node + val predProgram = getContext[CurrentRewriteProgramContext].program + val predDecl = getContext[ConstructingSuccessorOfContext].decl + Doc.messagesInContext(Seq( + (predProgram, predDecl, "A reference to the successor of this declaration was made, ..."), + (useProgram, useNode, "... but it has no successor in this position.") + )) + } getOrElse { + pre.o.messageInContext("A reference to the successor of this declaration was made, but it has no successor.") } - } } } diff --git a/src/colhelper/ColDefs.scala b/src/colhelper/ColDefs.scala index 4f49d7a739..e52b8b3871 100644 --- a/src/colhelper/ColDefs.scala +++ b/src/colhelper/ColDefs.scala @@ -17,6 +17,8 @@ object ColDefs { q"import vct.col.ref.Ref", q"import vct.col.resolve.ctx.Referrable", q"import vct.col.origin.ExpectedError", + q"import vct.result.VerificationError", + q"import vct.col.util.CurrentRewriteNodeContext", q"import hre.data.BitString", ) diff --git a/src/colhelper/ColHelperRewriteHelpers.scala b/src/colhelper/ColHelperRewriteHelpers.scala index 829b137228..264148d1c1 100644 --- a/src/colhelper/ColHelperRewriteHelpers.scala +++ b/src/colhelper/ColHelperRewriteHelpers.scala @@ -15,7 +15,7 @@ case class ColHelperRewriteHelpers(info: ColDescription) extends ColHelperMaker def rewrite(..${cls.params.map(rewriteHelperParam) ++ cls.blameType.toSeq.map(t => Term.Param(Nil, q"blame", Some(t), Some(q"rewriter.dispatch(subject.blame)"))) :+ - Term.Param(List(), q"o", Some(t"Origin"), Some(q"rewriter.dispatch(subject.o)"))}): ${cls.typ}[Post] = { + Term.Param(List(), q"o", Some(t"Origin"), Some(q"rewriter.dispatch(subject.o)"))}): ${cls.typ}[Post] = VerificationError.withContext(CurrentRewriteNodeContext(subject)) { ${ColDefs.DECLARATION_NAMESPACE.foldLeft( cls.make(cls.params.map(p => Term.Name(p.name.value)), q"blame", q"o") ){ diff --git a/src/hre/hre/io/CollectString.scala b/src/hre/hre/io/CollectString.scala new file mode 100644 index 0000000000..31225b1306 --- /dev/null +++ b/src/hre/hre/io/CollectString.scala @@ -0,0 +1,13 @@ +package hre.io + +import java.io.{ByteArrayOutputStream, PrintStream} +import java.nio.charset.StandardCharsets + +object CollectString { + def apply(f: PrintStream => Unit): String = { + val bytes = new ByteArrayOutputStream + val print = new PrintStream(bytes) + f(print) + new String(bytes.toByteArray, StandardCharsets.UTF_8) + } +} diff --git a/src/hre/vct/result/VerificationError.scala b/src/hre/vct/result/VerificationError.scala index cba416ea29..8811276f66 100644 --- a/src/hre/vct/result/VerificationError.scala +++ b/src/hre/vct/result/VerificationError.scala @@ -2,6 +2,7 @@ package vct.result import scala.collection.mutable.ArrayBuffer import scala.reflect.ClassTag +import scala.util.control.NonFatal sealed abstract class VerificationError extends RuntimeException { def text: String @@ -10,12 +11,22 @@ sealed abstract class VerificationError extends RuntimeException { val contexts: ArrayBuffer[VerificationError.Context] = ArrayBuffer() - def getContext[T](implicit tag: ClassTag[T]): Option[T] = { - contexts.collectFirst{ + def context[T](implicit tag: ClassTag[T]): Option[T] = { + contexts.collectFirst { case x : T => x } } + + def getContext[T](implicit tag: ClassTag[T]): T = context[T].get + + def messageContext(message: String, backupContext: String => String = identity): String = + // PB: note: the innermost context is added to contexts first, so we end up trying to use the most specific context + // for rendering the message context first, which is what we want. + contexts.foldLeft[Option[String]](None) { + case (Some(messageInContext), _) => Some(messageInContext) + case (None, ctx) => ctx.tryMessageContext(message, this) + } getOrElse backupContext(message) } object VerificationError { @@ -28,17 +39,33 @@ object VerificationError { * should be a (documented) UserError. */ abstract class SystemError extends VerificationError - case class Unreachable(text: String) extends SystemError + case class Unreachable(reason: String) extends SystemError { + override def text: String = messageContext(reason) + } + + case class Crash(cause: Throwable) extends SystemError { + initCause(cause) - trait Context + override def text: String = + messageContext("VerCors crashed near this position. Cause follows:") + } + + trait Context { + def tryMessageContext(message: String, err: VerificationError): Option[String] = None + } - def context[T](ctx: Context)(f: => T): T = { + def withContext[T](ctx: Context)(f: => T): T = { try { f } catch { case e : VerificationError => e.contexts += ctx throw e + case NonFatal(e) => + // PB: if you clicked here, scoll down to the exception cause :) + val crash = Crash(e) + crash.contexts += ctx + throw crash } } } \ No newline at end of file diff --git a/src/main/vct/main/modes/Verify.scala b/src/main/vct/main/modes/Verify.scala index f16c47a5d4..15c50e8443 100644 --- a/src/main/vct/main/modes/Verify.scala +++ b/src/main/vct/main/modes/Verify.scala @@ -2,7 +2,7 @@ package vct.main.modes import com.typesafe.scalalogging.LazyLogging import vct.options.Options -import hre.io.Readable +import hre.io.{CollectString, Readable} import sun.misc.{Signal, SignalHandler} import vct.col.origin.{BlameCollector, TableEntry, VerificationFailure} import vct.col.rewrite.bip.BIP @@ -72,9 +72,12 @@ case object Verify extends LazyLogging { } verifyWithOptions(options, options.inputs) match { - case Left(err) => + case Left(err: VerificationError.UserError) => logger.error(err.text) EXIT_CODE_ERROR + case Left(err: VerificationError.SystemError) => + logger.error(CollectString(s => err.printStackTrace(s))) + EXIT_CODE_ERROR case Right((Nil, report)) => logger.info("Verification completed successfully.") friendlyHandleBipReport(report, options.bipReportFile)